From 238fa5704b57ecafd95e8a022695a926f1c0d7e0 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 18 Feb 2019 17:30:10 +0100 Subject: [PATCH] src: add debugging array allocator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a subclass of `ArrayBufferAllocator` that performs additional debug checking, which in particular verifies that: - All `ArrayBuffer` backing stores have been allocated with this allocator, or have been explicitly marked as coming from a compatible source. - All memory allocated by the allocator has been freed once it is destroyed. PR-URL: https://github.com/nodejs/node/pull/26207 Reviewed-By: James M Snell Reviewed-By: Joyee Cheung Backport-PR-URL: https://github.com/nodejs/node/pull/26302 Reviewed-By: Michaƫl Zasso --- src/api/environment.cc | 76 +++++++++++++++++++++++++++++++++++++++++- src/node_internals.h | 23 +++++++++++++ src/node_options.cc | 4 +++ src/node_options.h | 1 + 4 files changed, 103 insertions(+), 1 deletion(-) diff --git a/src/api/environment.cc b/src/api/environment.cc index 9c509e174b8177..786ca5021b5ec7 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -75,8 +75,82 @@ void* ArrayBufferAllocator::Allocate(size_t size) { return UncheckedMalloc(size); } +DebuggingArrayBufferAllocator::~DebuggingArrayBufferAllocator() { + CHECK(allocations_.empty()); +} + +void* DebuggingArrayBufferAllocator::Allocate(size_t size) { + Mutex::ScopedLock lock(mutex_); + void* data = ArrayBufferAllocator::Allocate(size); + RegisterPointerInternal(data, size); + return data; +} + +void* DebuggingArrayBufferAllocator::AllocateUninitialized(size_t size) { + Mutex::ScopedLock lock(mutex_); + void* data = ArrayBufferAllocator::AllocateUninitialized(size); + RegisterPointerInternal(data, size); + return data; +} + +void DebuggingArrayBufferAllocator::Free(void* data, size_t size) { + Mutex::ScopedLock lock(mutex_); + UnregisterPointerInternal(data, size); + ArrayBufferAllocator::Free(data, size); +} + +void* DebuggingArrayBufferAllocator::Reallocate(void* data, + size_t old_size, + size_t size) { + Mutex::ScopedLock lock(mutex_); + void* ret = ArrayBufferAllocator::Reallocate(data, old_size, size); + if (ret == nullptr) { + if (size == 0) // i.e. equivalent to free(). + UnregisterPointerInternal(data, old_size); + return nullptr; + } + + if (data != nullptr) { + auto it = allocations_.find(data); + CHECK_NE(it, allocations_.end()); + allocations_.erase(it); + } + + RegisterPointerInternal(ret, size); + return ret; +} + +void DebuggingArrayBufferAllocator::RegisterPointer(void* data, size_t size) { + Mutex::ScopedLock lock(mutex_); + RegisterPointerInternal(data, size); +} + +void DebuggingArrayBufferAllocator::UnregisterPointer(void* data, size_t size) { + Mutex::ScopedLock lock(mutex_); + UnregisterPointerInternal(data, size); +} + +void DebuggingArrayBufferAllocator::UnregisterPointerInternal(void* data, + size_t size) { + if (data == nullptr) return; + auto it = allocations_.find(data); + CHECK_NE(it, allocations_.end()); + CHECK_EQ(it->second, size); + allocations_.erase(it); +} + +void DebuggingArrayBufferAllocator::RegisterPointerInternal(void* data, + size_t size) { + if (data == nullptr) return; + CHECK_EQ(allocations_.count(data), 0); + allocations_[data] = size; +} + ArrayBufferAllocator* CreateArrayBufferAllocator() { - return new ArrayBufferAllocator(); + if (per_process::cli_options->debug_arraybuffer_allocations) + return new DebuggingArrayBufferAllocator(); + else + return new ArrayBufferAllocator(); } void FreeArrayBufferAllocator(ArrayBufferAllocator* allocator) { diff --git a/src/node_internals.h b/src/node_internals.h index 6f09708371b97c..9ac2b0a331c531 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -109,11 +109,34 @@ class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator { void* AllocateUninitialized(size_t size) override { return node::UncheckedMalloc(size); } void Free(void* data, size_t) override { free(data); } + virtual void* Reallocate(void* data, size_t old_size, size_t size) { + return static_cast( + UncheckedRealloc(static_cast(data), size)); + } + virtual void RegisterPointer(void* data, size_t size) {} + virtual void UnregisterPointer(void* data, size_t size) {} private: uint32_t zero_fill_field_ = 1; // Boolean but exposed as uint32 to JS land. }; +class DebuggingArrayBufferAllocator final : public ArrayBufferAllocator { + public: + ~DebuggingArrayBufferAllocator() override; + void* Allocate(size_t size) override; + void* AllocateUninitialized(size_t size) override; + void Free(void* data, size_t size) override; + void* Reallocate(void* data, size_t old_size, size_t size) override; + void RegisterPointer(void* data, size_t size) override; + void UnregisterPointer(void* data, size_t size) override; + + private: + void RegisterPointerInternal(void* data, size_t size); + void UnregisterPointerInternal(void* data, size_t size); + Mutex mutex_; + std::unordered_map allocations_; +}; + namespace Buffer { v8::MaybeLocal Copy(Environment* env, const char* data, size_t len); v8::MaybeLocal New(Environment* env, size_t size); diff --git a/src/node_options.cc b/src/node_options.cc index f7816d34ac7c4d..b0270986d4cba8 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -369,6 +369,10 @@ PerProcessOptionsParser::PerProcessOptionsParser() { "SlowBuffer instances", &PerProcessOptions::zero_fill_all_buffers, kAllowedInEnvironment); + AddOption("--debug-arraybuffer-allocations", + "", /* undocumented, only for debugging */ + &PerProcessOptions::debug_arraybuffer_allocations, + kAllowedInEnvironment); AddOption("--security-reverts", "", &PerProcessOptions::security_reverts); AddOption("--completion-bash", diff --git a/src/node_options.h b/src/node_options.h index d09ba53dafa15c..e92fd3c983191f 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -168,6 +168,7 @@ class PerProcessOptions : public Options { uint64_t max_http_header_size = 8 * 1024; int64_t v8_thread_pool_size = 4; bool zero_fill_all_buffers = false; + bool debug_arraybuffer_allocations = false; std::vector security_reverts; bool print_bash_completion = false;