Skip to content

Commit

Permalink
worker: make MessagePort constructor non-callable
Browse files Browse the repository at this point in the history
Refactor the C++ code for creating `MessagePort`s to skip calling the
constructor and instead directly instantiating the `InstanceTemplate`,
and always throw an error from the `MessagePort` constructor.

This aligns behaviour with the web, and creating single `MessagePort`s
does not make sense anyway.
  • Loading branch information
addaleax committed Jun 10, 2019
1 parent bcf1135 commit a28b289
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 30 deletions.
8 changes: 8 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,14 @@ non-writable `stdout` or `stderr` stream.

A constructor for a class was called without `new`.

<a id="ERR_CONSTRUCT_CALL_INVALID"></a>
### ERR_CONSTRUCT_CALL_INVALID
<!--
added: REPLACEME
-->

A class constructor was called that is not callable.

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

Expand Down
4 changes: 3 additions & 1 deletion src/node_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ void FatalException(v8::Isolate* isolate,
V(ERR_BUFFER_CONTEXT_NOT_AVAILABLE, Error) \
V(ERR_BUFFER_OUT_OF_BOUNDS, RangeError) \
V(ERR_BUFFER_TOO_LARGE, Error) \
V(ERR_CONSTRUCT_CALL_REQUIRED, Error) \
V(ERR_CONSTRUCT_CALL_REQUIRED, TypeError) \
V(ERR_CONSTRUCT_CALL_INVALID, TypeError) \
V(ERR_INVALID_ARG_VALUE, TypeError) \
V(ERR_INVALID_ARG_TYPE, TypeError) \
V(ERR_INVALID_MODULE_SPECIFIER, TypeError) \
Expand Down Expand Up @@ -99,6 +100,7 @@ void FatalException(v8::Isolate* isolate,
#define PREDEFINED_ERROR_MESSAGES(V) \
V(ERR_BUFFER_CONTEXT_NOT_AVAILABLE, \
"Buffer is not available for the current Context") \
V(ERR_CONSTRUCT_CALL_INVALID, "Constructor cannot be called") \
V(ERR_CONSTRUCT_CALL_REQUIRED, "Cannot call constructor without `new`") \
V(ERR_INVALID_TRANSFER_OBJECT, "Found invalid object in transferList") \
V(ERR_MEMORY_ALLOCATION_FAILED, "Failed to allocate memory") \
Expand Down
32 changes: 12 additions & 20 deletions src/node_messaging.cc
Original file line number Diff line number Diff line change
Expand Up @@ -529,33 +529,26 @@ void MessagePort::Close(v8::Local<v8::Value> close_callback) {
}

void MessagePort::New(const FunctionCallbackInfo<Value>& args) {
// This constructor just throws an error. Unfortunately, we can’t use V8’s
// ConstructorBehavior::kThrow, as that also removes the prototype from the
// class (i.e. makes it behave like an arrow function).
Environment* env = Environment::GetCurrent(args);
if (!args.IsConstructCall()) {
THROW_ERR_CONSTRUCT_CALL_REQUIRED(env);
return;
}

Local<Context> context = args.This()->CreationContext();
Context::Scope context_scope(context);

new MessagePort(env, context, args.This());
THROW_ERR_CONSTRUCT_CALL_INVALID(env);
}

MessagePort* MessagePort::New(
Environment* env,
Local<Context> context,
std::unique_ptr<MessagePortData> data) {
Context::Scope context_scope(context);
Local<Function> ctor;
if (!GetMessagePortConstructor(env, context).ToLocal(&ctor))
return nullptr;
Local<FunctionTemplate> ctor_templ = GetMessagePortConstructorTemplate(env);

// Construct a new instance, then assign the listener instance and possibly
// the MessagePortData to it.
Local<Object> instance;
if (!ctor->NewInstance(context).ToLocal(&instance))
if (!ctor_templ->InstanceTemplate()->NewInstance(context).ToLocal(&instance))
return nullptr;
MessagePort* port = Unwrap<MessagePort>(instance);
MessagePort* port = new MessagePort(env, context, instance);
CHECK_NOT_NULL(port);
if (data) {
port->Detach();
Expand Down Expand Up @@ -830,13 +823,12 @@ void MessagePort::Entangle(MessagePort* a, MessagePortData* b) {
MessagePortData::Entangle(a->data_.get(), b);
}

MaybeLocal<Function> GetMessagePortConstructor(
Environment* env, Local<Context> context) {
Local<FunctionTemplate> GetMessagePortConstructorTemplate(Environment* env) {
// Factor generating the MessagePort JS constructor into its own piece
// of code, because it is needed early on in the child environment setup.
Local<FunctionTemplate> templ = env->message_port_constructor_template();
if (!templ.IsEmpty())
return templ->GetFunction(context);
return templ;

Isolate* isolate = env->isolate();

Expand All @@ -859,7 +851,7 @@ MaybeLocal<Function> GetMessagePortConstructor(
env->set_message_event_object_template(e);
}

return GetMessagePortConstructor(env, context);
return GetMessagePortConstructorTemplate(env);
}

namespace {
Expand Down Expand Up @@ -902,8 +894,8 @@ static void InitMessaging(Local<Object> target,

target->Set(context,
env->message_port_constructor_string(),
GetMessagePortConstructor(env, context).ToLocalChecked())
.Check();
GetMessagePortConstructorTemplate(env)
->GetFunction(context).ToLocalChecked()).Check();

// These are not methods on the MessagePort prototype, because
// the browser equivalents do not provide them.
Expand Down
4 changes: 2 additions & 2 deletions src/node_messaging.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ class MessagePort : public HandleWrap {
friend class MessagePortData;
};

v8::MaybeLocal<v8::Function> GetMessagePortConstructor(
Environment* env, v8::Local<v8::Context> context);
v8::Local<v8::FunctionTemplate> GetMessagePortConstructorTemplate(
Environment* env);

} // namespace worker
} // namespace node
Expand Down
27 changes: 27 additions & 0 deletions test/parallel/test-worker-message-port-constructor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';
require('../common');
const assert = require('assert');

const { MessageChannel, MessagePort } = require('worker_threads');

// Make sure that `MessagePort` is the constructor for MessagePort instances,
// but not callable.
const { port1 } = new MessageChannel();

assert(port1 instanceof MessagePort);
assert.strictEqual(port1.constructor, MessagePort);

assert.throws(() => MessagePort(), {
constructor: TypeError,
code: 'ERR_CONSTRUCT_CALL_INVALID'
});

assert.throws(() => new MessagePort(), {
constructor: TypeError,
code: 'ERR_CONSTRUCT_CALL_INVALID'
});

assert.throws(() => MessageChannel(), {
constructor: TypeError,
code: 'ERR_CONSTRUCT_CALL_REQUIRED'
});
7 changes: 0 additions & 7 deletions test/parallel/test-worker-message-port-move.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,6 @@ vm.runInContext('(' + function() {
}
assert(threw);
}

{
const newDummyPort = new (port.constructor)();
assert(!(newDummyPort instanceof MessagePort));
assert(newDummyPort.close instanceof Function);
newDummyPort.close();
}
} + ')()', context);

port2.on('message', common.mustCall((msg) => {
Expand Down

0 comments on commit a28b289

Please sign in to comment.