Skip to content

Commit 38dee8a

Browse files
authored
src: distinguish HTML transferable and cloneable
The HTML structured serialize algorithm treats transferable and serializable as two different bits. A web platform interface can be both transferable and serializable. Splits BaseObject::TransferMode to be able to compose the two bits and distinguishes the transferable and cloneable. PR-URL: #47956 Refs: v8/v8@cf13b9b Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Daeyeon Jeong <[email protected]>
1 parent 3205b19 commit 38dee8a

37 files changed

+547
-320
lines changed

common.gypi

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
# Reset this number to 0 on major V8 upgrades.
3838
# Increment by one for each non-official patch applied to deps/v8.
39-
'v8_embedder_string': '-node.10',
39+
'v8_embedder_string': '-node.11',
4040

4141
##### V8 defaults for Node.js #####
4242

deps/v8/include/v8-value-serializer.h

+14
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,20 @@ class V8_EXPORT ValueSerializer {
7575
*/
7676
virtual void ThrowDataCloneError(Local<String> message) = 0;
7777

78+
/**
79+
* The embedder overrides this method to enable custom host object filter
80+
* with Delegate::IsHostObject.
81+
*
82+
* This method is called at most once per serializer.
83+
*/
84+
virtual bool HasCustomHostObject(Isolate* isolate);
85+
86+
/**
87+
* The embedder overrides this method to determine if an JS object is a
88+
* host object and needs to be serialized by the host.
89+
*/
90+
virtual Maybe<bool> IsHostObject(Isolate* isolate, Local<Object> object);
91+
7892
/**
7993
* The embedder overrides this method to write some kind of host object, if
8094
* possible. If not, a suitable exception should be thrown and

deps/v8/src/api/api.cc

+13
Original file line numberDiff line numberDiff line change
@@ -3569,6 +3569,19 @@ Maybe<bool> ValueSerializer::Delegate::WriteHostObject(Isolate* v8_isolate,
35693569
return Nothing<bool>();
35703570
}
35713571

3572+
bool ValueSerializer::Delegate::HasCustomHostObject(Isolate* v8_isolate) {
3573+
return false;
3574+
}
3575+
3576+
Maybe<bool> ValueSerializer::Delegate::IsHostObject(Isolate* v8_isolate,
3577+
Local<Object> object) {
3578+
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
3579+
i::Handle<i::JSObject> js_object =
3580+
i::Handle<i::JSObject>::cast(Utils::OpenHandle(*object));
3581+
return Just<bool>(
3582+
i::JSObject::GetEmbedderFieldCount(js_object->map(i_isolate)));
3583+
}
3584+
35723585
Maybe<uint32_t> ValueSerializer::Delegate::GetSharedArrayBufferId(
35733586
Isolate* v8_isolate, Local<SharedArrayBuffer> shared_array_buffer) {
35743587
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(v8_isolate);

deps/v8/src/objects/value-serializer.cc

+28-2
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,12 @@ ValueSerializer::ValueSerializer(Isolate* isolate,
268268
zone_(isolate->allocator(), ZONE_NAME),
269269
id_map_(isolate->heap(), ZoneAllocationPolicy(&zone_)),
270270
array_buffer_transfer_map_(isolate->heap(),
271-
ZoneAllocationPolicy(&zone_)) {}
271+
ZoneAllocationPolicy(&zone_)) {
272+
if (delegate_) {
273+
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
274+
has_custom_host_objects_ = delegate_->HasCustomHostObject(v8_isolate);
275+
}
276+
}
272277

273278
ValueSerializer::~ValueSerializer() {
274279
if (buffer_) {
@@ -582,7 +587,11 @@ Maybe<bool> ValueSerializer::WriteJSReceiver(Handle<JSReceiver> receiver) {
582587
case JS_TYPED_ARRAY_PROTOTYPE_TYPE:
583588
case JS_API_OBJECT_TYPE: {
584589
Handle<JSObject> js_object = Handle<JSObject>::cast(receiver);
585-
if (JSObject::GetEmbedderFieldCount(js_object->map(isolate_))) {
590+
Maybe<bool> is_host_object = IsHostObject(js_object);
591+
if (is_host_object.IsNothing()) {
592+
return is_host_object;
593+
}
594+
if (is_host_object.FromJust()) {
586595
return WriteHostObject(js_object);
587596
} else {
588597
return WriteJSObject(js_object);
@@ -1190,6 +1199,23 @@ Maybe<uint32_t> ValueSerializer::WriteJSObjectPropertiesSlow(
11901199
return Just(properties_written);
11911200
}
11921201

1202+
Maybe<bool> ValueSerializer::IsHostObject(Handle<JSObject> js_object) {
1203+
if (!has_custom_host_objects_) {
1204+
return Just<bool>(
1205+
JSObject::GetEmbedderFieldCount(js_object->map(isolate_)));
1206+
}
1207+
DCHECK_NOT_NULL(delegate_);
1208+
1209+
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
1210+
Maybe<bool> result =
1211+
delegate_->IsHostObject(v8_isolate, Utils::ToLocal(js_object));
1212+
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
1213+
DCHECK(!result.IsNothing());
1214+
1215+
if (V8_UNLIKELY(out_of_memory_)) return ThrowIfOutOfMemory();
1216+
return result;
1217+
}
1218+
11931219
Maybe<bool> ValueSerializer::ThrowIfOutOfMemory() {
11941220
if (out_of_memory_) {
11951221
return ThrowDataCloneError(MessageTemplate::kDataCloneErrorOutOfMemory);

deps/v8/src/objects/value-serializer.h

+3
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ class ValueSerializer {
155155
Maybe<uint32_t> WriteJSObjectPropertiesSlow(
156156
Handle<JSObject> object, Handle<FixedArray> keys) V8_WARN_UNUSED_RESULT;
157157

158+
Maybe<bool> IsHostObject(Handle<JSObject> object);
159+
158160
/*
159161
* Asks the delegate to handle an error that occurred during data cloning, by
160162
* throwing an exception appropriate for the host.
@@ -172,6 +174,7 @@ class ValueSerializer {
172174
uint8_t* buffer_ = nullptr;
173175
size_t buffer_size_ = 0;
174176
size_t buffer_capacity_ = 0;
177+
bool has_custom_host_objects_ = false;
175178
bool treat_array_buffer_views_as_host_objects_ = false;
176179
bool out_of_memory_ = false;
177180
Zone zone_;

deps/v8/test/unittests/objects/value-serializer-unittest.cc

+52-1
Original file line numberDiff line numberDiff line change
@@ -2808,7 +2808,18 @@ TEST_F(ValueSerializerTest, UnsupportedHostObject) {
28082808

28092809
class ValueSerializerTestWithHostObject : public ValueSerializerTest {
28102810
protected:
2811-
ValueSerializerTestWithHostObject() : serializer_delegate_(this) {}
2811+
ValueSerializerTestWithHostObject() : serializer_delegate_(this) {
2812+
ON_CALL(serializer_delegate_, HasCustomHostObject)
2813+
.WillByDefault([this](Isolate* isolate) {
2814+
return serializer_delegate_
2815+
.ValueSerializer::Delegate::HasCustomHostObject(isolate);
2816+
});
2817+
ON_CALL(serializer_delegate_, IsHostObject)
2818+
.WillByDefault([this](Isolate* isolate, Local<Object> object) {
2819+
return serializer_delegate_.ValueSerializer::Delegate::IsHostObject(
2820+
isolate, object);
2821+
});
2822+
}
28122823

28132824
static const uint8_t kExampleHostObjectTag;
28142825

@@ -2832,6 +2843,9 @@ class ValueSerializerTestWithHostObject : public ValueSerializerTest {
28322843
public:
28332844
explicit SerializerDelegate(ValueSerializerTestWithHostObject* test)
28342845
: test_(test) {}
2846+
MOCK_METHOD(bool, HasCustomHostObject, (Isolate*), (override));
2847+
MOCK_METHOD(Maybe<bool>, IsHostObject, (Isolate*, Local<Object> object),
2848+
(override));
28352849
MOCK_METHOD(Maybe<bool>, WriteHostObject, (Isolate*, Local<Object> object),
28362850
(override));
28372851
void ThrowDataCloneError(Local<String> message) override {
@@ -3049,6 +3063,43 @@ TEST_F(ValueSerializerTestWithHostObject, DecodeSimpleHostObject) {
30493063
});
30503064
}
30513065

3066+
TEST_F(ValueSerializerTestWithHostObject,
3067+
RoundTripHostJSObjectWithoutCustomHostObject) {
3068+
EXPECT_CALL(serializer_delegate_, HasCustomHostObject(isolate()))
3069+
.WillOnce(Invoke([](Isolate* isolate) { return false; }));
3070+
RoundTripTest("({ a: { my_host_object: true }, get b() { return this.a; }})");
3071+
}
3072+
3073+
TEST_F(ValueSerializerTestWithHostObject, RoundTripHostJSObject) {
3074+
EXPECT_CALL(serializer_delegate_, HasCustomHostObject(isolate()))
3075+
.WillOnce(Invoke([](Isolate* isolate) { return true; }));
3076+
EXPECT_CALL(serializer_delegate_, IsHostObject(isolate(), _))
3077+
.WillRepeatedly(Invoke([this](Isolate* isolate, Local<Object> object) {
3078+
EXPECT_TRUE(object->IsObject());
3079+
Local<Context> context = isolate->GetCurrentContext();
3080+
return object->Has(context, StringFromUtf8("my_host_object"));
3081+
}));
3082+
EXPECT_CALL(serializer_delegate_, WriteHostObject(isolate(), _))
3083+
.WillOnce(Invoke([this](Isolate*, Local<Object> object) {
3084+
EXPECT_TRUE(object->IsObject());
3085+
WriteExampleHostObjectTag();
3086+
return Just(true);
3087+
}));
3088+
EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
3089+
.WillOnce(Invoke([this](Isolate* isolate) {
3090+
EXPECT_TRUE(ReadExampleHostObjectTag());
3091+
Local<Context> context = isolate->GetCurrentContext();
3092+
Local<Object> obj = Object::New(isolate);
3093+
obj->Set(context, StringFromUtf8("my_host_object"), v8::True(isolate))
3094+
.Check();
3095+
return obj;
3096+
}));
3097+
RoundTripTest("({ a: { my_host_object: true }, get b() { return this.a; }})");
3098+
ExpectScriptTrue("!('my_host_object' in result)");
3099+
ExpectScriptTrue("result.a.my_host_object");
3100+
ExpectScriptTrue("result.a === result.b");
3101+
}
3102+
30523103
class ValueSerializerTestWithHostArrayBufferView
30533104
: public ValueSerializerTestWithHostObject {
30543105
protected:

lib/internal/abort_controller.js

+16-12
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ const {
5858
const assert = require('internal/assert');
5959

6060
const {
61-
messaging_deserialize_symbol: kDeserialize,
62-
messaging_transfer_symbol: kTransfer,
63-
messaging_transfer_list_symbol: kTransferList,
64-
} = internalBinding('symbols');
61+
kDeserialize,
62+
kTransfer,
63+
kTransferList,
64+
} = require('internal/worker/js_transferable');
6565

6666
let _MessageChannel;
67-
let makeTransferable;
67+
let markTransferMode;
6868

69-
// Loading the MessageChannel and makeTransferable have to be done lazily
69+
// Loading the MessageChannel and markTransferable have to be done lazily
7070
// because otherwise we'll end up with a require cycle that ends up with
7171
// an incomplete initialization of abort_controller.
7272

@@ -75,10 +75,10 @@ function lazyMessageChannel() {
7575
return new _MessageChannel();
7676
}
7777

78-
function lazyMakeTransferable(obj) {
79-
makeTransferable ??=
80-
require('internal/worker/js_transferable').makeTransferable;
81-
return makeTransferable(obj);
78+
function lazyMarkTransferMode(obj, cloneable, transferable) {
79+
markTransferMode ??=
80+
require('internal/worker/js_transferable').markTransferMode;
81+
markTransferMode(obj, cloneable, transferable);
8282
}
8383

8484
const clearTimeoutRegistry = new SafeFinalizationRegistry(clearTimeout);
@@ -355,7 +355,10 @@ function createAbortSignal(init = kEmptyObject) {
355355
signal[kAborted] = aborted;
356356
signal[kReason] = reason;
357357
signal[kComposite] = composite;
358-
return transferable ? lazyMakeTransferable(signal) : signal;
358+
if (transferable) {
359+
lazyMarkTransferMode(signal, false, true);
360+
}
361+
return signal;
359362
}
360363

361364
function abortSignal(signal, reason) {
@@ -411,7 +414,8 @@ class AbortController {
411414
function transferableAbortSignal(signal) {
412415
if (signal?.[kAborted] === undefined)
413416
throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal);
414-
return lazyMakeTransferable(signal);
417+
lazyMarkTransferMode(signal, false, true);
418+
return signal;
415419
}
416420

417421
/**

lib/internal/blob.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const {
3232
const { URL } = require('internal/url');
3333

3434
const {
35-
makeTransferable,
35+
markTransferMode,
3636
kClone,
3737
kDeserialize,
3838
} = require('internal/worker/js_transferable');
@@ -136,6 +136,8 @@ class Blob {
136136
* @constructs {Blob}
137137
*/
138138
constructor(sources = [], options) {
139+
markTransferMode(this, true, false);
140+
139141
if (sources === null ||
140142
typeof sources[SymbolIterator] !== 'function' ||
141143
typeof sources === 'string') {
@@ -167,9 +169,6 @@ class Blob {
167169
type = `${type}`;
168170
this[kType] = RegExpPrototypeExec(disallowedTypeCharacters, type) !== null ?
169171
'' : StringPrototypeToLowerCase(type);
170-
171-
// eslint-disable-next-line no-constructor-return
172-
return makeTransferable(this);
173172
}
174173

175174
[kInspect](depth, options) {
@@ -385,16 +384,19 @@ class Blob {
385384
}
386385

387386
function ClonedBlob() {
388-
return makeTransferable(ReflectConstruct(function() {}, [], Blob));
387+
return ReflectConstruct(function() {
388+
markTransferMode(this, true, false);
389+
}, [], Blob);
389390
}
390391
ClonedBlob.prototype[kDeserialize] = () => {};
391392

392393
function createBlob(handle, length, type = '') {
393-
return makeTransferable(ReflectConstruct(function() {
394+
return ReflectConstruct(function() {
395+
markTransferMode(this, true, false);
394396
this[kHandle] = handle;
395397
this[kType] = type;
396398
this[kLength] = length;
397-
}, [], Blob));
399+
}, [], Blob);
398400
}
399401

400402
ObjectDefineProperty(Blob.prototype, SymbolToStringTag, {

lib/internal/blocklist.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const {
2020
} = require('internal/socketaddress');
2121

2222
const {
23-
JSTransferable,
23+
markTransferMode,
2424
kClone,
2525
kDeserialize,
2626
} = require('internal/worker/js_transferable');
@@ -36,9 +36,9 @@ const {
3636

3737
const { validateInt32, validateString } = require('internal/validators');
3838

39-
class BlockList extends JSTransferable {
39+
class BlockList {
4040
constructor() {
41-
super();
41+
markTransferMode(this, true, false);
4242
this[kHandle] = new BlockListHandle();
4343
this[kHandle][owner_symbol] = this;
4444
}
@@ -148,9 +148,9 @@ class BlockList extends JSTransferable {
148148
}
149149
}
150150

151-
class InternalBlockList extends JSTransferable {
151+
class InternalBlockList {
152152
constructor(handle) {
153-
super();
153+
markTransferMode(this, true, false);
154154
this[kHandle] = handle;
155155
if (handle !== undefined)
156156
handle[owner_symbol] = this;

lib/internal/crypto/keys.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const {
5757
} = require('internal/util/types');
5858

5959
const {
60-
makeTransferable,
60+
markTransferMode,
6161
kClone,
6262
kDeserialize,
6363
} = require('internal/worker/js_transferable');
@@ -706,24 +706,22 @@ ObjectDefineProperties(CryptoKey.prototype, {
706706
// All internal code must use new InternalCryptoKey to create
707707
// CryptoKey instances. The CryptoKey class is exposed to end
708708
// user code but is not permitted to be constructed directly.
709-
// Using makeTransferable also allows the CryptoKey to be
709+
// Using markTransferMode also allows the CryptoKey to be
710710
// cloned to Workers.
711711
class InternalCryptoKey {
712712
constructor(
713713
keyObject,
714714
algorithm,
715715
keyUsages,
716716
extractable) {
717+
markTransferMode(this, true, false);
717718
// Using symbol properties here currently instead of private
718719
// properties because (for now) the performance penalty of
719720
// private fields is still too high.
720721
this[kKeyObject] = keyObject;
721722
this[kAlgorithm] = algorithm;
722723
this[kExtractable] = extractable;
723724
this[kKeyUsages] = keyUsages;
724-
725-
// eslint-disable-next-line no-constructor-return
726-
return makeTransferable(this);
727725
}
728726

729727
[kClone]() {

0 commit comments

Comments
 (0)