Skip to content

Commit

Permalink
src: add debugging array allocator
Browse files Browse the repository at this point in the history
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: nodejs#26207
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
  • Loading branch information
addaleax committed Feb 28, 2019
1 parent 29ac9ef commit 86fdd46
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 1 deletion.
76 changes: 75 additions & 1 deletion src/api/environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
23 changes: 23 additions & 0 deletions src/node_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<void*>(
UncheckedRealloc<char>(static_cast<char*>(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<void*, size_t> allocations_;
};

namespace Buffer {
v8::MaybeLocal<v8::Object> Copy(Environment* env, const char* data, size_t len);
v8::MaybeLocal<v8::Object> New(Environment* env, size_t size);
Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> security_reverts;
bool print_bash_completion = false;
Expand Down

0 comments on commit 86fdd46

Please sign in to comment.