summaryrefslogtreecommitdiff
path: root/Kernel/Heap
diff options
context:
space:
mode:
authorDaniel Bertalan <dani@danielbertalan.dev>2021-06-20 09:39:20 +0200
committerAli Mohammad Pur <Ali.mpfard@gmail.com>2021-06-24 17:35:49 +0430
commit5491e0cdcc02916d44ca1f43eab8919d28e4406c (patch)
tree894dad3266b02f45e881f2c13306cfc0058a510e /Kernel/Heap
parentd6138df490389b313906f07c34ebdbc4b98183dc (diff)
downloadserenity-5491e0cdcc02916d44ca1f43eab8919d28e4406c.zip
AK+Kernel: Make fallible allocations compiler-agnostic
In standard C++, operators `new` and `new[]` are guaranteed to return a valid (non-null) pointer and throw an exception if the allocation couldn't be performed. Based on this, compilers did not check the returned pointer before attempting to use them for object construction. To avoid this, the allocator operators were changed to be `noexcept` in PR #7026, which made GCC emit the desired null checks. Unfortunately, this is a non-standard feature which meant that Clang would not accept these function definitions, as it did not match its expected declaration. To make compiling using Clang possible, the special "nothrow" versions of `new` are implemented in this commit. These take a tag type of `std::nothrow_t` (used for disambiguating from placement new/etc.), and are allowed by the standard to return null. There is a global variable, `std::nothrow`, declared with this type, which is also exported into the global namespace. To perform fallible allocations, the following syntax should be used: ```cpp auto ptr = new (nothrow) T; ``` As we don't support exceptions in the kernel, the only way of uphold the "throwing" new's guarantee is to abort if the allocation couldn't be performed. Once we have proper OOM handling in the kernel, this should only be used for critical allocations, where we wouldn't be able to recover from allocation failures anyway.
Diffstat (limited to 'Kernel/Heap')
-rw-r--r--Kernel/Heap/SlabAllocator.h22
-rw-r--r--Kernel/Heap/kmalloc.cpp22
-rw-r--r--Kernel/Heap/kmalloc.h32
3 files changed, 62 insertions, 14 deletions
diff --git a/Kernel/Heap/SlabAllocator.h b/Kernel/Heap/SlabAllocator.h
index de826d38e7..bfaa2c99e1 100644
--- a/Kernel/Heap/SlabAllocator.h
+++ b/Kernel/Heap/SlabAllocator.h
@@ -19,11 +19,23 @@ void slab_dealloc(void*, size_t slab_size);
void slab_alloc_init();
void slab_alloc_stats(Function<void(size_t slab_size, size_t allocated, size_t free)>);
-#define MAKE_SLAB_ALLOCATED(type) \
-public: \
- [[nodiscard]] void* operator new(size_t) noexcept { return slab_alloc(sizeof(type)); } \
- void operator delete(void* ptr) noexcept { slab_dealloc(ptr, sizeof(type)); } \
- \
+#define MAKE_SLAB_ALLOCATED(type) \
+public: \
+ [[nodiscard]] void* operator new(size_t) \
+ { \
+ void* ptr = slab_alloc(sizeof(type)); \
+ VERIFY(ptr); \
+ return ptr; \
+ } \
+ [[nodiscard]] void* operator new(size_t, const std::nothrow_t&) noexcept \
+ { \
+ return slab_alloc(sizeof(type)); \
+ } \
+ void operator delete(void* ptr) noexcept \
+ { \
+ slab_dealloc(ptr, sizeof(type)); \
+ } \
+ \
private:
}
diff --git a/Kernel/Heap/kmalloc.cpp b/Kernel/Heap/kmalloc.cpp
index e6538cc561..68e6fa3faa 100644
--- a/Kernel/Heap/kmalloc.cpp
+++ b/Kernel/Heap/kmalloc.cpp
@@ -29,6 +29,10 @@
#define POOL_SIZE (2 * MiB)
#define ETERNAL_RANGE_SIZE (2 * MiB)
+namespace std {
+const nothrow_t nothrow;
+}
+
static RecursiveSpinLock s_lock; // needs to be recursive because of dump_backtrace()
static void kmalloc_allocate_backup_memory();
@@ -300,12 +304,26 @@ size_t kmalloc_good_size(size_t size)
return size;
}
-void* operator new(size_t size) noexcept
+void* operator new(size_t size)
+{
+ void* ptr = kmalloc(size);
+ VERIFY(ptr);
+ return ptr;
+}
+
+void* operator new(size_t size, const std::nothrow_t&) noexcept
{
return kmalloc(size);
}
-void* operator new[](size_t size) noexcept
+void* operator new[](size_t size)
+{
+ void* ptr = kmalloc(size);
+ VERIFY(ptr);
+ return ptr;
+}
+
+void* operator new[](size_t size, const std::nothrow_t&) noexcept
{
return kmalloc(size);
}
diff --git a/Kernel/Heap/kmalloc.h b/Kernel/Heap/kmalloc.h
index c9db2b9285..fe35095663 100644
--- a/Kernel/Heap/kmalloc.h
+++ b/Kernel/Heap/kmalloc.h
@@ -13,13 +13,29 @@
#define KMALLOC_SCRUB_BYTE 0xbb
#define KFREE_SCRUB_BYTE 0xaa
-#define MAKE_ALIGNED_ALLOCATED(type, alignment) \
-public: \
- [[nodiscard]] void* operator new(size_t) noexcept { return kmalloc_aligned<alignment>(sizeof(type)); } \
- void operator delete(void* ptr) noexcept { kfree_aligned(ptr); } \
- \
+#define MAKE_ALIGNED_ALLOCATED(type, alignment) \
+public: \
+ [[nodiscard]] void* operator new(size_t) \
+ { \
+ void* ptr = kmalloc_aligned<alignment>(sizeof(type)); \
+ VERIFY(ptr); \
+ return ptr; \
+ } \
+ [[nodiscard]] void* operator new(size_t, const std::nothrow_t&) noexcept { return kmalloc_aligned<alignment>(sizeof(type)); } \
+ void operator delete(void* ptr) noexcept { kfree_aligned(ptr); } \
+ \
private:
+// The C++ standard specifies that the nothrow allocation tag should live in the std namespace.
+// Otherwise, `new (std::nothrow)` calls wouldn't get resolved.
+namespace std {
+struct nothrow_t {
+ explicit nothrow_t() = default;
+};
+
+extern const nothrow_t nothrow;
+};
+
void kmalloc_init();
[[gnu::malloc, gnu::returns_nonnull, gnu::alloc_size(1)]] void* kmalloc_impl(size_t);
[[gnu::malloc, gnu::returns_nonnull, gnu::alloc_size(1)]] void* kmalloc_eternal(size_t);
@@ -41,10 +57,12 @@ extern bool g_dump_kmalloc_stacks;
inline void* operator new(size_t, void* p) { return p; }
inline void* operator new[](size_t, void* p) { return p; }
-[[nodiscard]] void* operator new(size_t size) noexcept;
+[[nodiscard]] void* operator new(size_t size);
+[[nodiscard]] void* operator new(size_t size, const std::nothrow_t&) noexcept;
void operator delete(void* ptr) noexcept;
void operator delete(void* ptr, size_t) noexcept;
-[[nodiscard]] void* operator new[](size_t size) noexcept;
+[[nodiscard]] void* operator new[](size_t size);
+[[nodiscard]] void* operator new[](size_t size, const std::nothrow_t&) noexcept;
void operator delete[](void* ptrs) noexcept;
void operator delete[](void* ptr, size_t) noexcept;