Skip to content

Commit d60432b

Browse files
committed
src: throw DataCloneError on transfering untransferable objects
The HTML StructuredSerializeWithTransfer algorithm defines that when an untransferable object is in the transfer list, a DataCloneError is thrown. An array buffer that is already transferred is also considered as untransferable.
1 parent 682256c commit d60432b

9 files changed

+54
-15
lines changed

doc/api/worker_threads.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,15 @@ if (isMainThread) {
128128
added:
129129
- v14.5.0
130130
- v12.19.0
131+
changes:
132+
- version: REPLACEME
133+
pr-url: https://github.com/nodejs/node/pull/47604
134+
description: An error is thrown when the untransferable object is in the
135+
transfer list.
131136
-->
132137

133138
Mark an object as not transferable. If `object` occurs in the transfer list of
134-
a [`port.postMessage()`][] call, it is ignored.
139+
a [`port.postMessage()`][] call, an error is thrown.
135140

136141
In particular, this makes sense for objects that can be cloned, rather than
137142
transferred, and which are used by other objects on the sending side.
@@ -568,6 +573,11 @@ are part of the channel.
568573
<!-- YAML
569574
added: v10.5.0
570575
changes:
576+
changes:
577+
- version: REPLACEME
578+
pr-url: https://github.com/nodejs/node/pull/47604
579+
description: An error is thrown when an untransferable object is in the
580+
transfer list.
571581
- version:
572582
- v15.14.0
573583
- v14.18.0

src/env_properties.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
V(change_string, "change") \
6767
V(channel_string, "channel") \
6868
V(chunks_sent_since_last_write_string, "chunksSentSinceLastWrite") \
69-
V(clone_unsupported_type_str, "Cannot transfer object of unsupported type.") \
69+
V(clone_unsupported_type_str, "Cannot clone object of unsupported type.") \
7070
V(code_string, "code") \
7171
V(commonjs_string, "commonjs") \
7272
V(config_string, "config") \
@@ -301,6 +301,8 @@
301301
V(time_to_first_header_string, "timeToFirstHeader") \
302302
V(tls_ticket_string, "tlsTicket") \
303303
V(transfer_string, "transfer") \
304+
V(transfer_unsupported_type_str, \
305+
"Cannot transfer object of unsupported type.") \
304306
V(ttl_string, "ttl") \
305307
V(type_string, "type") \
306308
V(uid_string, "uid") \

src/node_messaging.cc

+11-5
Original file line numberDiff line numberDiff line change
@@ -459,11 +459,14 @@ Maybe<bool> Message::Serialize(Environment* env,
459459
.To(&untransferable)) {
460460
return Nothing<bool>();
461461
}
462-
if (untransferable) continue;
462+
if (untransferable) {
463+
ThrowDataCloneException(context, env->transfer_unsupported_type_str());
464+
return Nothing<bool>();
465+
}
463466
}
464467

465468
// Currently, we support ArrayBuffers and BaseObjects for which
466-
// GetTransferMode() does not return kUntransferable.
469+
// GetTransferMode() returns kTransferable.
467470
if (entry->IsArrayBuffer()) {
468471
Local<ArrayBuffer> ab = entry.As<ArrayBuffer>();
469472
// If we cannot render the ArrayBuffer unusable in this Isolate,
@@ -474,7 +477,10 @@ Maybe<bool> Message::Serialize(Environment* env,
474477
// raw data *and* an Isolate with a non-default ArrayBuffer allocator
475478
// is always going to outlive any Workers it creates, and so will its
476479
// allocator along with it.
477-
if (!ab->IsDetachable()) continue;
480+
if (!ab->IsDetachable() || ab->WasDetached()) {
481+
ThrowDataCloneException(context, env->transfer_unsupported_type_str());
482+
return Nothing<bool>();
483+
}
478484
if (std::find(array_buffers.begin(), array_buffers.end(), ab) !=
479485
array_buffers.end()) {
480486
ThrowDataCloneException(
@@ -524,8 +530,8 @@ Maybe<bool> Message::Serialize(Environment* env,
524530
entry.As<Object>()->GetConstructorName()));
525531
return Nothing<bool>();
526532
}
527-
if (host_object && host_object->GetTransferMode() !=
528-
BaseObject::TransferMode::kUntransferable) {
533+
if (host_object && host_object->GetTransferMode() ==
534+
BaseObject::TransferMode::kTransferable) {
529535
delegate.AddHostObject(host_object);
530536
continue;
531537
}

test/addons/worker-buffer-callback/test.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ const { buffer } = require(`./build/${common.buildType}/binding`);
99

1010
const { port1 } = new MessageChannel();
1111
const origByteLength = buffer.byteLength;
12-
port1.postMessage(buffer, [buffer.buffer]);
12+
assert.throws(() => port1.postMessage(buffer, [buffer.buffer]), {
13+
code: 25,
14+
name: 'DataCloneError',
15+
});
1316

1417
assert.strictEqual(buffer.byteLength, origByteLength);
1518
assert.notStrictEqual(buffer.byteLength, 0);

test/node-api/test_worker_buffer_callback/test.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ const { buffer } = require(`./build/${common.buildType}/binding`);
99

1010
const { port1 } = new MessageChannel();
1111
const origByteLength = buffer.byteLength;
12-
port1.postMessage(buffer, [buffer]);
12+
assert.throws(() => port1.postMessage(buffer, [buffer]), {
13+
code: 25,
14+
name: 'DataCloneError',
15+
});
1316

1417
assert.strictEqual(buffer.byteLength, origByteLength);
1518
assert.notStrictEqual(buffer.byteLength, 0);

test/parallel/test-buffer-pool-untransferable.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ assert.strictEqual(a.buffer, b.buffer);
1313
const length = a.length;
1414

1515
const { port1 } = new MessageChannel();
16-
port1.postMessage(a, [ a.buffer ]);
16+
assert.throws(() => port1.postMessage(a, [ a.buffer ]), {
17+
code: 25,
18+
name: 'DataCloneError',
19+
});
1720

1821
// Verify that the pool ArrayBuffer has not actually been transferred:
1922
assert.strictEqual(a.buffer, b.buffer);

test/parallel/test-worker-message-port-arraybuffer.js

+7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ const { MessageChannel } = require('worker_threads');
1212
typedArray[0] = 0x12345678;
1313

1414
port1.postMessage(typedArray, [ arrayBuffer ]);
15+
assert.strictEqual(arrayBuffer.byteLength, 0);
16+
// Transferring again should throw a DataCloneError.
17+
assert.throws(() => port1.postMessage(typedArray, [ arrayBuffer ]), {
18+
code: 25,
19+
name: 'DataCloneError',
20+
});
21+
1522
port2.on('message', common.mustCall((received) => {
1623
assert.strictEqual(received[0], 0x12345678);
1724
port2.close(common.mustCall());

test/parallel/test-worker-message-port-transfer-native.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const { internalBinding } = require('internal/test/binding');
3131
port1.postMessage(nativeObject);
3232
}, {
3333
name: 'DataCloneError',
34-
message: /Cannot transfer object of unsupported type\.$/
34+
message: /Cannot clone object of unsupported type\.$/
3535
});
3636
port1.close();
3737
}

test/parallel/test-worker-message-transfer-port-mark-as-untransferable.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ const { MessageChannel, markAsUntransferable } = require('worker_threads');
99
markAsUntransferable(ab);
1010
assert.strictEqual(ab.byteLength, 8);
1111

12-
const { port1, port2 } = new MessageChannel();
13-
port1.postMessage(ab, [ ab ]);
12+
const { port1 } = new MessageChannel();
13+
assert.throws(() => port1.postMessage(ab, [ ab ]), {
14+
code: 25,
15+
name: 'DataCloneError',
16+
});
1417

1518
assert.strictEqual(ab.byteLength, 8); // The AB is not detached.
16-
port2.once('message', common.mustCall());
1719
}
1820

1921
{
@@ -24,7 +26,10 @@ const { MessageChannel, markAsUntransferable } = require('worker_threads');
2426

2527
assert.throws(() => {
2628
channel1.port1.postMessage(channel2.port1, [ channel2.port1 ]);
27-
}, /was found in message but not listed in transferList/);
29+
}, {
30+
code: 25,
31+
name: 'DataCloneError',
32+
});
2833

2934
channel2.port1.postMessage('still works, not closed/transferred');
3035
channel2.port2.once('message', common.mustCall());

0 commit comments

Comments
 (0)