Skip to content
File

Blob: src/workerd/jsg/ser.c++

21.9 KB
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include "ser.h"
6 
7#include "dom-exception.h"
8#include "setup.h"
9 
10#include <v8-proxy.h>
11 
12namespace workerd::jsg {
13namespace {
14// Keep in sync with the nativeError serialization tag defined in
15// worker-interface.capnp
16constexpr uint32_t SERIALIZATION_TAG_NATIVE_ERROR = 10;
17 
18// The error tag is serialized as a uint32_t immediately following
19// the SERIALIZATION_TAG_NATIVE_ERROR tag in order to more efficiently
20// determine the type of error when deserializing so that we can
21// construct the appropriate v8::Exception type on deserialization
22// without having to expensive string comparison on the error name.
23enum class ErrorTag : uint32_t {
24 // The UNKNOWN tag is used when the error name is not recognized.
25 // When this occurs, we will serialize the name of the error, and
26 // when it is deserialized, we will create a generic Error and then
27 // set the name to the stored name.
28 UNKNOWN,
29 ERROR,
30 TYPE_ERROR,
31 RANGE_ERROR,
32 REFERENCE_ERROR,
33 SYNTAX_ERROR,
34 WASM_COMPILE_ERROR,
35 WASM_LINK_ERROR,
36 WASM_RUNTIME_ERROR,
37 WASM_SUSPEND_ERROR,
38 EVAL_ERROR,
39 URI_ERROR,
40 AGGREGATE_ERROR,
41 SUPPRESSED_ERROR,
42};
43 
44constexpr ErrorTag getErrorTagFromName(jsg::Lock& js, kj::StringPtr str) {
45 if (str == "Error"_kj) {
46 return ErrorTag::ERROR;
47 } else if (str == "TypeError"_kj) {
48 return ErrorTag::TYPE_ERROR;
49 } else if (str == "RangeError"_kj) {
50 return ErrorTag::RANGE_ERROR;
51 } else if (str == "ReferenceError"_kj) {
52 return ErrorTag::REFERENCE_ERROR;
53 } else if (str == "SyntaxError"_kj) {
54 return ErrorTag::SYNTAX_ERROR;
55 } else if (str == "WasmCompileError"_kj) {
56 return ErrorTag::WASM_COMPILE_ERROR;
57 } else if (str == "WasmLinkError"_kj) {
58 return ErrorTag::WASM_LINK_ERROR;
59 } else if (str == "WasmRuntimeError"_kj) {
60 return ErrorTag::WASM_RUNTIME_ERROR;
61 } else if (str == "WasmSuspendError"_kj) {
62 return ErrorTag::WASM_SUSPEND_ERROR;
63 } else if (str == "EvalError"_kj) {
64 return ErrorTag::EVAL_ERROR;
65 } else if (str == "URIError"_kj) {
66 return ErrorTag::URI_ERROR;
67 } else if (str == "AggregateError"_kj) {
68 return ErrorTag::AGGREGATE_ERROR;
69 } else if (str == "SuppressedError"_kj) {
70 return ErrorTag::SUPPRESSED_ERROR;
71 }
72 return ErrorTag::UNKNOWN;
73}
74 
75JsObject toJsError(Lock& js, ErrorTag tag, JsString message) {
76 switch (tag) {
77 case ErrorTag::ERROR: {
78 return JsObject(v8::Exception::Error(message).As<v8::Object>());
79 }
80 case ErrorTag::TYPE_ERROR: {
81 return JsObject(v8::Exception::TypeError(message).As<v8::Object>());
82 }
83 case ErrorTag::RANGE_ERROR: {
84 return JsObject(v8::Exception::RangeError(message).As<v8::Object>());
85 }
86 case ErrorTag::REFERENCE_ERROR: {
87 return JsObject(v8::Exception::ReferenceError(message).As<v8::Object>());
88 }
89 case ErrorTag::SYNTAX_ERROR: {
90 return JsObject(v8::Exception::SyntaxError(message).As<v8::Object>());
91 }
92 case ErrorTag::WASM_COMPILE_ERROR: {
93 return JsObject(v8::Exception::WasmCompileError(message).As<v8::Object>());
94 }
95 case ErrorTag::WASM_LINK_ERROR: {
96 return JsObject(v8::Exception::WasmLinkError(message).As<v8::Object>());
97 }
98 case ErrorTag::WASM_RUNTIME_ERROR: {
99 return JsObject(v8::Exception::WasmRuntimeError(message).As<v8::Object>());
100 }
101 case ErrorTag::WASM_SUSPEND_ERROR: {
102 return JsObject(v8::Exception::WasmSuspendError(message).As<v8::Object>());
103 }
104 case ErrorTag::EVAL_ERROR: {
105 return JsObject(v8::Exception::EvalError(message).As<v8::Object>());
106 }
107 case ErrorTag::URI_ERROR: {
108 return JsObject(v8::Exception::URIError(message).As<v8::Object>());
109 }
110 case ErrorTag::AGGREGATE_ERROR: {
111 return JsObject(v8::Exception::AggregateError(message).As<v8::Object>());
112 }
113 case ErrorTag::SUPPRESSED_ERROR: {
114 return JsObject(v8::Exception::SuppressedError(message).As<v8::Object>());
115 }
116 case ErrorTag::UNKNOWN: {
117 return JsObject(v8::Exception::Error(message).As<v8::Object>());
118 }
119 }
120 return JsObject(v8::Exception::Error(message).As<v8::Object>());
121}
122} // namespace
123 
124void Serializer::ExternalHandler::serializeFunction(
125 jsg::Lock& js, jsg::Serializer& serializer, v8::Local<v8::Function> func) {
126 JSG_FAIL_REQUIRE(DOMDataCloneError, func, " could not be cloned.");
127}
128 
129void Serializer::ExternalHandler::serializeProxy(
130 jsg::Lock& js, jsg::Serializer& serializer, v8::Local<v8::Proxy> proxy) {
131 JSG_FAIL_REQUIRE(DOMDataCloneError, proxy, " could not be cloned.");
132}
133 
134Serializer::Serializer(Lock& js, Options options)
135 : externalHandler(options.externalHandler),
136 treatClassInstancesAsPlainObjects(options.treatClassInstancesAsPlainObjects),
137 treatErrorsAsHostObjects(js.isUsingEnhancedErrorSerialization()),
138 ser(js.v8Isolate, this) {
139#ifdef KJ_DEBUG
140 kj::requireOnStack(this, "jsg::Serializer must be allocated on the stack");
141#endif
142 if (!treatClassInstancesAsPlainObjects) {
143 prototypeOfObject = js.obj().getPrototype(js);
144 }
145 if (externalHandler != kj::none) {
146 // If we have an ExternalHandler, we'll ask it to serialize host objects.
147 ser.SetTreatFunctionsAsHostObjects(true);
148 ser.SetTreatProxiesAsHostObjects(true);
149 }
150 KJ_IF_SOME(version, options.version) {
151 KJ_ASSERT(version >= 13, "The minimum serialization version is 13.");
152 KJ_ASSERT(jsg::check(ser.SetWriteVersion(version)));
153 }
154 if (!options.omitHeader) {
155 ser.WriteHeader();
156 }
157}
158 
159v8::Maybe<uint32_t> Serializer::GetSharedArrayBufferId(
160 v8::Isolate* isolate, v8::Local<v8::SharedArrayBuffer> sab) {
161 auto& js = jsg::Lock::from(isolate);
162 uint32_t n;
163 auto value = JsValue(sab);
164 for (n = 0; n < sharedArrayBuffers.size(); n++) {
165 // If the SharedArrayBuffer has already been added, return the existing ID for it.
166 if (sharedArrayBuffers[n].getHandle(js) == value) {
167 return v8::Just(n);
168 }
169 }
170 sharedArrayBuffers.add(jsg::JsRef(js, value));
171 sharedBackingStores.add(sab->GetBackingStore());
172 return v8::Just(n);
173}
174 
175void Serializer::throwDataCloneErrorForObject(jsg::Lock& js, v8::Local<v8::Object> obj) {
176 // The default error that V8 would generate is "#<TypeName> could not be cloned." -- for some
177 // reason, it surrounds the type name in "#<>", which seems bizarre? Let's generate a better
178 // error.
179 auto message = kj::str("Could not serialize object of type \"", obj->GetConstructorName(),
180 "\". This type does "
181 "not support serialization.");
182 auto exception = js.domException(kj::str("DataCloneError"), kj::mv(message));
183 js.throwException(jsg::JsValue(KJ_ASSERT_NONNULL(exception.tryGetHandle(js))));
184}
185 
186void Serializer::ThrowDataCloneError(v8::Local<v8::String> message) {
187 auto& js = jsg::Lock::current();
188 try {
189 auto exception = js.domException(kj::str("DataCloneError"), kj::str(message));
190 js.v8Isolate->ThrowException(KJ_ASSERT_NONNULL(exception.tryGetHandle(js)));
191 } catch (JsExceptionThrown&) {
192 // Apparently an exception was thrown during the construction of the DOMException. Most likely
193 // we were terminated. In any case, we'll let that exception stay scheduled and propagate back
194 // to V8.
195 } catch (...) {
196 // A KJ exception was thrown, we'll have to convert it to JavaScript and propagate that
197 // exception instead.
198 throwInternalError(js.v8Isolate, kj::getCaughtExceptionAsKj());
199 }
200}
201 
202bool Serializer::HasCustomHostObject(v8::Isolate* isolate) {
203 // By default, V8 will call WriteHostObject() for objects that have internal fields. We only need
204 // to override IsHostObject() if we want to treat pure-JS objects differently, which we do if
205 // treatClassInstancesAsPlainObjects is false, or if treatErrorsAsHostObjects is true.
206 return !treatClassInstancesAsPlainObjects || treatErrorsAsHostObjects;
207}
208 
209v8::Maybe<bool> Serializer::IsHostObject(v8::Isolate* isolate, v8::Local<v8::Object> object) {
210 // This is only called if HasCustomHostObject() returned true.
211 KJ_ASSERT(!treatClassInstancesAsPlainObjects || treatErrorsAsHostObjects);
212 
213 // Any object with internal fields is DEFINITELY a host object!
214 if (object->InternalFieldCount() > 0) {
215 return v8::Just(true);
216 }
217 
218 // Native errors are host object if the enhanced_error_serialization compat flag is enalbed.
219 if (object->IsNativeError()) {
220 return v8::Just(treatErrorsAsHostObjects);
221 }
222 
223 // If `treatClassInstancesAsPlainObjects` is on (the historical default), then nothing else is
224 // a host object. If it has been turned off (e.g. for RPC), then any object that isn't a raw
225 // object will be treated as a host object (mainly so that we can error out on these).
226 if (treatClassInstancesAsPlainObjects) return v8::Just(false);
227 KJ_ASSERT(!prototypeOfObject.IsEmpty());
228 
229 // If the object's prototype is Object.prototype, then it is a plain object, which we'll allow
230 // to be serialized normally. Otherwise, it is a class instance, which we should treat as a host
231 // object. Inside `WriteHostObject()` we will throw DataCloneError due to the object not having
232 // internal fields.
233#if V8_MAJOR_VERSION >= 15 || (V8_MAJOR_VERSION == 14 && V8_MINOR_VERSION >= 7)
234 return v8::Just(object->GetPrototype() != prototypeOfObject);
235#else
236 // TODO(cleanup): Remove when unnecessary.
237 return v8::Just(object->GetPrototypeV2() != prototypeOfObject);
238#endif
239}
240 
241v8::Maybe<bool> Serializer::WriteHostObject(v8::Isolate* isolate, v8::Local<v8::Object> object) {
242 try {
243 jsg::Lock& js = jsg::Lock::from(isolate);
244 
245 if (object->IsNativeError()) {
246 auto nameStr = js.str("name"_kj);
247 auto messageStr = js.str("message"_kj);
248 
249 // Get the standard name, message, stack, cause, error, errors properties from
250 // the error object.
251 writeRawUint32(SERIALIZATION_TAG_NATIVE_ERROR);
252 
253 // A mix of ad-hoc and regular serialization. We first serialize
254 // the error tag, which is an enum that identifies the type of error
255 // for faster/easier deserialization. Then we serialize the message which
256 // usually come from the prototype. Then we grab the own properties,
257 // serializing the number of them followed by each name and value in
258 // sequence.
259 
260 jsg::JsObject errorObj(object);
261 auto name = errorObj.get(js, nameStr);
262 auto nameKjStr = name.toString(js);
263 auto tag = getErrorTagFromName(js, nameKjStr);
264 writeRawUint32(static_cast<uint32_t>(tag));
265 // We only write the name if it is not one of the known error types.
266 if (tag == ErrorTag::UNKNOWN) {
267 write(js, name);
268 }
269 
270 write(js, errorObj.get(js, messageStr));
271 
272 auto names = errorObj.getPropertyNames(js, KeyCollectionFilter::OWN_ONLY,
273 PropertyFilter::ALL_PROPERTIES, IndexFilter::SKIP_INDICES);
274 
275 auto obj = js.obj();
276 for (size_t n = 0; n < names.size(); n++) {
277 auto name = names.get(js, n);
278 // The name typically comes from the prototype and therefore
279 // do not show up in the own properties of the error object, and the
280 // message we want to treat specially since we need it early in the
281 // deserialization.
282 // Since we already have them serialized above, we can filter them
283 // out here.
284 if (name.strictEquals(nameStr) || name.strictEquals(messageStr)) continue;
285 
286 obj.set(js, name, errorObj.get(js, name));
287 }
288 write(js, obj);
289 
290 return v8::Just(true);
291 }
292 
293 if (object->InternalFieldCount() != Wrappable::INTERNAL_FIELD_COUNT ||
294 !Wrappable::isWorkerdApiObject(object)) {
295 KJ_IF_SOME(eh, externalHandler) {
296 if (object->IsProxy()) {
297 eh.serializeProxy(js, *this, object.As<v8::Proxy>());
298 return v8::Just(true);
299 } else if (object->IsFunction()) {
300 eh.serializeFunction(js, *this, object.As<v8::Function>());
301 return v8::Just(true);
302 }
303 }
304 
305 // v8::ValueSerializer by default will send us anything that has internal fields, but this
306 // object doesn't appear to match the internal fields expected on a JSG object.
307 //
308 // We also get here if treatClassInstancesAsPlainObjects is false, and the object is an
309 // application-defined class. We don't currently support serializing class instances.
310 throwDataCloneErrorForObject(js, object);
311 }
312 
313 Wrappable* wrappable = reinterpret_cast<Wrappable*>(
314 object->GetAlignedPointerFromInternalField(Wrappable::WRAPPED_OBJECT_FIELD_INDEX,
315 static_cast<v8::EmbedderDataTypeTag>(Wrappable::WRAPPED_OBJECT_FIELD_INDEX)));
316 
317 // HACK: Although we don't technically know yet that `wrappable` is an `Object`, we know that
318 // only subclasses of `Object` register serializers. So *if* a serializer is found, then this
319 // cast is valid, and the pointer won't be accessed otherwise. We can't do a dynamic_cast
320 // here since `Wrappable` is privately inherited by `Object` and anyway we don't want the
321 // overhead of dynamic_cast.
322 // TODO(cleanup): Probably `Wrappable` should contain a bool indicating if it is an `Object`
323 // or not?
324 Object* obj = reinterpret_cast<jsg::Object*>(wrappable);
325 
326 if (!IsolateBase::from(isolate).serialize(
327 Lock::from(isolate), typeid(*wrappable), *obj, *this)) {
328 // This type is not serializable.
329 throwDataCloneErrorForObject(js, object);
330 }
331 
332 return v8::Just(true);
333 } catch (JsExceptionThrown&) {
334 return v8::Nothing<bool>();
335 } catch (...) {
336 throwInternalError(isolate, kj::getCaughtExceptionAsKj());
337 return v8::Nothing<bool>();
338 }
339}
340 
341Serializer::Released Serializer::release() {
342 KJ_ASSERT(!released, "The data has already been released.");
343 released = true;
344 sharedArrayBuffers.clear();
345 arrayBuffers.clear();
346 auto pair = ser.Release();
347 return Released{
348 .data = kj::Array(pair.first, pair.second, jsg::SERIALIZED_BUFFER_DISPOSER),
349 .sharedArrayBuffers = sharedBackingStores.releaseAsArray(),
350 .transferredArrayBuffers = backingStores.releaseAsArray(),
351 };
352}
353 
354void Serializer::transfer(Lock& js, const JsValue& value) {
355 KJ_ASSERT(!released, "The data has already been released.");
356 // Currently we only allow transfer of ArrayBuffers
357 v8::Local<v8::ArrayBuffer> arrayBuffer;
358 if (value.isArrayBufferView()) {
359 auto view = v8::Local<v8::Value>(value).As<v8::ArrayBufferView>();
360 arrayBuffer = view->Buffer();
361 } else if (value.isArrayBuffer()) {
362 arrayBuffer = v8::Local<v8::Value>(value).As<v8::ArrayBuffer>();
363 } else {
364 JSG_FAIL_REQUIRE(TypeError, "Object is not transferable");
365 }
366 
367 // SharedArrayBuffers are not transferable. Per the HTML spec, attempting to
368 // transfer a SharedArrayBuffer (or a view backed by one) must throw a
369 // DataCloneError. We must check before calling Detach(), which would trigger
370 // a fatal V8 CHECK failure on non-detachable buffers.
371 JSG_REQUIRE(arrayBuffer->IsDetachable(), DOMDataCloneError, "Object is not transferable.");
372 
373 uint32_t n;
374 for (n = 0; n < arrayBuffers.size(); n++) {
375 // If the ArrayBuffer has already been added, we do not want to try adding it again.
376 if (arrayBuffers[n].getHandle(js) == value) {
377 return;
378 }
379 }
380 arrayBuffers.add(jsg::JsRef(js, value));
381 
382 backingStores.add(arrayBuffer->GetBackingStore());
383 check(arrayBuffer->Detach(v8::Local<v8::Value>()));
384 ser.TransferArrayBuffer(n, arrayBuffer);
385}
386 
387void Serializer::write(Lock& js, const JsValue& value) {
388 KJ_ASSERT(!released, "The data has already been released.");
389 KJ_ASSERT(check(ser.WriteValue(js.v8Context(), value)));
390}
391 
392Deserializer::ExternalHandler::~ExternalHandler() noexcept(false) {}
393 
394Deserializer::Deserializer(Lock& js,
395 kj::ArrayPtr<const kj::byte> data,
396 kj::Maybe<kj::ArrayPtr<std::shared_ptr<v8::BackingStore>>> transferredArrayBuffers,
397 kj::Maybe<kj::ArrayPtr<std::shared_ptr<v8::BackingStore>>> sharedArrayBuffers,
398 kj::Maybe<Options> maybeOptions)
399 : totalInputSize(data.size()),
400 deser(js.v8Isolate, data.begin(), data.size(), this),
401 sharedBackingStores(kj::mv(sharedArrayBuffers)) {
402#ifdef KJ_DEBUG
403 kj::requireOnStack(this, "jsg::Deserializer must be allocated on the stack");
404#endif
405 init(js, kj::mv(transferredArrayBuffers), kj::mv(maybeOptions));
406}
407 
408Deserializer::Deserializer(
409 Lock& js, Serializer::Released& released, kj::Maybe<Options> maybeOptions)
410 : Deserializer(js,
411 released.data.asPtr(),
412 released.transferredArrayBuffers.asPtr(),
413 released.sharedArrayBuffers.asPtr(),
414 kj::mv(maybeOptions)) {}
415 
416void Deserializer::init(Lock& js,
417 kj::Maybe<kj::ArrayPtr<std::shared_ptr<v8::BackingStore>>> transferredArrayBuffers,
418 kj::Maybe<Options> maybeOptions) {
419 auto options = kj::mv(maybeOptions).orDefault({});
420 externalHandler = options.externalHandler;
421 if (options.readHeader) {
422 check(deser.ReadHeader(js.v8Context()));
423 }
424 preserveStackInErrors = options.preserveStackInErrors;
425 KJ_IF_SOME(version, options.version) {
426 KJ_ASSERT(version >= 13, "The minimum serialization version is 13.");
427 deser.SetWireFormatVersion(version);
428 }
429 KJ_IF_SOME(arrayBuffers, transferredArrayBuffers) {
430 for (auto n: kj::indices(arrayBuffers)) {
431 deser.TransferArrayBuffer(n, v8::ArrayBuffer::New(js.v8Isolate, kj::mv((arrayBuffers)[n])));
432 }
433 }
434}
435 
436JsValue Deserializer::readValue(Lock& js) {
437 return JsValue(check(deser.ReadValue(js.v8Context())));
438}
439 
440uint Deserializer::readRawUint32() {
441 uint32_t result;
442 KJ_ASSERT(deser.ReadUint32(&result), "deserialization failure, possible corruption");
443 return result;
444}
445 
446uint64_t Deserializer::readRawUint64() {
447 uint64_t result;
448 KJ_ASSERT(deser.ReadUint64(&result), "deserialization failure, possible corruption");
449 return result;
450}
451 
452kj::ArrayPtr<const kj::byte> Deserializer::readRawBytes(size_t size) {
453 const void* data;
454 KJ_ASSERT(deser.ReadRawBytes(size, &data), "deserialization failure, possible corruption");
455 return kj::arrayPtr(reinterpret_cast<const kj::byte*>(data), size);
456}
457 
458kj::ArrayPtr<const kj::byte> Deserializer::readLengthDelimitedBytes() {
459 return readRawBytes(readRawUint64());
460}
461 
462kj::String Deserializer::readRawString(size_t size) {
463 return kj::str(readRawBytes(size).asChars());
464}
465 
466kj::String Deserializer::readLengthDelimitedString() {
467 return kj::str(readLengthDelimitedBytes().asChars());
468}
469 
470v8::MaybeLocal<v8::SharedArrayBuffer> Deserializer::GetSharedArrayBufferFromId(
471 v8::Isolate* isolate, uint32_t clone_id) {
472 KJ_IF_SOME(backingStores, sharedBackingStores) {
473 KJ_ASSERT(clone_id < backingStores.size());
474 return v8::SharedArrayBuffer::New(isolate, backingStores[clone_id]);
475 }
476 return v8::MaybeLocal<v8::SharedArrayBuffer>();
477}
478 
479v8::MaybeLocal<v8::Object> Deserializer::ReadHostObject(v8::Isolate* isolate) {
480 try {
481 uint tag = readRawUint32();
482 
483 if (tag == SERIALIZATION_TAG_NATIVE_ERROR) {
484 auto& js = Lock::from(isolate);
485 auto stack = js.str("stack"_kj);
486 
487 // The first uint32_t is the error tag, which identifies the type of error.
488 auto errorTag = static_cast<ErrorTag>(readRawUint32());
489 // If The error tag is UNKNOWN, we will read the name of the error next.
490 // If the error is known, we don't both serializing the name.
491 kj::Maybe<JsValue> maybeName;
492 if (errorTag == ErrorTag::UNKNOWN) {
493 maybeName = readValue(js);
494 }
495 
496 // The next value is the message, which is always present.
497 auto messageValue = readValue(js);
498 auto messageStr = KJ_UNWRAP_OR(messageValue.tryCast<JsString>(), {
499 JSG_FAIL_REQUIRE(
500 DOMDataCloneError, "Deserialization failed: error message is not a string");
501 });
502 // Now let's create the error object based on the tag and message.
503 auto obj = toJsError(js, errorTag, messageStr);
504 
505 // If we have a name, we set it on the error object. This is not
506 // perfect but it gets close enough. Specifically, when the error
507 // was serialized, if the user has modified the name or created
508 // their own subclass, then we end up having to create just a
509 // regular error here and change the name. It is not possible
510 // for us here to clone the exact error class that was used,
511 // so instanceof checks will not work as expected. But, that's ok.
512 KJ_IF_SOME(name, maybeName) {
513 // We use defineProperty here since the name is not typically
514 // modifiable with set() on error objects.
515 obj.defineProperty(js, "name"_kj, name);
516 }
517 
518 // Now let's read the remaining properties... They were serialized as
519 // a plain object with some own properties.
520 KJ_IF_SOME(serObj, readValue(js).tryCast<JsObject>()) {
521 auto names = serObj.getPropertyNames(js, KeyCollectionFilter::OWN_ONLY,
522 PropertyFilter::ALL_PROPERTIES, IndexFilter::SKIP_INDICES);
523 for (size_t n = 0; n < names.size(); n++) {
524 auto name = names.get(js, n);
525 // If the preserveStackInErrors option is false, then we will not
526 // restore the serialized stack property if it is included in the
527 // serialized output.
528 if (!preserveStackInErrors && name.strictEquals(stack)) continue;
529 auto value = serObj.get(js, name);
530 obj.set(js, name, value);
531 }
532 }
533 
534 v8::Local<v8::Object> ret = obj;
535 return ret;
536 }
537 
538 KJ_IF_SOME(result, IsolateBase::from(isolate).deserialize(Lock::from(isolate), tag, *this)) {
539 return result;
540 } else {
541 // Unknown tag is a platform error, so use KJ assert.
542 KJ_FAIL_ASSERT("encountered unknown tag in deserialization", tag);
543 }
544 } catch (JsExceptionThrown&) {
545 return {};
546 } catch (...) {
547 throwInternalError(isolate, kj::getCaughtExceptionAsKj());
548 return {};
549 }
550}
551 
552void SerializedBufferDisposer::disposeImpl(void* firstElement,
553 size_t elementSize,
554 size_t elementCount,
555 size_t capacity,
556 void (*destroyElement)(void*)) const {
557 free(firstElement);
558}
559 
560JsValue structuredClone(
561 Lock& js, const JsValue& value, kj::Maybe<kj::Array<JsValue>> maybeTransfer) {
562 Serializer ser(js);
563 KJ_IF_SOME(transfers, maybeTransfer) {
564 for (auto& item: transfers) {
565 ser.transfer(js, item);
566 }
567 }
568 ser.write(js, value);
569 auto released = ser.release();
570 Deserializer des(js, released);
571 return des.readValue(js);
572}
573 
574} // namespace workerd::jsg