diff --git a/doc/api/errors.md b/doc/api/errors.md
index 6a286772dee..a90080e79bc 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -613,6 +613,19 @@ An attempt was made to register something that is not a function as an
The type of an asynchronous resource was invalid. Note that users are also able
to define their own types if using the public embedder API.
+
+### ERR_BUFFER_CONTEXT_NOT_AVAILABLE
+
+An attempt was made to create a Node.js `Buffer` instance from addon or embedder
+code, while in a JS engine Context that is not associated with a Node.js
+instance. The data passed to the `Buffer` method will have been released
+by the time the method returns.
+
+When encountering this error, a possible alternative to creating a `Buffer`
+instance is to create a normal `Uint8Array`, which only differs in the
+prototype of the resulting object. `Uint8Array`s are generally accepted in all
+Node.js core APIs where `Buffer`s are; they are available in all Contexts.
+
### ERR_BUFFER_OUT_OF_BOUNDS
diff --git a/src/env-inl.h b/src/env-inl.h
index 4e0a95ed424..bff754f5a0e 100644
--- a/src/env-inl.h
+++ b/src/env-inl.h
@@ -47,8 +47,16 @@ inline uv_loop_t* IsolateData::event_loop() const {
return event_loop_;
}
-inline uint32_t* IsolateData::zero_fill_field() const {
- return zero_fill_field_;
+inline bool IsolateData::uses_node_allocator() const {
+ return uses_node_allocator_;
+}
+
+inline v8::ArrayBuffer::Allocator* IsolateData::allocator() const {
+ return allocator_;
+}
+
+inline ArrayBufferAllocator* IsolateData::node_allocator() const {
+ return node_allocator_;
}
inline MultiIsolatePlatform* IsolateData::platform() const {
@@ -661,6 +669,104 @@ inline IsolateData* Environment::isolate_data() const {
return isolate_data_;
}
+inline char* Environment::AllocateUnchecked(size_t size) {
+ return static_cast(
+ isolate_data()->allocator()->AllocateUninitialized(size));
+}
+
+inline char* Environment::Allocate(size_t size) {
+ char* ret = AllocateUnchecked(size);
+ CHECK_NE(ret, nullptr);
+ return ret;
+}
+
+inline void Environment::Free(char* data, size_t size) {
+ if (data != nullptr)
+ isolate_data()->allocator()->Free(data, size);
+}
+
+inline AllocatedBuffer Environment::AllocateManaged(size_t size, bool checked) {
+ char* data = checked ? Allocate(size) : AllocateUnchecked(size);
+ if (data == nullptr) size = 0;
+ return AllocatedBuffer(this, uv_buf_init(data, size));
+}
+
+inline AllocatedBuffer::AllocatedBuffer(Environment* env, uv_buf_t buf)
+ : env_(env), buffer_(buf) {}
+
+inline void AllocatedBuffer::Resize(size_t len) {
+ char* new_data = env_->Reallocate(buffer_.base, buffer_.len, len);
+ CHECK_IMPLIES(len > 0, new_data != nullptr);
+ buffer_ = uv_buf_init(new_data, len);
+}
+
+inline uv_buf_t AllocatedBuffer::release() {
+ uv_buf_t ret = buffer_;
+ buffer_ = uv_buf_init(nullptr, 0);
+ return ret;
+}
+
+inline char* AllocatedBuffer::data() {
+ return buffer_.base;
+}
+
+inline const char* AllocatedBuffer::data() const {
+ return buffer_.base;
+}
+
+inline size_t AllocatedBuffer::size() const {
+ return buffer_.len;
+}
+
+inline AllocatedBuffer::AllocatedBuffer(Environment* env)
+ : env_(env), buffer_(uv_buf_init(nullptr, 0)) {}
+
+inline AllocatedBuffer::AllocatedBuffer(AllocatedBuffer&& other)
+ : AllocatedBuffer() {
+ *this = std::move(other);
+}
+
+inline AllocatedBuffer& AllocatedBuffer::operator=(AllocatedBuffer&& other) {
+ clear();
+ env_ = other.env_;
+ buffer_ = other.release();
+ return *this;
+}
+
+inline AllocatedBuffer::~AllocatedBuffer() {
+ clear();
+}
+
+inline void AllocatedBuffer::clear() {
+ uv_buf_t buf = release();
+ env_->Free(buf.base, buf.len);
+}
+
+// It's a bit awkward to define this Buffer::New() overload here, but it
+// avoids a circular dependency with node_internals.h.
+namespace Buffer {
+v8::MaybeLocal New(Environment* env,
+ char* data,
+ size_t length,
+ bool uses_malloc);
+}
+
+inline v8::MaybeLocal AllocatedBuffer::ToBuffer() {
+ CHECK_NOT_NULL(env_);
+ v8::MaybeLocal obj = Buffer::New(env_, data(), size(), false);
+ if (!obj.IsEmpty()) release();
+ return obj;
+}
+
+inline v8::Local AllocatedBuffer::ToArrayBuffer() {
+ CHECK_NOT_NULL(env_);
+ uv_buf_t buf = release();
+ return v8::ArrayBuffer::New(env_->isolate(),
+ buf.base,
+ buf.len,
+ v8::ArrayBufferCreationMode::kInternalized);
+}
+
inline void Environment::ThrowError(const char* errmsg) {
ThrowError(v8::Exception::Error, errmsg);
}
diff --git a/src/env.cc b/src/env.cc
index d76e691a53f..47cd43e8394 100644
--- a/src/env.cc
+++ b/src/env.cc
@@ -13,6 +13,7 @@
namespace node {
+using v8::ArrayBuffer;
using v8::Context;
using v8::FunctionTemplate;
using v8::HandleScope;
@@ -36,11 +37,14 @@ void* Environment::kNodeContextTagPtr = const_cast(
IsolateData::IsolateData(Isolate* isolate,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
- uint32_t* zero_fill_field) :
- isolate_(isolate),
- event_loop_(event_loop),
- zero_fill_field_(zero_fill_field),
- platform_(platform) {
+ ArrayBufferAllocator* node_allocator)
+ : isolate_(isolate),
+ event_loop_(event_loop),
+ allocator_(isolate->GetArrayBufferAllocator()),
+ node_allocator_(node_allocator),
+ uses_node_allocator_(allocator_ == node_allocator_),
+ platform_(platform) {
+ CHECK_NOT_NULL(allocator_);
if (platform_ != nullptr)
platform_->RegisterIsolate(isolate_, event_loop);
@@ -688,6 +692,23 @@ void Environment::BuildEmbedderGraph(v8::Isolate* isolate,
});
}
+char* Environment::Reallocate(char* data, size_t old_size, size_t size) {
+ // If we know that the allocator is our ArrayBufferAllocator, we can let
+ // if reallocate directly.
+ if (isolate_data()->uses_node_allocator()) {
+ return static_cast(
+ isolate_data()->node_allocator()->Reallocate(data, old_size, size));
+ }
+ // Generic allocators do not provide a reallocation method; we need to
+ // allocate a new chunk of memory and copy the data over.
+ char* new_data = AllocateUnchecked(size);
+ if (new_data == nullptr) return nullptr;
+ memcpy(new_data, data, std::min(size, old_size));
+ if (size > old_size)
+ memset(new_data + old_size, 0, size - old_size);
+ Free(data, old_size);
+ return new_data;
+}
// Not really any better place than env.cc at this moment.
void BaseObject::DeleteMe(void* data) {
diff --git a/src/env.h b/src/env.h
index 4b678cee289..e0ef958dbcd 100644
--- a/src/env.h
+++ b/src/env.h
@@ -362,15 +362,19 @@ class Environment;
class IsolateData {
public:
- IsolateData(v8::Isolate* isolate, uv_loop_t* event_loop,
+ IsolateData(v8::Isolate* isolate,
+ uv_loop_t* event_loop,
MultiIsolatePlatform* platform = nullptr,
- uint32_t* zero_fill_field = nullptr);
+ ArrayBufferAllocator* node_allocator = nullptr);
~IsolateData();
inline uv_loop_t* event_loop() const;
- inline uint32_t* zero_fill_field() const;
inline MultiIsolatePlatform* platform() const;
inline std::shared_ptr options();
+ inline bool uses_node_allocator() const;
+ inline v8::ArrayBuffer::Allocator* allocator() const;
+ inline ArrayBufferAllocator* node_allocator() const;
+
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)
#define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName)
#define VS(PropertyName, StringValue) V(v8::String, PropertyName)
@@ -401,7 +405,9 @@ class IsolateData {
v8::Isolate* const isolate_;
uv_loop_t* const event_loop_;
- uint32_t* const zero_fill_field_;
+ v8::ArrayBuffer::Allocator* const allocator_;
+ ArrayBufferAllocator* const node_allocator_;
+ const bool uses_node_allocator_;
MultiIsolatePlatform* platform_;
std::shared_ptr options_;
@@ -428,6 +434,38 @@ enum class DebugCategory {
CATEGORY_COUNT
};
+// A unique-pointer-ish object that is compatible with the JS engine's
+// ArrayBuffer::Allocator.
+struct AllocatedBuffer {
+ public:
+ explicit inline AllocatedBuffer(Environment* env = nullptr);
+ inline AllocatedBuffer(Environment* env, uv_buf_t buf);
+ inline ~AllocatedBuffer();
+ inline void Resize(size_t len);
+
+ inline uv_buf_t release();
+ inline char* data();
+ inline const char* data() const;
+ inline size_t size() const;
+ inline void clear();
+
+ inline v8::MaybeLocal ToBuffer();
+ inline v8::Local ToArrayBuffer();
+
+ inline AllocatedBuffer(AllocatedBuffer&& other);
+ inline AllocatedBuffer& operator=(AllocatedBuffer&& other);
+ AllocatedBuffer(const AllocatedBuffer& other) = delete;
+ AllocatedBuffer& operator=(const AllocatedBuffer& other) = delete;
+
+ private:
+ Environment* env_;
+ // We do not pass this to libuv directly, but uv_buf_t is a convenient way
+ // to represent a chunk of memory, and plays nicely with other parts of core.
+ uv_buf_t buffer_;
+
+ friend class Environment;
+};
+
class Environment {
public:
class AsyncHooks {
@@ -642,6 +680,15 @@ class Environment {
inline IsolateData* isolate_data() const;
+ // Utilites that allocate memory using the Isolate's ArrayBuffer::Allocator.
+ // In particular, using AllocateManaged() will provide a RAII-style object
+ // with easy conversion to `Buffer` and `ArrayBuffer` objects.
+ inline AllocatedBuffer AllocateManaged(size_t size, bool checked = true);
+ inline char* Allocate(size_t size);
+ inline char* AllocateUnchecked(size_t size);
+ char* Reallocate(char* data, size_t old_size, size_t size);
+ inline void Free(char* data, size_t size);
+
inline bool printed_error() const;
inline void set_printed_error(bool value);
diff --git a/src/node.cc b/src/node.cc
index aa51c0b46a1..21a9c9beefa 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -629,6 +629,77 @@ 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;
+}
+
namespace {
bool ShouldAbortOnUncaughtException(Isolate* isolate) {
@@ -2805,7 +2876,10 @@ int EmitExit(Environment* env) {
ArrayBufferAllocator* CreateArrayBufferAllocator() {
- return new ArrayBufferAllocator();
+ if (per_process_opts->debug_arraybuffer_allocations)
+ return new DebuggingArrayBufferAllocator();
+ else
+ return new ArrayBufferAllocator();
}
@@ -2832,7 +2906,7 @@ IsolateData* CreateIsolateData(
uv_loop_t* loop,
MultiIsolatePlatform* platform,
ArrayBufferAllocator* allocator) {
- return new IsolateData(isolate, loop, platform, allocator->zero_fill_field());
+ return new IsolateData(isolate, loop, platform, allocator);
}
diff --git a/src/node_buffer.cc b/src/node_buffer.cc
index 475770def92..94f978f89af 100644
--- a/src/node_buffer.cc
+++ b/src/node_buffer.cc
@@ -55,20 +55,6 @@
size_t length = end - start;
namespace node {
-
-// if true, all Buffer and SlowBuffer instances will automatically zero-fill
-bool zero_fill_all_buffers = false;
-
-namespace {
-
-inline void* BufferMalloc(v8::Isolate* isolate, size_t length) {
- auto* allocator = isolate->GetArrayBufferAllocator();
- return zero_fill_all_buffers ? allocator->Allocate(length) :
- allocator->AllocateUninitialized(length);
-}
-
-} // namespace
-
namespace Buffer {
using v8::ArrayBuffer;
@@ -246,7 +232,7 @@ MaybeLocal