Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

buffer: throw exception when creating from non-Node.js Context #23938

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<a id="ERR_BUFFER_CONTEXT_NOT_AVAILABLE"></a>
### ERR_BUFFER_CONTEXT_NOT_AVAILABLE

An attempt was made to create a Node.js `Buffer` instance from addon or embedder
code, while in an JS engine Context that is not associated with a Node.js
vsemozhetbyt marked this conversation as resolved.
Show resolved Hide resolved
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.

<a id="ERR_BUFFER_OUT_OF_BOUNDS"></a>
### ERR_BUFFER_OUT_OF_BOUNDS

Expand Down
22 changes: 18 additions & 4 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,10 @@ MaybeLocal<Object> New(Isolate* isolate, size_t length) {
EscapableHandleScope handle_scope(isolate);
Local<Object> obj;
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here.
if (env == nullptr) {
THROW_ERR_BUFFER_CONTEXT_NOT_AVAILABLE(isolate);
return MaybeLocal<Object>();
}
if (Buffer::New(env, length).ToLocal(&obj))
return handle_scope.Escape(obj);
return Local<Object>();
Expand Down Expand Up @@ -314,7 +317,10 @@ MaybeLocal<Object> New(Environment* env, size_t length) {
MaybeLocal<Object> Copy(Isolate* isolate, const char* data, size_t length) {
EscapableHandleScope handle_scope(isolate);
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here.
if (env == nullptr) {
THROW_ERR_BUFFER_CONTEXT_NOT_AVAILABLE(isolate);
return MaybeLocal<Object>();
}
Local<Object> obj;
if (Buffer::Copy(env, data, length).ToLocal(&obj))
return handle_scope.Escape(obj);
Expand Down Expand Up @@ -364,7 +370,11 @@ MaybeLocal<Object> New(Isolate* isolate,
void* hint) {
EscapableHandleScope handle_scope(isolate);
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here.
if (env == nullptr) {
callback(data, hint);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Belated review: this callback() call and the free() call on line 417 look a bit incongruent because they're not executed when the Buffer::New() calls below them fail.

Performing the action only sometimes is confusing, IMO. A caller that diligently checks the return value and frees the memory when it's empty will now (sometimes) double-free.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bnoordhuis I think it was more or less the idea to be more consistent here and always take ownership, because in other cases we already did free the data when failing (e.g. in the New(Isolate*, Local<String>, encoding) variant)…

THROW_ERR_BUFFER_CONTEXT_NOT_AVAILABLE(isolate);
return MaybeLocal<Object>();
}
Local<Object> obj;
if (Buffer::New(env, data, length, callback, hint).ToLocal(&obj))
return handle_scope.Escape(obj);
Expand Down Expand Up @@ -403,7 +413,11 @@ MaybeLocal<Object> New(Environment* env,
MaybeLocal<Object> New(Isolate* isolate, char* data, size_t length) {
EscapableHandleScope handle_scope(isolate);
Environment* env = Environment::GetCurrent(isolate);
CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here.
if (env == nullptr) {
free(data);
THROW_ERR_BUFFER_CONTEXT_NOT_AVAILABLE(isolate);
return MaybeLocal<Object>();
}
Local<Object> obj;
if (Buffer::New(env, data, length).ToLocal(&obj))
return handle_scope.Escape(obj);
Expand Down
3 changes: 3 additions & 0 deletions src/node_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace node {
// a `Local<Value>` containing the TypeError with proper code and message

#define ERRORS_WITH_CODE(V) \
V(ERR_BUFFER_CONTEXT_NOT_AVAILABLE, Error) \
V(ERR_BUFFER_OUT_OF_BOUNDS, RangeError) \
V(ERR_BUFFER_TOO_LARGE, Error) \
V(ERR_CANNOT_TRANSFER_OBJECT, TypeError) \
Expand Down Expand Up @@ -64,6 +65,8 @@ namespace node {
// Errors with predefined static messages

#define PREDEFINED_ERROR_MESSAGES(V) \
V(ERR_BUFFER_CONTEXT_NOT_AVAILABLE, \
"Buffer is not available for the current Context") \
V(ERR_CANNOT_TRANSFER_OBJECT, "Cannot transfer object of unsupported type")\
V(ERR_CLOSED_MESSAGE_PORT, "Cannot send data on closed MessagePort") \
V(ERR_CONSTRUCT_CALL_REQUIRED, "Cannot call constructor without `new`") \
Expand Down
21 changes: 14 additions & 7 deletions test/addons/non-node-context/binding.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <node.h>
#include <node_buffer.h>
#include <assert.h>

namespace {
Expand All @@ -15,6 +16,17 @@ using v8::Script;
using v8::String;
using v8::Value;

inline void MakeBufferInNewContext(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = Context::New(isolate);
Context::Scope context_scope(context);

// This should throw an exception, rather than actually do anything.
MaybeLocal<Object> buf = node::Buffer::Copy(isolate, "foo", 3);
assert(buf.IsEmpty());
}

inline void RunInNewContext(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = args.GetIsolate();
Expand All @@ -41,13 +53,8 @@ inline void RunInNewContext(
inline void Initialize(Local<Object> exports,
Local<Value> module,
Local<Context> context) {
Isolate* isolate = context->GetIsolate();
Local<String> key = String::NewFromUtf8(
isolate, "runInNewContext", NewStringType::kNormal).ToLocalChecked();
Local<Function> value = FunctionTemplate::New(isolate, RunInNewContext)
->GetFunction(context)
.ToLocalChecked();
assert(exports->Set(context, key, value).IsJust());
NODE_SET_METHOD(exports, "runInNewContext", RunInNewContext);
NODE_SET_METHOD(exports, "makeBufferInNewContext", MakeBufferInNewContext);
}

} // anonymous namespace
Expand Down
22 changes: 22 additions & 0 deletions test/addons/non-node-context/test-make-buffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

const common = require('../../common');
const assert = require('assert');
const {
makeBufferInNewContext
} = require(`./build/${common.buildType}/binding`);

// Because the `Buffer` function and its protoype property only (currently)
// exist in a Node.js instance’s main context, trying to create buffers from
// another context throws an exception.

try {
makeBufferInNewContext();
} catch (exception) {
assert.strictEqual(exception.constructor.name, 'Error');
assert(!(exception.constructor instanceof Error));

assert.strictEqual(exception.code, 'ERR_BUFFER_CONTEXT_NOT_AVAILABLE');
assert.strictEqual(exception.message,
'Buffer is not available for the current Context');
}