Skip to content

Commit 22c826f

Browse files
committed
src: do proper error checking in AsyncWrap::MakeCallback
At least one method on a native object is added as a getter, namely `MessagePort.prototype.onmessage`. When a MessagePort attempts to call this method from C++ in response to receiving data, it will first invoke that getter and then call the function. Since `worker.terminate()` interrupts execution, this means that the getter may fail (without being faulty code on its own). This means that at least one test exercising these methods in combination has been flaky and could have crashed, because we did not actually check that the getter returns a value so far, resulting in dereferencing an empty `Local`. The proper fix for this is to use the non-deprecated overload of `Get()` and check the result like we should be doing. Also, as a (related) fix, don’t crash if the method is not a function but rather something else, like a getter could provide. Example test failure: https://ci.nodejs.org/job/node-test-commit-linux-containered/4976/nodes=ubuntu1604_sharedlibs_zlib_x64/console 17:56:56 not ok 1955 parallel/test-worker-dns-terminate 17:56:56 --- 17:56:56 duration_ms: 1.237 17:56:56 severity: crashed 17:56:56 exitcode: -11 17:56:56 stack: |- PR-URL: #21189 Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 61e9e3c commit 22c826f

File tree

6 files changed

+75
-16
lines changed

6 files changed

+75
-16
lines changed

src/async_wrap-inl.h

+8-12
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,14 @@ inline v8::MaybeLocal<v8::Value> AsyncWrap::MakeCallback(
8181
const v8::Local<v8::Name> symbol,
8282
int argc,
8383
v8::Local<v8::Value>* argv) {
84-
v8::Local<v8::Value> cb_v = object()->Get(symbol);
85-
CHECK(cb_v->IsFunction());
86-
return MakeCallback(cb_v.As<v8::Function>(), argc, argv);
87-
}
88-
89-
90-
inline v8::MaybeLocal<v8::Value> AsyncWrap::MakeCallback(
91-
uint32_t index,
92-
int argc,
93-
v8::Local<v8::Value>* argv) {
94-
v8::Local<v8::Value> cb_v = object()->Get(index);
95-
CHECK(cb_v->IsFunction());
84+
v8::Local<v8::Value> cb_v;
85+
if (!object()->Get(env()->context(), symbol).ToLocal(&cb_v))
86+
return v8::MaybeLocal<v8::Value>();
87+
if (!cb_v->IsFunction()) {
88+
// TODO(addaleax): We should throw an error here to fulfill the
89+
// `MaybeLocal<>` API contract.
90+
return v8::MaybeLocal<v8::Value>();
91+
}
9692
return MakeCallback(cb_v.As<v8::Function>(), argc, argv);
9793
}
9894

src/async_wrap.h

-3
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,6 @@ class AsyncWrap : public BaseObject {
173173
const v8::Local<v8::Name> symbol,
174174
int argc,
175175
v8::Local<v8::Value>* argv);
176-
inline v8::MaybeLocal<v8::Value> MakeCallback(uint32_t index,
177-
int argc,
178-
v8::Local<v8::Value>* argv);
179176

180177
virtual size_t self_size() const = 0;
181178
virtual std::string diagnostic_name() const;

src/handle_wrap.h

+4
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ class HandleWrap : public AsyncWrap {
8383
void MarkAsInitialized();
8484
void MarkAsUninitialized();
8585

86+
inline bool IsHandleClosing() const {
87+
return state_ == kClosing || state_ == kClosed;
88+
}
89+
8690
private:
8791
friend class Environment;
8892
friend void GetActiveHandles(const v8::FunctionCallbackInfo<v8::Value>&);

src/node_messaging.cc

+12-1
Original file line numberDiff line numberDiff line change
@@ -391,9 +391,21 @@ uv_async_t* MessagePort::async() {
391391
}
392392

393393
void MessagePort::TriggerAsync() {
394+
if (IsHandleClosing()) return;
394395
CHECK_EQ(uv_async_send(async()), 0);
395396
}
396397

398+
void MessagePort::Close(v8::Local<v8::Value> close_callback) {
399+
if (data_) {
400+
// Wrap this call with accessing the mutex, so that TriggerAsync()
401+
// can check IsHandleClosing() without race conditions.
402+
Mutex::ScopedLock sibling_lock(data_->mutex_);
403+
HandleWrap::Close(close_callback);
404+
} else {
405+
HandleWrap::Close(close_callback);
406+
}
407+
}
408+
397409
void MessagePort::New(const FunctionCallbackInfo<Value>& args) {
398410
Environment* env = Environment::GetCurrent(args);
399411
if (!args.IsConstructCall()) {
@@ -476,7 +488,6 @@ void MessagePort::OnMessage() {
476488
};
477489

478490
if (args[0].IsEmpty() ||
479-
!object()->Has(context, env()->onmessage_string()).FromMaybe(false) ||
480491
MakeCallback(env()->onmessage_string(), 1, args).IsEmpty()) {
481492
// Re-schedule OnMessage() execution in case of failure.
482493
if (data_)

src/node_messaging.h

+2
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ class MessagePort : public HandleWrap {
154154
std::unique_ptr<MessagePortData> Detach();
155155

156156
bool IsSiblingClosed() const;
157+
void Close(
158+
v8::Local<v8::Value> close_callback = v8::Local<v8::Value>()) override;
157159

158160
size_t self_size() const override;
159161

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Flags: --experimental-worker
2+
'use strict';
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
const { MessageChannel } = require('worker_threads');
7+
8+
{
9+
const { port1, port2 } = new MessageChannel();
10+
11+
// Returning a non-function in the getter should not crash.
12+
Object.defineProperty(port1, 'onmessage', {
13+
get() {
14+
port1.unref();
15+
return 42;
16+
}
17+
});
18+
19+
port2.postMessage({ foo: 'bar' });
20+
21+
// We need to start the port manually because .onmessage assignment tracking
22+
// has been overridden.
23+
port1.start();
24+
port1.ref();
25+
}
26+
27+
{
28+
const err = new Error('eyecatcher');
29+
process.on('uncaughtException', common.mustCall((exception) => {
30+
port1.unref();
31+
assert.strictEqual(exception, err);
32+
}));
33+
34+
const { port1, port2 } = new MessageChannel();
35+
36+
// Throwing in the getter should not crash.
37+
Object.defineProperty(port1, 'onmessage', {
38+
get() {
39+
throw err;
40+
}
41+
});
42+
43+
port2.postMessage({ foo: 'bar' });
44+
45+
// We need to start the port manually because .onmessage assignment tracking
46+
// has been overridden.
47+
port1.start();
48+
port1.ref();
49+
}

0 commit comments

Comments
 (0)