%PDF- %PDF-
Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/objects/ |
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/objects/value-serializer.cc |
// Copyright 2016 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/objects/value-serializer.h" #include <type_traits> #include "include/v8-maybe.h" #include "include/v8-value-serializer-version.h" #include "include/v8-value-serializer.h" #include "include/v8-wasm.h" #include "src/api/api-inl.h" #include "src/base/logging.h" #include "src/base/platform/memory.h" #include "src/execution/isolate.h" #include "src/flags/flags.h" #include "src/handles/global-handles-inl.h" #include "src/handles/handles-inl.h" #include "src/handles/maybe-handles-inl.h" #include "src/handles/shared-object-conveyor-handles.h" #include "src/heap/factory.h" #include "src/numbers/conversions.h" #include "src/objects/heap-number-inl.h" #include "src/objects/js-array-buffer-inl.h" #include "src/objects/js-array-buffer.h" #include "src/objects/js-array-inl.h" #include "src/objects/js-collection-inl.h" #include "src/objects/js-regexp-inl.h" #include "src/objects/js-shared-array-inl.h" #include "src/objects/js-struct-inl.h" #include "src/objects/map-updater.h" #include "src/objects/objects-inl.h" #include "src/objects/objects.h" #include "src/objects/oddball-inl.h" #include "src/objects/ordered-hash-table-inl.h" #include "src/objects/property-descriptor.h" #include "src/objects/property-details.h" #include "src/objects/smi.h" #include "src/objects/transitions-inl.h" #include "src/snapshot/code-serializer.h" #if V8_ENABLE_WEBASSEMBLY #include "src/wasm/wasm-objects-inl.h" #endif // V8_ENABLE_WEBASSEMBLY namespace v8 { namespace internal { // Version 9: (imported from Blink) // Version 10: one-byte (Latin-1) strings // Version 11: properly separate undefined from the hole in arrays // Version 12: regexp and string objects share normal string encoding // Version 13: host objects have an explicit tag (rather than handling all // unknown tags) // Version 14: flags for JSArrayBufferViews // Version 15: support for shared objects with an explicit tag // // WARNING: Increasing this value is a change which cannot safely be rolled // back without breaking compatibility with data stored on disk. It is // strongly recommended that you do not make such changes near a release // milestone branch point. // // Recent changes are routinely reverted in preparation for branch, and this // has been the cause of at least one bug in the past. static const uint32_t kLatestVersion = 15; static_assert(kLatestVersion == v8::CurrentValueSerializerFormatVersion(), "Exported format version must match latest version."); namespace { // For serializing JSArrayBufferView flags. Instead of serializing / // deserializing the flags directly, we serialize them bit by bit. This is for // ensuring backwards compatilibity in the case where the representation // changes. Note that the ValueSerializer data can be stored on disk. using JSArrayBufferViewIsLengthTracking = base::BitField<bool, 0, 1>; using JSArrayBufferViewIsBackedByRab = JSArrayBufferViewIsLengthTracking::Next<bool, 1>; } // namespace template <typename T> static size_t BytesNeededForVarint(T value) { static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value, "Only unsigned integer types can be written as varints."); size_t result = 0; do { result++; value >>= 7; } while (value); return result; } enum class SerializationTag : uint8_t { // version:uint32_t (if at beginning of data, sets version > 0) kVersion = 0xFF, // ignore kPadding = '\0', // refTableSize:uint32_t (previously used for sanity checks; safe to ignore) kVerifyObjectCount = '?', // Oddballs (no data). kTheHole = '-', kUndefined = '_', kNull = '0', kTrue = 'T', kFalse = 'F', // Number represented as 32-bit integer, ZigZag-encoded // (like sint32 in protobuf) kInt32 = 'I', // Number represented as 32-bit unsigned integer, varint-encoded // (like uint32 in protobuf) kUint32 = 'U', // Number represented as a 64-bit double. // Host byte order is used (N.B. this makes the format non-portable). kDouble = 'N', // BigInt. Bitfield:uint32_t, then raw digits storage. kBigInt = 'Z', // byteLength:uint32_t, then raw data kUtf8String = 'S', kOneByteString = '"', kTwoByteString = 'c', // Reference to a serialized object. objectID:uint32_t kObjectReference = '^', // Beginning of a JS object. kBeginJSObject = 'o', // End of a JS object. numProperties:uint32_t kEndJSObject = '{', // Beginning of a sparse JS array. length:uint32_t // Elements and properties are written as key/value pairs, like objects. kBeginSparseJSArray = 'a', // End of a sparse JS array. numProperties:uint32_t length:uint32_t kEndSparseJSArray = '@', // Beginning of a dense JS array. length:uint32_t // |length| elements, followed by properties as key/value pairs kBeginDenseJSArray = 'A', // End of a dense JS array. numProperties:uint32_t length:uint32_t kEndDenseJSArray = '$', // Date. millisSinceEpoch:double kDate = 'D', // Boolean object. No data. kTrueObject = 'y', kFalseObject = 'x', // Number object. value:double kNumberObject = 'n', // BigInt object. Bitfield:uint32_t, then raw digits storage. kBigIntObject = 'z', // String object, UTF-8 encoding. byteLength:uint32_t, then raw data. kStringObject = 's', // Regular expression, UTF-8 encoding. byteLength:uint32_t, raw data, // flags:uint32_t. kRegExp = 'R', // Beginning of a JS map. kBeginJSMap = ';', // End of a JS map. length:uint32_t. kEndJSMap = ':', // Beginning of a JS set. kBeginJSSet = '\'', // End of a JS set. length:uint32_t. kEndJSSet = ',', // Array buffer. byteLength:uint32_t, then raw data. kArrayBuffer = 'B', // Resizable ArrayBuffer. kResizableArrayBuffer = '~', // Array buffer (transferred). transferID:uint32_t kArrayBufferTransfer = 't', // View into an array buffer. // subtag:ArrayBufferViewTag, byteOffset:uint32_t, byteLength:uint32_t // For typed arrays, byteOffset and byteLength must be divisible by the size // of the element. // Note: kArrayBufferView is special, and should have an ArrayBuffer (or an // ObjectReference to one) serialized just before it. This is a quirk arising // from the previous stack-based implementation. kArrayBufferView = 'V', // Shared array buffer. transferID:uint32_t kSharedArrayBuffer = 'u', // A HeapObject shared across Isolates. sharedValueID:uint32_t kSharedObject = 'p', // A wasm module object transfer. next value is its index. kWasmModuleTransfer = 'w', // The delegate is responsible for processing all following data. // This "escapes" to whatever wire format the delegate chooses. kHostObject = '\\', // A transferred WebAssembly.Memory object. maximumPages:int32_t, then by // SharedArrayBuffer tag and its data. kWasmMemoryTransfer = 'm', // A list of (subtag: ErrorTag, [subtag dependent data]). See ErrorTag for // details. kError = 'r', // The following tags are reserved because they were in use in Chromium before // the kHostObject tag was introduced in format version 13, at // v8 refs/heads/master@{#43466} // chromium/src refs/heads/master@{#453568} // // They must not be reused without a version check to prevent old values from // starting to deserialize incorrectly. For simplicity, it's recommended to // avoid them altogether. // // This is the set of tags that existed in SerializationTag.h at that time and // still exist at the time of this writing (i.e., excluding those that were // removed on the Chromium side because there should be no real user data // containing them). // // It might be possible to also free up other tags which were never persisted // (e.g. because they were used only for transfer) in the future. kLegacyReservedMessagePort = 'M', kLegacyReservedBlob = 'b', kLegacyReservedBlobIndex = 'i', kLegacyReservedFile = 'f', kLegacyReservedFileIndex = 'e', kLegacyReservedDOMFileSystem = 'd', kLegacyReservedFileList = 'l', kLegacyReservedFileListIndex = 'L', kLegacyReservedImageData = '#', kLegacyReservedImageBitmap = 'g', kLegacyReservedImageBitmapTransfer = 'G', kLegacyReservedOffscreenCanvas = 'H', kLegacyReservedCryptoKey = 'K', kLegacyReservedRTCCertificate = 'k', }; namespace { enum class ArrayBufferViewTag : uint8_t { kInt8Array = 'b', kUint8Array = 'B', kUint8ClampedArray = 'C', kInt16Array = 'w', kUint16Array = 'W', kInt32Array = 'd', kUint32Array = 'D', kFloat32Array = 'f', kFloat64Array = 'F', kBigInt64Array = 'q', kBigUint64Array = 'Q', kDataView = '?', }; // Sub-tags only meaningful for error serialization. enum class ErrorTag : uint8_t { // The error is a EvalError. No accompanying data. kEvalErrorPrototype = 'E', // The error is a RangeError. No accompanying data. kRangeErrorPrototype = 'R', // The error is a ReferenceError. No accompanying data. kReferenceErrorPrototype = 'F', // The error is a SyntaxError. No accompanying data. kSyntaxErrorPrototype = 'S', // The error is a TypeError. No accompanying data. kTypeErrorPrototype = 'T', // The error is a URIError. No accompanying data. kUriErrorPrototype = 'U', // Followed by message: string. kMessage = 'm', // Followed by a JS object: cause. kCause = 'c', // Followed by stack: string. kStack = 's', // The end of this error information. kEnd = '.', }; } // namespace ValueSerializer::ValueSerializer(Isolate* isolate, v8::ValueSerializer::Delegate* delegate) : isolate_(isolate), delegate_(delegate), zone_(isolate->allocator(), ZONE_NAME), id_map_(isolate->heap(), ZoneAllocationPolicy(&zone_)), array_buffer_transfer_map_(isolate->heap(), ZoneAllocationPolicy(&zone_)) { if (delegate_) { v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); has_custom_host_objects_ = delegate_->HasCustomHostObject(v8_isolate); } } ValueSerializer::~ValueSerializer() { if (buffer_) { if (delegate_) { delegate_->FreeBufferMemory(buffer_); } else { base::Free(buffer_); } } } void ValueSerializer::WriteHeader() { WriteTag(SerializationTag::kVersion); WriteVarint(kLatestVersion); } void ValueSerializer::SetTreatArrayBufferViewsAsHostObjects(bool mode) { treat_array_buffer_views_as_host_objects_ = mode; } void ValueSerializer::WriteTag(SerializationTag tag) { uint8_t raw_tag = static_cast<uint8_t>(tag); WriteRawBytes(&raw_tag, sizeof(raw_tag)); } template <typename T> void ValueSerializer::WriteVarint(T value) { // Writes an unsigned integer as a base-128 varint. // The number is written, 7 bits at a time, from the least significant to the // most significant 7 bits. Each byte, except the last, has the MSB set. // See also https://developers.google.com/protocol-buffers/docs/encoding static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value, "Only unsigned integer types can be written as varints."); uint8_t stack_buffer[sizeof(T) * 8 / 7 + 1]; uint8_t* next_byte = &stack_buffer[0]; do { *next_byte = (value & 0x7F) | 0x80; next_byte++; value >>= 7; } while (value); *(next_byte - 1) &= 0x7F; WriteRawBytes(stack_buffer, next_byte - stack_buffer); } template <typename T> void ValueSerializer::WriteZigZag(T value) { // Writes a signed integer as a varint using ZigZag encoding (i.e. 0 is // encoded as 0, -1 as 1, 1 as 2, -2 as 3, and so on). // See also https://developers.google.com/protocol-buffers/docs/encoding // Note that this implementation relies on the right shift being arithmetic. static_assert(std::is_integral<T>::value && std::is_signed<T>::value, "Only signed integer types can be written as zigzag."); using UnsignedT = typename std::make_unsigned<T>::type; WriteVarint((static_cast<UnsignedT>(value) << 1) ^ (value >> (8 * sizeof(T) - 1))); } template EXPORT_TEMPLATE_DEFINE( V8_EXPORT_PRIVATE) void ValueSerializer::WriteZigZag(int32_t value); void ValueSerializer::WriteDouble(double value) { // Warning: this uses host endianness. WriteRawBytes(&value, sizeof(value)); } void ValueSerializer::WriteOneByteString(base::Vector<const uint8_t> chars) { WriteVarint<uint32_t>(chars.length()); WriteRawBytes(chars.begin(), chars.length() * sizeof(uint8_t)); } void ValueSerializer::WriteTwoByteString(base::Vector<const base::uc16> chars) { // Warning: this uses host endianness. WriteVarint<uint32_t>(chars.length() * sizeof(base::uc16)); WriteRawBytes(chars.begin(), chars.length() * sizeof(base::uc16)); } void ValueSerializer::WriteBigIntContents(Tagged<BigInt> bigint) { uint32_t bitfield = bigint->GetBitfieldForSerialization(); int bytelength = BigInt::DigitsByteLengthForBitfield(bitfield); WriteVarint<uint32_t>(bitfield); uint8_t* dest; if (ReserveRawBytes(bytelength).To(&dest)) { bigint->SerializeDigits(dest); } } void ValueSerializer::WriteRawBytes(const void* source, size_t length) { uint8_t* dest; if (ReserveRawBytes(length).To(&dest) && length > 0) { memcpy(dest, source, length); } } Maybe<uint8_t*> ValueSerializer::ReserveRawBytes(size_t bytes) { size_t old_size = buffer_size_; size_t new_size = old_size + bytes; if (V8_UNLIKELY(new_size > buffer_capacity_)) { bool ok; if (!ExpandBuffer(new_size).To(&ok)) { return Nothing<uint8_t*>(); } } buffer_size_ = new_size; return Just(&buffer_[old_size]); } Maybe<bool> ValueSerializer::ExpandBuffer(size_t required_capacity) { DCHECK_GT(required_capacity, buffer_capacity_); size_t requested_capacity = std::max(required_capacity, buffer_capacity_ * 2) + 64; size_t provided_capacity = 0; void* new_buffer = nullptr; if (delegate_) { new_buffer = delegate_->ReallocateBufferMemory(buffer_, requested_capacity, &provided_capacity); } else { new_buffer = base::Realloc(buffer_, requested_capacity); provided_capacity = requested_capacity; } if (new_buffer) { DCHECK(provided_capacity >= requested_capacity); buffer_ = reinterpret_cast<uint8_t*>(new_buffer); buffer_capacity_ = provided_capacity; return Just(true); } else { out_of_memory_ = true; return Nothing<bool>(); } } void ValueSerializer::WriteByte(uint8_t value) { uint8_t* dest; if (ReserveRawBytes(sizeof(uint8_t)).To(&dest)) { *dest = value; } } void ValueSerializer::WriteUint32(uint32_t value) { WriteVarint<uint32_t>(value); } void ValueSerializer::WriteUint64(uint64_t value) { WriteVarint<uint64_t>(value); } std::pair<uint8_t*, size_t> ValueSerializer::Release() { auto result = std::make_pair(buffer_, buffer_size_); buffer_ = nullptr; buffer_size_ = 0; buffer_capacity_ = 0; return result; } void ValueSerializer::TransferArrayBuffer(uint32_t transfer_id, Handle<JSArrayBuffer> array_buffer) { DCHECK(!array_buffer_transfer_map_.Find(array_buffer)); DCHECK(!array_buffer->is_shared()); array_buffer_transfer_map_.Insert(array_buffer, transfer_id); } Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) { // There is no sense in trying to proceed if we've previously run out of // memory. Bail immediately, as this likely implies that some write has // previously failed and so the buffer is corrupt. if (V8_UNLIKELY(out_of_memory_)) return ThrowIfOutOfMemory(); if (IsSmi(*object)) { WriteSmi(Smi::cast(*object)); return ThrowIfOutOfMemory(); } DCHECK(IsHeapObject(*object)); InstanceType instance_type = HeapObject::cast(*object)->map(isolate_)->instance_type(); switch (instance_type) { case ODDBALL_TYPE: WriteOddball(Oddball::cast(*object)); return ThrowIfOutOfMemory(); case HEAP_NUMBER_TYPE: WriteHeapNumber(HeapNumber::cast(*object)); return ThrowIfOutOfMemory(); case BIGINT_TYPE: WriteBigInt(BigInt::cast(*object)); return ThrowIfOutOfMemory(); case JS_TYPED_ARRAY_TYPE: case JS_DATA_VIEW_TYPE: case JS_RAB_GSAB_DATA_VIEW_TYPE: { // Despite being JSReceivers, these have their wrapped buffer serialized // first. That makes this logic a little quirky, because it needs to // happen before we assign object IDs. // TODO(jbroman): It may be possible to avoid materializing a typed // array's buffer here. Handle<JSArrayBufferView> view = Handle<JSArrayBufferView>::cast(object); if (!id_map_.Find(view) && !treat_array_buffer_views_as_host_objects_) { Handle<JSArrayBuffer> buffer( InstanceTypeChecker::IsJSTypedArray(instance_type) ? Handle<JSTypedArray>::cast(view)->GetBuffer() : handle(JSArrayBuffer::cast(view->buffer()), isolate_)); if (!WriteJSReceiver(buffer).FromMaybe(false)) return Nothing<bool>(); } return WriteJSReceiver(view); } default: if (InstanceTypeChecker::IsString(instance_type)) { WriteString(Handle<String>::cast(object)); return ThrowIfOutOfMemory(); } else if (InstanceTypeChecker::IsJSReceiver(instance_type)) { return WriteJSReceiver(Handle<JSReceiver>::cast(object)); } else { return ThrowDataCloneError(MessageTemplate::kDataCloneError, object); } } } void ValueSerializer::WriteOddball(Tagged<Oddball> oddball) { SerializationTag tag = SerializationTag::kUndefined; switch (oddball->kind()) { case Oddball::kUndefined: tag = SerializationTag::kUndefined; break; case Oddball::kFalse: tag = SerializationTag::kFalse; break; case Oddball::kTrue: tag = SerializationTag::kTrue; break; case Oddball::kNull: tag = SerializationTag::kNull; break; default: UNREACHABLE(); } WriteTag(tag); } void ValueSerializer::WriteSmi(Tagged<Smi> smi) { static_assert(kSmiValueSize <= 32, "Expected SMI <= 32 bits."); WriteTag(SerializationTag::kInt32); WriteZigZag<int32_t>(smi.value()); } void ValueSerializer::WriteHeapNumber(Tagged<HeapNumber> number) { WriteTag(SerializationTag::kDouble); WriteDouble(number->value()); } void ValueSerializer::WriteBigInt(Tagged<BigInt> bigint) { WriteTag(SerializationTag::kBigInt); WriteBigIntContents(bigint); } void ValueSerializer::WriteString(Handle<String> string) { string = String::Flatten(isolate_, string); DisallowGarbageCollection no_gc; String::FlatContent flat = string->GetFlatContent(no_gc); DCHECK(flat.IsFlat()); if (flat.IsOneByte()) { base::Vector<const uint8_t> chars = flat.ToOneByteVector(); WriteTag(SerializationTag::kOneByteString); WriteOneByteString(chars); } else if (flat.IsTwoByte()) { base::Vector<const base::uc16> chars = flat.ToUC16Vector(); uint32_t byte_length = chars.length() * sizeof(base::uc16); // The existing reading code expects 16-byte strings to be aligned. if ((buffer_size_ + 1 + BytesNeededForVarint(byte_length)) & 1) WriteTag(SerializationTag::kPadding); WriteTag(SerializationTag::kTwoByteString); WriteTwoByteString(chars); } else { UNREACHABLE(); } } Maybe<bool> ValueSerializer::WriteJSReceiver(Handle<JSReceiver> receiver) { // If the object has already been serialized, just write its ID. auto find_result = id_map_.FindOrInsert(receiver); if (find_result.already_exists) { WriteTag(SerializationTag::kObjectReference); WriteVarint(*find_result.entry - 1); return ThrowIfOutOfMemory(); } // Otherwise, allocate an ID for it. uint32_t id = next_id_++; *find_result.entry = id + 1; // Eliminate callable and exotic objects, which should not be serialized. InstanceType instance_type = receiver->map()->instance_type(); if (IsCallable(*receiver) || (IsSpecialReceiverInstanceType(instance_type) && instance_type != JS_SPECIAL_API_OBJECT_TYPE)) { return ThrowDataCloneError(MessageTemplate::kDataCloneError, receiver); } // If we are at the end of the stack, abort. This function may recurse. STACK_CHECK(isolate_, Nothing<bool>()); HandleScope scope(isolate_); switch (instance_type) { case JS_ARRAY_TYPE: return WriteJSArray(Handle<JSArray>::cast(receiver)); case JS_ARRAY_ITERATOR_PROTOTYPE_TYPE: case JS_ITERATOR_PROTOTYPE_TYPE: case JS_MAP_ITERATOR_PROTOTYPE_TYPE: case JS_OBJECT_PROTOTYPE_TYPE: case JS_OBJECT_TYPE: case JS_PROMISE_PROTOTYPE_TYPE: case JS_REG_EXP_PROTOTYPE_TYPE: case JS_SET_ITERATOR_PROTOTYPE_TYPE: case JS_SET_PROTOTYPE_TYPE: case JS_STRING_ITERATOR_PROTOTYPE_TYPE: case JS_TYPED_ARRAY_PROTOTYPE_TYPE: case JS_API_OBJECT_TYPE: { Handle<JSObject> js_object = Handle<JSObject>::cast(receiver); Maybe<bool> is_host_object = IsHostObject(js_object); if (is_host_object.IsNothing()) { return is_host_object; } if (is_host_object.FromJust()) { return WriteHostObject(js_object); } else { return WriteJSObject(js_object); } } case JS_SPECIAL_API_OBJECT_TYPE: return WriteHostObject(Handle<JSObject>::cast(receiver)); case JS_DATE_TYPE: WriteJSDate(JSDate::cast(*receiver)); return ThrowIfOutOfMemory(); case JS_PRIMITIVE_WRAPPER_TYPE: return WriteJSPrimitiveWrapper( Handle<JSPrimitiveWrapper>::cast(receiver)); case JS_REG_EXP_TYPE: WriteJSRegExp(Handle<JSRegExp>::cast(receiver)); return ThrowIfOutOfMemory(); case JS_MAP_TYPE: return WriteJSMap(Handle<JSMap>::cast(receiver)); case JS_SET_TYPE: return WriteJSSet(Handle<JSSet>::cast(receiver)); case JS_ARRAY_BUFFER_TYPE: return WriteJSArrayBuffer(Handle<JSArrayBuffer>::cast(receiver)); case JS_TYPED_ARRAY_TYPE: case JS_DATA_VIEW_TYPE: case JS_RAB_GSAB_DATA_VIEW_TYPE: return WriteJSArrayBufferView(JSArrayBufferView::cast(*receiver)); case JS_ERROR_TYPE: return WriteJSError(Handle<JSObject>::cast(receiver)); case JS_SHARED_ARRAY_TYPE: return WriteJSSharedArray(Handle<JSSharedArray>::cast(receiver)); case JS_SHARED_STRUCT_TYPE: return WriteJSSharedStruct(Handle<JSSharedStruct>::cast(receiver)); case JS_ATOMICS_MUTEX_TYPE: case JS_ATOMICS_CONDITION_TYPE: return WriteSharedObject(receiver); #if V8_ENABLE_WEBASSEMBLY case WASM_MODULE_OBJECT_TYPE: return WriteWasmModule(Handle<WasmModuleObject>::cast(receiver)); case WASM_MEMORY_OBJECT_TYPE: return WriteWasmMemory(Handle<WasmMemoryObject>::cast(receiver)); #endif // V8_ENABLE_WEBASSEMBLY default: break; } return ThrowDataCloneError(MessageTemplate::kDataCloneError, receiver); } Maybe<bool> ValueSerializer::WriteJSObject(Handle<JSObject> object) { DCHECK(!IsCustomElementsReceiverMap(object->map())); const bool can_serialize_fast = object->HasFastProperties(isolate_) && object->elements()->length() == 0; if (!can_serialize_fast) return WriteJSObjectSlow(object); Handle<Map> map(object->map(), isolate_); WriteTag(SerializationTag::kBeginJSObject); // Write out fast properties as long as they are only data properties and the // map doesn't change. uint32_t properties_written = 0; bool map_changed = false; for (InternalIndex i : map->IterateOwnDescriptors()) { Handle<Name> key(map->instance_descriptors(isolate_)->GetKey(i), isolate_); if (!IsString(*key, isolate_)) continue; PropertyDetails details = map->instance_descriptors(isolate_)->GetDetails(i); if (details.IsDontEnum()) continue; Handle<Object> value; if (V8_LIKELY(!map_changed)) map_changed = *map != object->map(); if (V8_LIKELY(!map_changed && details.location() == PropertyLocation::kField)) { DCHECK_EQ(PropertyKind::kData, details.kind()); FieldIndex field_index = FieldIndex::ForDetails(*map, details); value = JSObject::FastPropertyAt(isolate_, object, details.representation(), field_index); } else { // This logic should essentially match WriteJSObjectPropertiesSlow. // If the property is no longer found, do not serialize it. // This could happen if a getter deleted the property. LookupIterator it(isolate_, object, key, LookupIterator::OWN); if (!it.IsFound()) continue; if (!Object::GetProperty(&it).ToHandle(&value)) return Nothing<bool>(); } if (!WriteObject(key).FromMaybe(false) || !WriteObject(value).FromMaybe(false)) { return Nothing<bool>(); } properties_written++; } WriteTag(SerializationTag::kEndJSObject); WriteVarint<uint32_t>(properties_written); return ThrowIfOutOfMemory(); } Maybe<bool> ValueSerializer::WriteJSObjectSlow(Handle<JSObject> object) { WriteTag(SerializationTag::kBeginJSObject); Handle<FixedArray> keys; uint32_t properties_written = 0; if (!KeyAccumulator::GetKeys(isolate_, object, KeyCollectionMode::kOwnOnly, ENUMERABLE_STRINGS) .ToHandle(&keys) || !WriteJSObjectPropertiesSlow(object, keys).To(&properties_written)) { return Nothing<bool>(); } WriteTag(SerializationTag::kEndJSObject); WriteVarint<uint32_t>(properties_written); return ThrowIfOutOfMemory(); } Maybe<bool> ValueSerializer::WriteJSArray(Handle<JSArray> array) { PtrComprCageBase cage_base(isolate_); uint32_t length = 0; bool valid_length = Object::ToArrayLength(array->length(), &length); DCHECK(valid_length); USE(valid_length); // To keep things simple, for now we decide between dense and sparse // serialization based on elements kind. A more principled heuristic could // count the elements, but would need to take care to note which indices // existed (as only indices which were enumerable own properties at this point // should be serialized). const bool should_serialize_densely = array->HasFastElements(cage_base) && !array->HasHoleyElements(cage_base); if (should_serialize_densely) { DCHECK_LE(length, static_cast<uint32_t>(FixedArray::kMaxLength)); WriteTag(SerializationTag::kBeginDenseJSArray); WriteVarint<uint32_t>(length); uint32_t i = 0; // Fast paths. Note that PACKED_ELEMENTS in particular can bail due to the // structure of the elements changing. switch (array->GetElementsKind(cage_base)) { case PACKED_SMI_ELEMENTS: { DisallowGarbageCollection no_gc; Tagged<FixedArray> elements = FixedArray::cast(array->elements()); for (i = 0; i < length; i++) WriteSmi(Smi::cast(elements->get(cage_base, i))); break; } case PACKED_DOUBLE_ELEMENTS: { // Elements are empty_fixed_array, not a FixedDoubleArray, if the array // is empty. No elements to encode in this case anyhow. if (length == 0) break; DisallowGarbageCollection no_gc; Tagged<FixedDoubleArray> elements = FixedDoubleArray::cast(array->elements()); for (i = 0; i < length; i++) { WriteTag(SerializationTag::kDouble); WriteDouble(elements->get_scalar(i)); } break; } case PACKED_ELEMENTS: { Handle<Object> old_length(array->length(cage_base), isolate_); for (; i < length; i++) { if (array->length(cage_base) != *old_length || array->GetElementsKind(cage_base) != PACKED_ELEMENTS) { // Fall back to slow path. break; } Handle<Object> element( FixedArray::cast(array->elements())->get(cage_base, i), isolate_); if (!WriteObject(element).FromMaybe(false)) return Nothing<bool>(); } break; } default: break; } // If there are elements remaining, serialize them slowly. for (; i < length; i++) { // Serializing the array's elements can have arbitrary side effects, so we // cannot rely on still having fast elements, even if it did to begin // with. Handle<Object> element; LookupIterator it(isolate_, array, i, array, LookupIterator::OWN); if (!it.IsFound()) { // This can happen in the case where an array that was originally dense // became sparse during serialization. It's too late to switch to the // sparse format, but we can mark the elements as absent. WriteTag(SerializationTag::kTheHole); continue; } if (!Object::GetProperty(&it).ToHandle(&element) || !WriteObject(element).FromMaybe(false)) { return Nothing<bool>(); } } Handle<FixedArray> keys; if (!KeyAccumulator::GetKeys(isolate_, array, KeyCollectionMode::kOwnOnly, ENUMERABLE_STRINGS, GetKeysConversion::kKeepNumbers, false, true) .ToHandle(&keys)) { return Nothing<bool>(); } uint32_t properties_written; if (!WriteJSObjectPropertiesSlow(array, keys).To(&properties_written)) { return Nothing<bool>(); } WriteTag(SerializationTag::kEndDenseJSArray); WriteVarint<uint32_t>(properties_written); WriteVarint<uint32_t>(length); } else { WriteTag(SerializationTag::kBeginSparseJSArray); WriteVarint<uint32_t>(length); Handle<FixedArray> keys; uint32_t properties_written = 0; if (!KeyAccumulator::GetKeys(isolate_, array, KeyCollectionMode::kOwnOnly, ENUMERABLE_STRINGS) .ToHandle(&keys) || !WriteJSObjectPropertiesSlow(array, keys).To(&properties_written)) { return Nothing<bool>(); } WriteTag(SerializationTag::kEndSparseJSArray); WriteVarint<uint32_t>(properties_written); WriteVarint<uint32_t>(length); } return ThrowIfOutOfMemory(); } void ValueSerializer::WriteJSDate(Tagged<JSDate> date) { WriteTag(SerializationTag::kDate); WriteDouble(Object::Number(date->value())); } Maybe<bool> ValueSerializer::WriteJSPrimitiveWrapper( Handle<JSPrimitiveWrapper> value) { PtrComprCageBase cage_base(isolate_); { DisallowGarbageCollection no_gc; Tagged<Object> inner_value = value->value(); if (IsTrue(inner_value, isolate_)) { WriteTag(SerializationTag::kTrueObject); } else if (IsFalse(inner_value, isolate_)) { WriteTag(SerializationTag::kFalseObject); } else if (IsNumber(inner_value, cage_base)) { WriteTag(SerializationTag::kNumberObject); WriteDouble(Object::Number(inner_value)); } else if (IsBigInt(inner_value, cage_base)) { WriteTag(SerializationTag::kBigIntObject); WriteBigIntContents(BigInt::cast(inner_value)); } else if (IsString(inner_value, cage_base)) { WriteTag(SerializationTag::kStringObject); WriteString(handle(String::cast(inner_value), isolate_)); } else { AllowGarbageCollection allow_gc; DCHECK(IsSymbol(inner_value)); return ThrowDataCloneError(MessageTemplate::kDataCloneError, value); } } return ThrowIfOutOfMemory(); } void ValueSerializer::WriteJSRegExp(Handle<JSRegExp> regexp) { WriteTag(SerializationTag::kRegExp); WriteString(handle(regexp->source(), isolate_)); WriteVarint(static_cast<uint32_t>(regexp->flags())); } Maybe<bool> ValueSerializer::WriteJSMap(Handle<JSMap> js_map) { // First copy the key-value pairs, since getters could mutate them. Handle<OrderedHashMap> table(OrderedHashMap::cast(js_map->table()), isolate_); int length = table->NumberOfElements() * 2; Handle<FixedArray> entries = isolate_->factory()->NewFixedArray(length); { DisallowGarbageCollection no_gc; Tagged<OrderedHashMap> raw_table = *table; Tagged<FixedArray> raw_entries = *entries; Tagged<Hole> hash_table_hole = ReadOnlyRoots(isolate_).hash_table_hole_value(); int result_index = 0; for (InternalIndex entry : raw_table->IterateEntries()) { Tagged<Object> key = raw_table->KeyAt(entry); if (key == hash_table_hole) continue; raw_entries->set(result_index++, key); raw_entries->set(result_index++, raw_table->ValueAt(entry)); } DCHECK_EQ(result_index, length); } // Then write it out. WriteTag(SerializationTag::kBeginJSMap); for (int i = 0; i < length; i++) { if (!WriteObject(handle(entries->get(i), isolate_)).FromMaybe(false)) { return Nothing<bool>(); } } WriteTag(SerializationTag::kEndJSMap); WriteVarint<uint32_t>(length); return ThrowIfOutOfMemory(); } Maybe<bool> ValueSerializer::WriteJSSet(Handle<JSSet> js_set) { // First copy the element pointers, since getters could mutate them. Handle<OrderedHashSet> table(OrderedHashSet::cast(js_set->table()), isolate_); int length = table->NumberOfElements(); Handle<FixedArray> entries = isolate_->factory()->NewFixedArray(length); { DisallowGarbageCollection no_gc; Tagged<OrderedHashSet> raw_table = *table; Tagged<FixedArray> raw_entries = *entries; Tagged<Hole> hash_table_hole = ReadOnlyRoots(isolate_).hash_table_hole_value(); int result_index = 0; for (InternalIndex entry : raw_table->IterateEntries()) { Tagged<Object> key = raw_table->KeyAt(entry); if (key == hash_table_hole) continue; raw_entries->set(result_index++, key); } DCHECK_EQ(result_index, length); } // Then write it out. WriteTag(SerializationTag::kBeginJSSet); for (int i = 0; i < length; i++) { if (!WriteObject(handle(entries->get(i), isolate_)).FromMaybe(false)) { return Nothing<bool>(); } } WriteTag(SerializationTag::kEndJSSet); WriteVarint<uint32_t>(length); return ThrowIfOutOfMemory(); } Maybe<bool> ValueSerializer::WriteJSArrayBuffer( Handle<JSArrayBuffer> array_buffer) { if (array_buffer->is_shared()) { if (!delegate_) { return ThrowDataCloneError(MessageTemplate::kDataCloneError, array_buffer); } v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); Maybe<uint32_t> index = delegate_->GetSharedArrayBufferId( v8_isolate, Utils::ToLocalShared(array_buffer)); RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>()); WriteTag(SerializationTag::kSharedArrayBuffer); WriteVarint(index.FromJust()); return ThrowIfOutOfMemory(); } uint32_t* transfer_entry = array_buffer_transfer_map_.Find(array_buffer); if (transfer_entry) { WriteTag(SerializationTag::kArrayBufferTransfer); WriteVarint(*transfer_entry); return ThrowIfOutOfMemory(); } if (array_buffer->was_detached()) { return ThrowDataCloneError( MessageTemplate::kDataCloneErrorDetachedArrayBuffer); } size_t byte_length = array_buffer->byte_length(); if (byte_length > std::numeric_limits<uint32_t>::max()) { return ThrowDataCloneError(MessageTemplate::kDataCloneError, array_buffer); } if (array_buffer->is_resizable_by_js()) { size_t max_byte_length = array_buffer->max_byte_length(); if (max_byte_length > std::numeric_limits<uint32_t>::max()) { return ThrowDataCloneError(MessageTemplate::kDataCloneError, array_buffer); } WriteTag(SerializationTag::kResizableArrayBuffer); WriteVarint<uint32_t>(static_cast<uint32_t>(byte_length)); WriteVarint<uint32_t>(static_cast<uint32_t>(max_byte_length)); WriteRawBytes(array_buffer->backing_store(), byte_length); return ThrowIfOutOfMemory(); } WriteTag(SerializationTag::kArrayBuffer); WriteVarint<uint32_t>(static_cast<uint32_t>(byte_length)); WriteRawBytes(array_buffer->backing_store(), byte_length); return ThrowIfOutOfMemory(); } Maybe<bool> ValueSerializer::WriteJSArrayBufferView( Tagged<JSArrayBufferView> view) { if (treat_array_buffer_views_as_host_objects_) { return WriteHostObject(handle(view, isolate_)); } WriteTag(SerializationTag::kArrayBufferView); ArrayBufferViewTag tag = ArrayBufferViewTag::kInt8Array; if (IsJSTypedArray(view)) { if (JSTypedArray::cast(view)->IsOutOfBounds()) { DCHECK(v8_flags.harmony_rab_gsab); return ThrowDataCloneError(MessageTemplate::kDataCloneError, handle(view, isolate_)); } switch (JSTypedArray::cast(view)->type()) { #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ case kExternal##Type##Array: \ tag = ArrayBufferViewTag::k##Type##Array; \ break; TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE } } else { DCHECK(IsJSDataViewOrRabGsabDataView(view)); if (IsJSRabGsabDataView(view) && JSRabGsabDataView::cast(view)->IsOutOfBounds()) { DCHECK(v8_flags.harmony_rab_gsab); return ThrowDataCloneError(MessageTemplate::kDataCloneError, handle(view, isolate_)); } tag = ArrayBufferViewTag::kDataView; } WriteVarint(static_cast<uint8_t>(tag)); WriteVarint(static_cast<uint32_t>(view->byte_offset())); WriteVarint(static_cast<uint32_t>(view->byte_length())); uint32_t flags = JSArrayBufferViewIsLengthTracking::encode(view->is_length_tracking()) | JSArrayBufferViewIsBackedByRab::encode(view->is_backed_by_rab()); WriteVarint(flags); return ThrowIfOutOfMemory(); } Maybe<bool> ValueSerializer::WriteJSError(Handle<JSObject> error) { Handle<Object> stack; PropertyDescriptor message_desc; Maybe<bool> message_found = JSReceiver::GetOwnPropertyDescriptor( isolate_, error, isolate_->factory()->message_string(), &message_desc); MAYBE_RETURN(message_found, Nothing<bool>()); PropertyDescriptor cause_desc; Maybe<bool> cause_found = JSReceiver::GetOwnPropertyDescriptor( isolate_, error, isolate_->factory()->cause_string(), &cause_desc); WriteTag(SerializationTag::kError); Handle<Object> name_object; if (!JSObject::GetProperty(isolate_, error, "name").ToHandle(&name_object)) { return Nothing<bool>(); } Handle<String> name; if (!Object::ToString(isolate_, name_object).ToHandle(&name)) { return Nothing<bool>(); } if (name->IsOneByteEqualTo(base::CStrVector("EvalError"))) { WriteVarint(static_cast<uint8_t>(ErrorTag::kEvalErrorPrototype)); } else if (name->IsOneByteEqualTo(base::CStrVector("RangeError"))) { WriteVarint(static_cast<uint8_t>(ErrorTag::kRangeErrorPrototype)); } else if (name->IsOneByteEqualTo(base::CStrVector("ReferenceError"))) { WriteVarint(static_cast<uint8_t>(ErrorTag::kReferenceErrorPrototype)); } else if (name->IsOneByteEqualTo(base::CStrVector("SyntaxError"))) { WriteVarint(static_cast<uint8_t>(ErrorTag::kSyntaxErrorPrototype)); } else if (name->IsOneByteEqualTo(base::CStrVector("TypeError"))) { WriteVarint(static_cast<uint8_t>(ErrorTag::kTypeErrorPrototype)); } else if (name->IsOneByteEqualTo(base::CStrVector("URIError"))) { WriteVarint(static_cast<uint8_t>(ErrorTag::kUriErrorPrototype)); } else { // The default prototype in the deserialization side is Error.prototype, so // we don't have to do anything here. } if (message_found.FromJust() && PropertyDescriptor::IsDataDescriptor(&message_desc)) { Handle<String> message; if (!Object::ToString(isolate_, message_desc.value()).ToHandle(&message)) { return Nothing<bool>(); } WriteVarint(static_cast<uint8_t>(ErrorTag::kMessage)); WriteString(message); } if (cause_found.FromJust() && PropertyDescriptor::IsDataDescriptor(&cause_desc)) { Handle<Object> cause = cause_desc.value(); WriteVarint(static_cast<uint8_t>(ErrorTag::kCause)); if (!WriteObject(cause).FromMaybe(false)) { return Nothing<bool>(); } } if (!Object::GetProperty(isolate_, error, isolate_->factory()->stack_string()) .ToHandle(&stack)) { return Nothing<bool>(); } if (IsString(*stack)) { WriteVarint(static_cast<uint8_t>(ErrorTag::kStack)); WriteString(Handle<String>::cast(stack)); } WriteVarint(static_cast<uint8_t>(ErrorTag::kEnd)); return ThrowIfOutOfMemory(); } Maybe<bool> ValueSerializer::WriteJSSharedStruct( Handle<JSSharedStruct> shared_struct) { // TODO(v8:12547): Support copying serialization for shared structs as well. return WriteSharedObject(shared_struct); } Maybe<bool> ValueSerializer::WriteJSSharedArray( Handle<JSSharedArray> shared_array) { return WriteSharedObject(shared_array); } #if V8_ENABLE_WEBASSEMBLY Maybe<bool> ValueSerializer::WriteWasmModule(Handle<WasmModuleObject> object) { if (delegate_ == nullptr) { return ThrowDataCloneError(MessageTemplate::kDataCloneError, object); } // TODO(titzer): introduce a Utils::ToLocal for WasmModuleObject. Maybe<uint32_t> transfer_id = delegate_->GetWasmModuleTransferId( reinterpret_cast<v8::Isolate*>(isolate_), v8::Local<v8::WasmModuleObject>::Cast( Utils::ToLocal(Handle<JSObject>::cast(object)))); RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>()); uint32_t id = 0; if (transfer_id.To(&id)) { WriteTag(SerializationTag::kWasmModuleTransfer); WriteVarint<uint32_t>(id); return Just(true); } return ThrowIfOutOfMemory(); } Maybe<bool> ValueSerializer::WriteWasmMemory(Handle<WasmMemoryObject> object) { if (!object->array_buffer()->is_shared()) { return ThrowDataCloneError(MessageTemplate::kDataCloneError, object); } GlobalBackingStoreRegistry::Register( object->array_buffer()->GetBackingStore()); WriteTag(SerializationTag::kWasmMemoryTransfer); WriteZigZag<int32_t>(object->maximum_pages()); WriteByte(object->is_memory64() ? 1 : 0); return WriteJSReceiver(Handle<JSReceiver>(object->array_buffer(), isolate_)); } #endif // V8_ENABLE_WEBASSEMBLY Maybe<bool> ValueSerializer::WriteSharedObject(Handle<HeapObject> object) { if (!delegate_ || !isolate_->has_shared_space()) { return ThrowDataCloneError(MessageTemplate::kDataCloneError, object); } DCHECK(IsShared(*object)); // The first time a shared object is serialized, a new conveyor is made. This // conveyor is used for every shared object in this serialization and // subsequent deserialization sessions. The embedder owns the lifetime of the // conveyor. if (!shared_object_conveyor_) { v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); v8::SharedValueConveyor v8_conveyor(v8_isolate); shared_object_conveyor_ = v8_conveyor.private_.get(); if (!delegate_->AdoptSharedValueConveyor(v8_isolate, std::move(v8_conveyor))) { shared_object_conveyor_ = nullptr; RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>()); return Nothing<bool>(); } } WriteTag(SerializationTag::kSharedObject); WriteVarint(shared_object_conveyor_->Persist(*object)); return ThrowIfOutOfMemory(); } Maybe<bool> ValueSerializer::WriteHostObject(Handle<JSObject> object) { WriteTag(SerializationTag::kHostObject); if (!delegate_) { isolate_->Throw(*isolate_->factory()->NewError( isolate_->error_function(), MessageTemplate::kDataCloneError, object)); return Nothing<bool>(); } v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); Maybe<bool> result = delegate_->WriteHostObject(v8_isolate, Utils::ToLocal(object)); RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>()); USE(result); DCHECK(!result.IsNothing()); DCHECK(result.ToChecked()); return ThrowIfOutOfMemory(); } Maybe<uint32_t> ValueSerializer::WriteJSObjectPropertiesSlow( Handle<JSObject> object, Handle<FixedArray> keys) { uint32_t properties_written = 0; int length = keys->length(); for (int i = 0; i < length; i++) { Handle<Object> key(keys->get(i), isolate_); PropertyKey lookup_key(isolate_, key); LookupIterator it(isolate_, object, lookup_key, LookupIterator::OWN); Handle<Object> value; if (!Object::GetProperty(&it).ToHandle(&value)) return Nothing<uint32_t>(); // If the property is no longer found, do not serialize it. // This could happen if a getter deleted the property. if (!it.IsFound()) continue; if (!WriteObject(key).FromMaybe(false) || !WriteObject(value).FromMaybe(false)) { return Nothing<uint32_t>(); } properties_written++; } return Just(properties_written); } Maybe<bool> ValueSerializer::IsHostObject(Handle<JSObject> js_object) { if (!has_custom_host_objects_) { return Just<bool>( JSObject::GetEmbedderFieldCount(js_object->map(isolate_))); } DCHECK_NOT_NULL(delegate_); v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); Maybe<bool> result = delegate_->IsHostObject(v8_isolate, Utils::ToLocal(js_object)); RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>()); DCHECK(!result.IsNothing()); if (V8_UNLIKELY(out_of_memory_)) return ThrowIfOutOfMemory(); return result; } Maybe<bool> ValueSerializer::ThrowIfOutOfMemory() { if (out_of_memory_) { return ThrowDataCloneError(MessageTemplate::kDataCloneErrorOutOfMemory); } return Just(true); } Maybe<bool> ValueSerializer::ThrowDataCloneError( MessageTemplate template_index) { return ThrowDataCloneError(template_index, isolate_->factory()->empty_string()); } Maybe<bool> ValueSerializer::ThrowDataCloneError(MessageTemplate index, Handle<Object> arg0) { Handle<String> message = MessageFormatter::Format(isolate_, index, arg0); if (delegate_) { delegate_->ThrowDataCloneError(Utils::ToLocal(message)); } else { isolate_->Throw( *isolate_->factory()->NewError(isolate_->error_function(), message)); } if (isolate_->has_scheduled_exception()) { isolate_->PromoteScheduledException(); } return Nothing<bool>(); } ValueDeserializer::ValueDeserializer(Isolate* isolate, base::Vector<const uint8_t> data, v8::ValueDeserializer::Delegate* delegate) : isolate_(isolate), delegate_(delegate), position_(data.begin()), end_(data.end()), id_map_(isolate->global_handles()->Create( ReadOnlyRoots(isolate_).empty_fixed_array())) {} ValueDeserializer::ValueDeserializer(Isolate* isolate, const uint8_t* data, size_t size) : isolate_(isolate), delegate_(nullptr), position_(data), end_(data + size), id_map_(isolate->global_handles()->Create( ReadOnlyRoots(isolate_).empty_fixed_array())) {} ValueDeserializer::~ValueDeserializer() { DCHECK_LE(position_, end_); GlobalHandles::Destroy(id_map_.location()); Handle<Object> transfer_map_handle; if (array_buffer_transfer_map_.ToHandle(&transfer_map_handle)) { GlobalHandles::Destroy(transfer_map_handle.location()); } } Maybe<bool> ValueDeserializer::ReadHeader() { if (position_ < end_ && *position_ == static_cast<uint8_t>(SerializationTag::kVersion)) { ReadTag().ToChecked(); if (!ReadVarintLoop<uint32_t>().To(&version_) || version_ > kLatestVersion) { isolate_->Throw(*isolate_->factory()->NewError( MessageTemplate::kDataCloneDeserializationVersionError)); return Nothing<bool>(); } } return Just(true); } Maybe<SerializationTag> ValueDeserializer::PeekTag() const { const uint8_t* peek_position = position_; SerializationTag tag; do { if (peek_position >= end_) return Nothing<SerializationTag>(); tag = static_cast<SerializationTag>(*peek_position); peek_position++; } while (tag == SerializationTag::kPadding); return Just(tag); } void ValueDeserializer::ConsumeTag(SerializationTag peeked_tag) { SerializationTag actual_tag = ReadTag().ToChecked(); DCHECK(actual_tag == peeked_tag); USE(actual_tag); } Maybe<SerializationTag> ValueDeserializer::ReadTag() { SerializationTag tag; do { if (position_ >= end_) return Nothing<SerializationTag>(); tag = static_cast<SerializationTag>(*position_); position_++; } while (tag == SerializationTag::kPadding); return Just(tag); } template <typename T> Maybe<T> ValueDeserializer::ReadVarint() { // Reads an unsigned integer as a base-128 varint. // The number is written, 7 bits at a time, from the least significant to the // most significant 7 bits. Each byte, except the last, has the MSB set. // If the varint is larger than T, any more significant bits are discarded. // See also https://developers.google.com/protocol-buffers/docs/encoding static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value, "Only unsigned integer types can be read as varints."); if (sizeof(T) > 4) return ReadVarintLoop<T>(); auto max_read_position = position_ + sizeof(T) + 1; if (V8_UNLIKELY(max_read_position >= end_)) return ReadVarintLoop<T>(); #ifdef DEBUG // DCHECK code to make sure the manually unrolled loop yields the exact // same end state and result. auto previous_position = position_; Maybe<T> maybe_expected_value = ReadVarintLoop<T>(); // ReadVarintLoop can't return Nothing here; all such conditions have been // checked above. T expected_value = maybe_expected_value.ToChecked(); auto expected_position = position_; position_ = previous_position; #endif // DEBUG #define EXIT_DCHECK() \ DCHECK_LE(position_, end_); \ DCHECK_EQ(position_, expected_position); \ DCHECK_EQ(value, expected_value) T value = 0; #define ITERATION_SHIFTED(shift) \ if (shift < sizeof(T) * 8) { \ uint8_t byte = *position_; \ position_++; \ if (byte < 0x80) { \ value |= static_cast<T>(byte) << shift; \ EXIT_DCHECK(); \ return Just(value); \ } else { \ value |= static_cast<T>(byte & 0x7F) << shift; \ } \ } // Manually unroll the loop to achieve the best measured peformance. // This is ~15% faster than ReadVarintLoop. ITERATION_SHIFTED(0); ITERATION_SHIFTED(7); ITERATION_SHIFTED(14); ITERATION_SHIFTED(21); ITERATION_SHIFTED(28); EXIT_DCHECK(); return Just(value); #undef ITERATION_SHIFTED #undef EXIT_DCHECK } template <typename T> Maybe<T> ValueDeserializer::ReadVarintLoop() { static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value, "Only unsigned integer types can be read as varints."); T value = 0; unsigned shift = 0; bool has_another_byte; do { if (position_ >= end_) return Nothing<T>(); uint8_t byte = *position_; has_another_byte = byte & 0x80; if (V8_LIKELY(shift < sizeof(T) * 8)) { value |= static_cast<T>(byte & 0x7F) << shift; shift += 7; } else { // For consistency with the fast unrolled loop in ReadVarint we return // after we have read size(T) + 1 bytes. #ifdef V8_VALUE_DESERIALIZER_HARD_FAIL CHECK(!has_another_byte); #endif // V8_VALUE_DESERIALIZER_HARD_FAIL return Just(value); } position_++; } while (has_another_byte); return Just(value); } template <typename T> Maybe<T> ValueDeserializer::ReadZigZag() { // Writes a signed integer as a varint using ZigZag encoding (i.e. 0 is // encoded as 0, -1 as 1, 1 as 2, -2 as 3, and so on). // See also https://developers.google.com/protocol-buffers/docs/encoding static_assert(std::is_integral<T>::value && std::is_signed<T>::value, "Only signed integer types can be read as zigzag."); using UnsignedT = typename std::make_unsigned<T>::type; UnsignedT unsigned_value; if (!ReadVarint<UnsignedT>().To(&unsigned_value)) return Nothing<T>(); return Just(static_cast<T>((unsigned_value >> 1) ^ -static_cast<T>(unsigned_value & 1))); } template EXPORT_TEMPLATE_DEFINE( V8_EXPORT_PRIVATE) Maybe<int32_t> ValueDeserializer::ReadZigZag(); Maybe<double> ValueDeserializer::ReadDouble() { // Warning: this uses host endianness. if (sizeof(double) > static_cast<unsigned>(end_ - position_)) { return Nothing<double>(); } double value; memcpy(&value, position_, sizeof(double)); position_ += sizeof(double); if (std::isnan(value)) value = std::numeric_limits<double>::quiet_NaN(); return Just(value); } Maybe<base::Vector<const uint8_t>> ValueDeserializer::ReadRawBytes( size_t size) { if (size > static_cast<size_t>(end_ - position_)) { return Nothing<base::Vector<const uint8_t>>(); } const uint8_t* start = position_; position_ += size; return Just(base::Vector<const uint8_t>(start, size)); } bool ValueDeserializer::ReadByte(uint8_t* value) { if (static_cast<size_t>(end_ - position_) < sizeof(uint8_t)) return false; *value = *position_; position_++; return true; } bool ValueDeserializer::ReadUint32(uint32_t* value) { return ReadVarint<uint32_t>().To(value); } bool ValueDeserializer::ReadUint64(uint64_t* value) { return ReadVarint<uint64_t>().To(value); } bool ValueDeserializer::ReadDouble(double* value) { return ReadDouble().To(value); } bool ValueDeserializer::ReadRawBytes(size_t length, const void** data) { if (length > static_cast<size_t>(end_ - position_)) return false; *data = position_; position_ += length; return true; } void ValueDeserializer::TransferArrayBuffer( uint32_t transfer_id, Handle<JSArrayBuffer> array_buffer) { if (array_buffer_transfer_map_.is_null()) { array_buffer_transfer_map_ = isolate_->global_handles()->Create( *SimpleNumberDictionary::New(isolate_, 0)); } Handle<SimpleNumberDictionary> dictionary = array_buffer_transfer_map_.ToHandleChecked(); Handle<SimpleNumberDictionary> new_dictionary = SimpleNumberDictionary::Set( isolate_, dictionary, transfer_id, array_buffer); if (!new_dictionary.is_identical_to(dictionary)) { GlobalHandles::Destroy(dictionary.location()); array_buffer_transfer_map_ = isolate_->global_handles()->Create(*new_dictionary); } } MaybeHandle<Object> ValueDeserializer::ReadObjectWrapper() { // We had a bug which produced invalid version 13 data (see // crbug.com/1284506). This compatibility mode tries to first read the data // normally, and if it fails, and the version is 13, tries to read the broken // format. const uint8_t* original_position = position_; suppress_deserialization_errors_ = true; MaybeHandle<Object> result = ReadObject(); // The deserialization code doesn't throw errors for invalid data. It throws // errors for stack overflows, though, and in that case we won't retry. if (result.is_null() && version_ == 13 && !isolate_->has_pending_exception()) { version_13_broken_data_mode_ = true; position_ = original_position; result = ReadObject(); } if (result.is_null() && !isolate_->has_pending_exception()) { isolate_->Throw(*isolate_->factory()->NewError( MessageTemplate::kDataCloneDeserializationError)); } return result; } MaybeHandle<Object> ValueDeserializer::ReadObject() { DisallowJavascriptExecution no_js(isolate_); // If we are at the end of the stack, abort. This function may recurse. STACK_CHECK(isolate_, MaybeHandle<Object>()); MaybeHandle<Object> result = ReadObjectInternal(); // ArrayBufferView is special in that it consumes the value before it, even // after format version 0. Handle<Object> object; SerializationTag tag; if (result.ToHandle(&object) && V8_UNLIKELY(IsJSArrayBuffer(*object)) && PeekTag().To(&tag) && tag == SerializationTag::kArrayBufferView) { ConsumeTag(SerializationTag::kArrayBufferView); result = ReadJSArrayBufferView(Handle<JSArrayBuffer>::cast(object)); } if (result.is_null() && !suppress_deserialization_errors_ && !isolate_->has_pending_exception()) { isolate_->Throw(*isolate_->factory()->NewError( MessageTemplate::kDataCloneDeserializationError)); } #if defined(DEBUG) && defined(VERIFY_HEAP) if (!result.is_null() && v8_flags.enable_slow_asserts && v8_flags.verify_heap) { Object::ObjectVerify(*object, isolate_); } #endif return result; } MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() { SerializationTag tag; if (!ReadTag().To(&tag)) return MaybeHandle<Object>(); switch (tag) { case SerializationTag::kVerifyObjectCount: // Read the count and ignore it. if (ReadVarint<uint32_t>().IsNothing()) return MaybeHandle<Object>(); return ReadObject(); case SerializationTag::kUndefined: return isolate_->factory()->undefined_value(); case SerializationTag::kNull: return isolate_->factory()->null_value(); case SerializationTag::kTrue: return isolate_->factory()->true_value(); case SerializationTag::kFalse: return isolate_->factory()->false_value(); case SerializationTag::kInt32: { Maybe<int32_t> number = ReadZigZag<int32_t>(); if (number.IsNothing()) return MaybeHandle<Object>(); return isolate_->factory()->NewNumberFromInt(number.FromJust()); } case SerializationTag::kUint32: { Maybe<uint32_t> number = ReadVarint<uint32_t>(); if (number.IsNothing()) return MaybeHandle<Object>(); return isolate_->factory()->NewNumberFromUint(number.FromJust()); } case SerializationTag::kDouble: { Maybe<double> number = ReadDouble(); if (number.IsNothing()) return MaybeHandle<Object>(); return isolate_->factory()->NewNumber(number.FromJust()); } case SerializationTag::kBigInt: return ReadBigInt(); case SerializationTag::kUtf8String: return ReadUtf8String(); case SerializationTag::kOneByteString: return ReadOneByteString(); case SerializationTag::kTwoByteString: return ReadTwoByteString(); case SerializationTag::kObjectReference: { uint32_t id; if (!ReadVarint<uint32_t>().To(&id)) return MaybeHandle<Object>(); return GetObjectWithID(id); } case SerializationTag::kBeginJSObject: return ReadJSObject(); case SerializationTag::kBeginSparseJSArray: return ReadSparseJSArray(); case SerializationTag::kBeginDenseJSArray: return ReadDenseJSArray(); case SerializationTag::kDate: return ReadJSDate(); case SerializationTag::kTrueObject: case SerializationTag::kFalseObject: case SerializationTag::kNumberObject: case SerializationTag::kBigIntObject: case SerializationTag::kStringObject: return ReadJSPrimitiveWrapper(tag); case SerializationTag::kRegExp: return ReadJSRegExp(); case SerializationTag::kBeginJSMap: return ReadJSMap(); case SerializationTag::kBeginJSSet: return ReadJSSet(); case SerializationTag::kArrayBuffer: { constexpr bool is_shared = false; constexpr bool is_resizable = false; return ReadJSArrayBuffer(is_shared, is_resizable); } case SerializationTag::kResizableArrayBuffer: { constexpr bool is_shared = false; constexpr bool is_resizable = true; return ReadJSArrayBuffer(is_shared, is_resizable); } case SerializationTag::kArrayBufferTransfer: { return ReadTransferredJSArrayBuffer(); } case SerializationTag::kSharedArrayBuffer: { constexpr bool is_shared = true; constexpr bool is_resizable = false; return ReadJSArrayBuffer(is_shared, is_resizable); } case SerializationTag::kError: return ReadJSError(); #if V8_ENABLE_WEBASSEMBLY case SerializationTag::kWasmModuleTransfer: return ReadWasmModuleTransfer(); case SerializationTag::kWasmMemoryTransfer: return ReadWasmMemory(); #endif // V8_ENABLE_WEBASSEMBLY case SerializationTag::kHostObject: return ReadHostObject(); case SerializationTag::kSharedObject: if (version_ >= 15) return ReadSharedObject(); // If the data doesn't support shared values because it is from an older // version, treat the tag as unknown. V8_FALLTHROUGH; default: // Before there was an explicit tag for host objects, all unknown tags // were delegated to the host. if (version_ < 13) { position_--; return ReadHostObject(); } return MaybeHandle<Object>(); } } MaybeHandle<String> ValueDeserializer::ReadString() { if (version_ < 12) return ReadUtf8String(); Handle<Object> object; if (!ReadObject().ToHandle(&object) || !IsString(*object, isolate_)) { return MaybeHandle<String>(); } return Handle<String>::cast(object); } MaybeHandle<BigInt> ValueDeserializer::ReadBigInt() { uint32_t bitfield; if (!ReadVarint<uint32_t>().To(&bitfield)) return MaybeHandle<BigInt>(); int bytelength = BigInt::DigitsByteLengthForBitfield(bitfield); base::Vector<const uint8_t> digits_storage; if (!ReadRawBytes(bytelength).To(&digits_storage)) { return MaybeHandle<BigInt>(); } return BigInt::FromSerializedDigits(isolate_, bitfield, digits_storage); } MaybeHandle<String> ValueDeserializer::ReadUtf8String( AllocationType allocation) { uint32_t utf8_length; if (!ReadVarint<uint32_t>().To(&utf8_length)) return {}; // utf8_length is checked in ReadRawBytes. base::Vector<const uint8_t> utf8_bytes; if (!ReadRawBytes(utf8_length).To(&utf8_bytes)) return {}; return isolate_->factory()->NewStringFromUtf8( base::Vector<const char>::cast(utf8_bytes), allocation); } MaybeHandle<String> ValueDeserializer::ReadOneByteString( AllocationType allocation) { uint32_t byte_length; base::Vector<const uint8_t> bytes; if (!ReadVarint<uint32_t>().To(&byte_length)) return {}; // byte_length is checked in ReadRawBytes. if (!ReadRawBytes(byte_length).To(&bytes)) return {}; return isolate_->factory()->NewStringFromOneByte(bytes, allocation); } MaybeHandle<String> ValueDeserializer::ReadTwoByteString( AllocationType allocation) { uint32_t byte_length; base::Vector<const uint8_t> bytes; if (!ReadVarint<uint32_t>().To(&byte_length)) return {}; // byte_length is checked in ReadRawBytes. if (byte_length % sizeof(base::uc16) != 0 || !ReadRawBytes(byte_length).To(&bytes)) { return MaybeHandle<String>(); } // Allocate an uninitialized string so that we can do a raw memcpy into the // string on the heap (regardless of alignment). if (byte_length == 0) return isolate_->factory()->empty_string(); Handle<SeqTwoByteString> string; if (!isolate_->factory() ->NewRawTwoByteString(byte_length / sizeof(base::uc16), allocation) .ToHandle(&string)) { return MaybeHandle<String>(); } // Copy the bytes directly into the new string. // Warning: this uses host endianness. DisallowGarbageCollection no_gc; memcpy(string->GetChars(no_gc), bytes.begin(), bytes.length()); return string; } bool ValueDeserializer::ReadExpectedString(Handle<String> expected) { DisallowGarbageCollection no_gc; // In the case of failure, the position in the stream is reset. const uint8_t* original_position = position_; SerializationTag tag; uint32_t byte_length; base::Vector<const uint8_t> bytes; if (!ReadTag().To(&tag) || !ReadVarint<uint32_t>().To(&byte_length)) { return {}; } // Length is also checked in ReadRawBytes. #ifdef V8_VALUE_DESERIALIZER_HARD_FAIL CHECK_LE(byte_length, static_cast<uint32_t>(std::numeric_limits<int32_t>::max())); #endif // V8_VALUE_DESERIALIZER_HARD_FAIL if (!ReadRawBytes(byte_length).To(&bytes)) { position_ = original_position; return false; } String::FlatContent flat = expected->GetFlatContent(no_gc); // If the bytes are verbatim what is in the flattened string, then the string // is successfully consumed. if (tag == SerializationTag::kOneByteString && flat.IsOneByte()) { base::Vector<const uint8_t> chars = flat.ToOneByteVector(); if (byte_length == static_cast<size_t>(chars.length()) && memcmp(bytes.begin(), chars.begin(), byte_length) == 0) { return true; } } else if (tag == SerializationTag::kTwoByteString && flat.IsTwoByte()) { base::Vector<const base::uc16> chars = flat.ToUC16Vector(); if (byte_length == static_cast<unsigned>(chars.length()) * sizeof(base::uc16) && memcmp(bytes.begin(), chars.begin(), byte_length) == 0) { return true; } } else if (tag == SerializationTag::kUtf8String && flat.IsOneByte()) { base::Vector<const uint8_t> chars = flat.ToOneByteVector(); if (byte_length == static_cast<size_t>(chars.length()) && String::IsAscii(chars.begin(), chars.length()) && memcmp(bytes.begin(), chars.begin(), byte_length) == 0) { return true; } } position_ = original_position; return false; } MaybeHandle<JSObject> ValueDeserializer::ReadJSObject() { // If we are at the end of the stack, abort. This function may recurse. STACK_CHECK(isolate_, MaybeHandle<JSObject>()); uint32_t id = next_id_++; HandleScope scope(isolate_); Handle<JSObject> object = isolate_->factory()->NewJSObject(isolate_->object_function()); AddObjectWithID(id, object); uint32_t num_properties; uint32_t expected_num_properties; if (!ReadJSObjectProperties(object, SerializationTag::kEndJSObject, true) .To(&num_properties) || !ReadVarint<uint32_t>().To(&expected_num_properties) || num_properties != expected_num_properties) { return MaybeHandle<JSObject>(); } DCHECK(HasObjectWithID(id)); return scope.CloseAndEscape(object); } MaybeHandle<JSArray> ValueDeserializer::ReadSparseJSArray() { // If we are at the end of the stack, abort. This function may recurse. STACK_CHECK(isolate_, MaybeHandle<JSArray>()); uint32_t length; if (!ReadVarint<uint32_t>().To(&length)) return MaybeHandle<JSArray>(); uint32_t id = next_id_++; HandleScope scope(isolate_); Handle<JSArray> array = isolate_->factory()->NewJSArray(0, TERMINAL_FAST_ELEMENTS_KIND); MAYBE_RETURN(JSArray::SetLength(array, length), MaybeHandle<JSArray>()); AddObjectWithID(id, array); uint32_t num_properties; uint32_t expected_num_properties; uint32_t expected_length; if (!ReadJSObjectProperties(array, SerializationTag::kEndSparseJSArray, false) .To(&num_properties) || !ReadVarint<uint32_t>().To(&expected_num_properties) || !ReadVarint<uint32_t>().To(&expected_length) || num_properties != expected_num_properties || length != expected_length) { return MaybeHandle<JSArray>(); } DCHECK(HasObjectWithID(id)); return scope.CloseAndEscape(array); } MaybeHandle<JSArray> ValueDeserializer::ReadDenseJSArray() { // If we are at the end of the stack, abort. This function may recurse. STACK_CHECK(isolate_, MaybeHandle<JSArray>()); // We shouldn't permit an array larger than the biggest we can request from // V8. As an additional sanity check, since each entry will take at least one // byte to encode, if there are fewer bytes than that we can also fail fast. uint32_t length; if (!ReadVarint<uint32_t>().To(&length) || length > static_cast<uint32_t>(FixedArray::kMaxLength) || length > static_cast<size_t>(end_ - position_)) { return MaybeHandle<JSArray>(); } uint32_t id = next_id_++; HandleScope scope(isolate_); Handle<JSArray> array = isolate_->factory()->NewJSArray( HOLEY_ELEMENTS, length, length, ArrayStorageAllocationMode::INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE); AddObjectWithID(id, array); Handle<FixedArray> elements(FixedArray::cast(array->elements()), isolate_); auto elements_length = static_cast<uint32_t>(elements->length()); for (uint32_t i = 0; i < length; i++) { SerializationTag tag; if (PeekTag().To(&tag) && tag == SerializationTag::kTheHole) { ConsumeTag(SerializationTag::kTheHole); continue; } Handle<Object> element; if (!ReadObject().ToHandle(&element)) return MaybeHandle<JSArray>(); // Serialization versions less than 11 encode the hole the same as // undefined. For consistency with previous behavior, store these as the // hole. Past version 11, undefined means undefined. if (version_ < 11 && IsUndefined(*element, isolate_)) continue; // Safety check. if (i >= elements_length) return MaybeHandle<JSArray>(); elements->set(i, *element); } uint32_t num_properties; uint32_t expected_num_properties; uint32_t expected_length; if (!ReadJSObjectProperties(array, SerializationTag::kEndDenseJSArray, false) .To(&num_properties) || !ReadVarint<uint32_t>().To(&expected_num_properties) || !ReadVarint<uint32_t>().To(&expected_length) || num_properties != expected_num_properties || length != expected_length) { return MaybeHandle<JSArray>(); } DCHECK(HasObjectWithID(id)); return scope.CloseAndEscape(array); } MaybeHandle<JSDate> ValueDeserializer::ReadJSDate() { double value; if (!ReadDouble().To(&value)) return MaybeHandle<JSDate>(); uint32_t id = next_id_++; Handle<JSDate> date; if (!JSDate::New(isolate_->date_function(), isolate_->date_function(), value) .ToHandle(&date)) { return MaybeHandle<JSDate>(); } AddObjectWithID(id, date); return date; } MaybeHandle<JSPrimitiveWrapper> ValueDeserializer::ReadJSPrimitiveWrapper( SerializationTag tag) { uint32_t id = next_id_++; Handle<JSPrimitiveWrapper> value; switch (tag) { case SerializationTag::kTrueObject: value = Handle<JSPrimitiveWrapper>::cast( isolate_->factory()->NewJSObject(isolate_->boolean_function())); value->set_value(ReadOnlyRoots(isolate_).true_value()); break; case SerializationTag::kFalseObject: value = Handle<JSPrimitiveWrapper>::cast( isolate_->factory()->NewJSObject(isolate_->boolean_function())); value->set_value(ReadOnlyRoots(isolate_).false_value()); break; case SerializationTag::kNumberObject: { double number; if (!ReadDouble().To(&number)) return MaybeHandle<JSPrimitiveWrapper>(); value = Handle<JSPrimitiveWrapper>::cast( isolate_->factory()->NewJSObject(isolate_->number_function())); Handle<Object> number_object = isolate_->factory()->NewNumber(number); value->set_value(*number_object); break; } case SerializationTag::kBigIntObject: { Handle<BigInt> bigint; if (!ReadBigInt().ToHandle(&bigint)) return MaybeHandle<JSPrimitiveWrapper>(); value = Handle<JSPrimitiveWrapper>::cast( isolate_->factory()->NewJSObject(isolate_->bigint_function())); value->set_value(*bigint); break; } case SerializationTag::kStringObject: { Handle<String> string; if (!ReadString().ToHandle(&string)) return MaybeHandle<JSPrimitiveWrapper>(); value = Handle<JSPrimitiveWrapper>::cast( isolate_->factory()->NewJSObject(isolate_->string_function())); value->set_value(*string); break; } default: UNREACHABLE(); } AddObjectWithID(id, value); return value; } MaybeHandle<JSRegExp> ValueDeserializer::ReadJSRegExp() { uint32_t id = next_id_++; Handle<String> pattern; uint32_t raw_flags; Handle<JSRegExp> regexp; if (!ReadString().ToHandle(&pattern) || !ReadVarint<uint32_t>().To(&raw_flags)) { return MaybeHandle<JSRegExp>(); } // Ensure the deserialized flags are valid. uint32_t bad_flags_mask = static_cast<uint32_t>(-1) << JSRegExp::kFlagCount; // kLinear is accepted only with the appropriate flag. if (!v8_flags.enable_experimental_regexp_engine) { bad_flags_mask |= JSRegExp::kLinear; } if ((raw_flags & bad_flags_mask) || !JSRegExp::New(isolate_, pattern, static_cast<JSRegExp::Flags>(raw_flags)) .ToHandle(®exp)) { return MaybeHandle<JSRegExp>(); } AddObjectWithID(id, regexp); return regexp; } MaybeHandle<JSMap> ValueDeserializer::ReadJSMap() { // If we are at the end of the stack, abort. This function may recurse. STACK_CHECK(isolate_, MaybeHandle<JSMap>()); HandleScope scope(isolate_); uint32_t id = next_id_++; Handle<JSMap> map = isolate_->factory()->NewJSMap(); AddObjectWithID(id, map); Handle<JSFunction> map_set = isolate_->map_set(); uint32_t length = 0; while (true) { SerializationTag tag; if (!PeekTag().To(&tag)) return MaybeHandle<JSMap>(); if (tag == SerializationTag::kEndJSMap) { ConsumeTag(SerializationTag::kEndJSMap); break; } Handle<Object> argv[2]; if (!ReadObject().ToHandle(&argv[0]) || !ReadObject().ToHandle(&argv[1])) { return MaybeHandle<JSMap>(); } AllowJavascriptExecution allow_js(isolate_); if (Execution::Call(isolate_, map_set, map, arraysize(argv), argv) .is_null()) { return MaybeHandle<JSMap>(); } length += 2; } uint32_t expected_length; if (!ReadVarint<uint32_t>().To(&expected_length) || length != expected_length) { return MaybeHandle<JSMap>(); } DCHECK(HasObjectWithID(id)); return scope.CloseAndEscape(map); } MaybeHandle<JSSet> ValueDeserializer::ReadJSSet() { // If we are at the end of the stack, abort. This function may recurse. STACK_CHECK(isolate_, MaybeHandle<JSSet>()); HandleScope scope(isolate_); uint32_t id = next_id_++; Handle<JSSet> set = isolate_->factory()->NewJSSet(); AddObjectWithID(id, set); Handle<JSFunction> set_add = isolate_->set_add(); uint32_t length = 0; while (true) { SerializationTag tag; if (!PeekTag().To(&tag)) return MaybeHandle<JSSet>(); if (tag == SerializationTag::kEndJSSet) { ConsumeTag(SerializationTag::kEndJSSet); break; } Handle<Object> argv[1]; if (!ReadObject().ToHandle(&argv[0])) return MaybeHandle<JSSet>(); AllowJavascriptExecution allow_js(isolate_); if (Execution::Call(isolate_, set_add, set, arraysize(argv), argv) .is_null()) { return MaybeHandle<JSSet>(); } length++; } uint32_t expected_length; if (!ReadVarint<uint32_t>().To(&expected_length) || length != expected_length) { return MaybeHandle<JSSet>(); } DCHECK(HasObjectWithID(id)); return scope.CloseAndEscape(set); } MaybeHandle<JSArrayBuffer> ValueDeserializer::ReadJSArrayBuffer( bool is_shared, bool is_resizable) { uint32_t id = next_id_++; if (is_shared) { uint32_t clone_id; Local<SharedArrayBuffer> sab_value; if (!ReadVarint<uint32_t>().To(&clone_id) || delegate_ == nullptr || !delegate_ ->GetSharedArrayBufferFromId( reinterpret_cast<v8::Isolate*>(isolate_), clone_id) .ToLocal(&sab_value)) { RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, JSArrayBuffer); return MaybeHandle<JSArrayBuffer>(); } Handle<JSArrayBuffer> array_buffer = Utils::OpenHandle(*sab_value); DCHECK_EQ(is_shared, array_buffer->is_shared()); AddObjectWithID(id, array_buffer); return array_buffer; } uint32_t byte_length; if (!ReadVarint<uint32_t>().To(&byte_length)) { return MaybeHandle<JSArrayBuffer>(); } uint32_t max_byte_length = byte_length; if (is_resizable) { if (!ReadVarint<uint32_t>().To(&max_byte_length)) { return MaybeHandle<JSArrayBuffer>(); } if (byte_length > max_byte_length) { return MaybeHandle<JSArrayBuffer>(); } if (!v8_flags.harmony_rab_gsab) { // Disable resizability. This ensures that no resizable buffers are // created in a version which has the harmony_rab_gsab turned off, even if // such a version is reading data containing resizable buffers from disk. is_resizable = false; max_byte_length = byte_length; } } if (byte_length > static_cast<size_t>(end_ - position_)) { return MaybeHandle<JSArrayBuffer>(); } MaybeHandle<JSArrayBuffer> result = isolate_->factory()->NewJSArrayBufferAndBackingStore( byte_length, max_byte_length, InitializedFlag::kUninitialized, is_resizable ? ResizableFlag::kResizable : ResizableFlag::kNotResizable); Handle<JSArrayBuffer> array_buffer; if (!result.ToHandle(&array_buffer)) return result; if (byte_length > 0) { memcpy(array_buffer->backing_store(), position_, byte_length); } position_ += byte_length; AddObjectWithID(id, array_buffer); return array_buffer; } MaybeHandle<JSArrayBuffer> ValueDeserializer::ReadTransferredJSArrayBuffer() { uint32_t id = next_id_++; uint32_t transfer_id; Handle<SimpleNumberDictionary> transfer_map; if (!ReadVarint<uint32_t>().To(&transfer_id) || !array_buffer_transfer_map_.ToHandle(&transfer_map)) { return MaybeHandle<JSArrayBuffer>(); } InternalIndex index = transfer_map->FindEntry(isolate_, transfer_id); if (index.is_not_found()) { return MaybeHandle<JSArrayBuffer>(); } Handle<JSArrayBuffer> array_buffer( JSArrayBuffer::cast(transfer_map->ValueAt(index)), isolate_); AddObjectWithID(id, array_buffer); return array_buffer; } MaybeHandle<JSArrayBufferView> ValueDeserializer::ReadJSArrayBufferView( Handle<JSArrayBuffer> buffer) { uint32_t buffer_byte_length = static_cast<uint32_t>(buffer->GetByteLength()); uint8_t tag = 0; uint32_t byte_offset = 0; uint32_t byte_length = 0; uint32_t flags = 0; if (!ReadVarint<uint8_t>().To(&tag) || !ReadVarint<uint32_t>().To(&byte_offset) || !ReadVarint<uint32_t>().To(&byte_length) || byte_offset > buffer_byte_length || byte_length > buffer_byte_length - byte_offset) { return MaybeHandle<JSArrayBufferView>(); } const bool should_read_flags = version_ >= 14 || version_13_broken_data_mode_; if (should_read_flags && !ReadVarint<uint32_t>().To(&flags)) { return MaybeHandle<JSArrayBufferView>(); } uint32_t id = next_id_++; ExternalArrayType external_array_type = kExternalInt8Array; unsigned element_size = 0; switch (static_cast<ArrayBufferViewTag>(tag)) { case ArrayBufferViewTag::kDataView: { bool is_length_tracking = false; bool is_backed_by_rab = false; if (!ValidateJSArrayBufferViewFlags(*buffer, flags, is_length_tracking, is_backed_by_rab)) { return MaybeHandle<JSArrayBufferView>(); } Handle<JSDataViewOrRabGsabDataView> data_view = isolate_->factory()->NewJSDataViewOrRabGsabDataView( buffer, byte_offset, byte_length, is_length_tracking); CHECK_EQ(is_backed_by_rab, data_view->is_backed_by_rab()); CHECK_EQ(is_length_tracking, data_view->is_length_tracking()); AddObjectWithID(id, data_view); return data_view; } #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ case ArrayBufferViewTag::k##Type##Array: \ external_array_type = kExternal##Type##Array; \ element_size = sizeof(ctype); \ break; TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE } if (element_size == 0 || byte_offset % element_size != 0 || byte_length % element_size != 0) { return MaybeHandle<JSArrayBufferView>(); } bool is_length_tracking = false; bool is_backed_by_rab = false; if (!ValidateJSArrayBufferViewFlags(*buffer, flags, is_length_tracking, is_backed_by_rab)) { return MaybeHandle<JSArrayBufferView>(); } Handle<JSTypedArray> typed_array = isolate_->factory()->NewJSTypedArray( external_array_type, buffer, byte_offset, byte_length / element_size, is_length_tracking); CHECK_EQ(is_length_tracking, typed_array->is_length_tracking()); CHECK_EQ(is_backed_by_rab, typed_array->is_backed_by_rab()); AddObjectWithID(id, typed_array); return typed_array; } bool ValueDeserializer::ValidateJSArrayBufferViewFlags( Tagged<JSArrayBuffer> buffer, uint32_t serialized_flags, bool& is_length_tracking, bool& is_backed_by_rab) { is_length_tracking = JSArrayBufferViewIsLengthTracking::decode(serialized_flags); is_backed_by_rab = JSArrayBufferViewIsBackedByRab::decode(serialized_flags); // TODO(marja): When the version number is bumped the next time, check that // serialized_flags doesn't contain spurious 1-bits. if (!v8_flags.harmony_rab_gsab) { // Disable resizability. This ensures that no resizable buffers are // created in a version which has the harmony_rab_gsab turned off, even if // such a version is reading data containing resizable buffers from disk. is_length_tracking = false; is_backed_by_rab = false; // The resizability of the buffer was already disabled. CHECK(!buffer->is_resizable_by_js()); } if (is_backed_by_rab || is_length_tracking) { if (!buffer->is_resizable_by_js()) { return false; } if (is_backed_by_rab && buffer->is_shared()) { return false; } } // The RAB-ness of the buffer and the TA's "is_backed_by_rab" need to be in // sync. if (buffer->is_resizable_by_js() && !buffer->is_shared() && !is_backed_by_rab) { return false; } return true; } MaybeHandle<Object> ValueDeserializer::ReadJSError() { uint32_t id = next_id_++; Handle<Object> message = isolate_->factory()->undefined_value(); Handle<Object> options = isolate_->factory()->undefined_value(); Handle<Object> stack = isolate_->factory()->undefined_value(); Handle<Object> no_caller; auto constructor = isolate_->error_function(); bool done = false; while (!done) { uint8_t tag; if (!ReadVarint<uint8_t>().To(&tag)) { return MaybeHandle<JSObject>(); } switch (static_cast<ErrorTag>(tag)) { case ErrorTag::kEvalErrorPrototype: constructor = isolate_->eval_error_function(); break; case ErrorTag::kRangeErrorPrototype: constructor = isolate_->range_error_function(); break; case ErrorTag::kReferenceErrorPrototype: constructor = isolate_->reference_error_function(); break; case ErrorTag::kSyntaxErrorPrototype: constructor = isolate_->syntax_error_function(); break; case ErrorTag::kTypeErrorPrototype: constructor = isolate_->type_error_function(); break; case ErrorTag::kUriErrorPrototype: constructor = isolate_->uri_error_function(); break; case ErrorTag::kMessage: { Handle<String> message_string; if (!ReadString().ToHandle(&message_string)) { return MaybeHandle<JSObject>(); } message = message_string; break; } case ErrorTag::kCause: { Handle<Object> cause; if (!ReadObject().ToHandle(&cause)) { return MaybeHandle<JSObject>(); } options = isolate_->factory()->NewJSObject(isolate_->object_function()); if (JSObject::DefinePropertyOrElementIgnoreAttributes( Handle<JSObject>::cast(options), isolate_->factory()->cause_string(), cause, DONT_ENUM) .is_null()) { return MaybeHandle<JSObject>(); } break; } case ErrorTag::kStack: { Handle<String> stack_string; if (!ReadString().ToHandle(&stack_string)) { return MaybeHandle<JSObject>(); } stack = stack_string; break; } case ErrorTag::kEnd: done = true; break; default: return MaybeHandle<JSObject>(); } } Handle<JSObject> error; if (!ErrorUtils::Construct(isolate_, constructor, constructor, message, options, SKIP_NONE, no_caller, ErrorUtils::StackTraceCollection::kDisabled) .ToHandle(&error)) { return MaybeHandle<Object>(); } ErrorUtils::SetFormattedStack(isolate_, error, stack); AddObjectWithID(id, error); return error; } #if V8_ENABLE_WEBASSEMBLY MaybeHandle<JSObject> ValueDeserializer::ReadWasmModuleTransfer() { uint32_t transfer_id = 0; Local<Value> module_value; if (!ReadVarint<uint32_t>().To(&transfer_id) || delegate_ == nullptr || !delegate_ ->GetWasmModuleFromId(reinterpret_cast<v8::Isolate*>(isolate_), transfer_id) .ToLocal(&module_value)) { RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, JSObject); return MaybeHandle<JSObject>(); } uint32_t id = next_id_++; Handle<JSObject> module = Handle<JSObject>::cast(Utils::OpenHandle(*module_value)); AddObjectWithID(id, module); return module; } MaybeHandle<WasmMemoryObject> ValueDeserializer::ReadWasmMemory() { uint32_t id = next_id_++; int32_t maximum_pages; if (!ReadZigZag<int32_t>().To(&maximum_pages)) return {}; uint8_t memory64_byte; if (!ReadByte(&memory64_byte)) return {}; if (memory64_byte > 1) return {}; bool is_memory64 = memory64_byte; Handle<Object> buffer_object; if (!ReadObject().ToHandle(&buffer_object)) return {}; if (!IsJSArrayBuffer(*buffer_object)) return {}; Handle<JSArrayBuffer> buffer = Handle<JSArrayBuffer>::cast(buffer_object); if (!buffer->is_shared()) return {}; Handle<WasmMemoryObject> result = WasmMemoryObject::New(isolate_, buffer, maximum_pages, is_memory64 ? WasmMemoryFlag::kWasmMemory64 : WasmMemoryFlag::kWasmMemory32); AddObjectWithID(id, result); return result; } #endif // V8_ENABLE_WEBASSEMBLY namespace { // Throws a generic "deserialization failed" exception by default, unless a more // specific exception has already been thrown. void ThrowDeserializationExceptionIfNonePending(Isolate* isolate) { if (!isolate->has_pending_exception()) { isolate->Throw(*isolate->factory()->NewError( MessageTemplate::kDataCloneDeserializationError)); } DCHECK(isolate->has_pending_exception()); } } // namespace MaybeHandle<HeapObject> ValueDeserializer::ReadSharedObject() { STACK_CHECK(isolate_, MaybeHandle<HeapObject>()); DCHECK_GE(version_, 15); uint32_t shared_object_id; if (!ReadVarint<uint32_t>().To(&shared_object_id)) { RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, HeapObject); return MaybeHandle<HeapObject>(); } if (!delegate_) { ThrowDeserializationExceptionIfNonePending(isolate_); return MaybeHandle<HeapObject>(); } if (shared_object_conveyor_ == nullptr) { const v8::SharedValueConveyor* conveyor = delegate_->GetSharedValueConveyor( reinterpret_cast<v8::Isolate*>(isolate_)); if (!conveyor) { RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, HeapObject); return MaybeHandle<HeapObject>(); } shared_object_conveyor_ = conveyor->private_.get(); } Handle<HeapObject> shared_object( shared_object_conveyor_->GetPersisted(shared_object_id), isolate_); DCHECK(IsShared(*shared_object)); return shared_object; } MaybeHandle<JSObject> ValueDeserializer::ReadHostObject() { if (!delegate_) return MaybeHandle<JSObject>(); STACK_CHECK(isolate_, MaybeHandle<JSObject>()); uint32_t id = next_id_++; v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_); v8::Local<v8::Object> object; if (!delegate_->ReadHostObject(v8_isolate).ToLocal(&object)) { RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, JSObject); return MaybeHandle<JSObject>(); } Handle<JSObject> js_object = Handle<JSObject>::cast(Utils::OpenHandle(*object)); AddObjectWithID(id, js_object); return js_object; } // Copies a vector of property values into an object, given the map that should // be used. static void CommitProperties(Handle<JSObject> object, Handle<Map> map, const std::vector<Handle<Object>>& properties) { JSObject::AllocateStorageForMap(object, map); DCHECK(!object->map()->is_dictionary_map()); DisallowGarbageCollection no_gc; Tagged<DescriptorArray> descriptors = object->map()->instance_descriptors(); for (InternalIndex i : InternalIndex::Range(properties.size())) { // Initializing store. object->WriteToField(i, descriptors->GetDetails(i), *properties[i.raw_value()]); } } static bool IsValidObjectKey(Tagged<Object> value, Isolate* isolate) { if (IsSmi(value)) return true; auto instance_type = HeapObject::cast(value)->map(isolate)->instance_type(); return InstanceTypeChecker::IsName(instance_type) || InstanceTypeChecker::IsHeapNumber(instance_type); } Maybe<uint32_t> ValueDeserializer::ReadJSObjectProperties( Handle<JSObject> object, SerializationTag end_tag, bool can_use_transitions) { uint32_t num_properties = 0; // Fast path (following map transitions). if (can_use_transitions) { bool transitioning = true; Handle<Map> map(object->map(), isolate_); DCHECK(!map->is_dictionary_map()); DCHECK_EQ(0, map->instance_descriptors(isolate_)->number_of_descriptors()); std::vector<Handle<Object>> properties; properties.reserve(8); while (transitioning) { // If there are no more properties, finish. SerializationTag tag; if (!PeekTag().To(&tag)) return Nothing<uint32_t>(); if (tag == end_tag) { ConsumeTag(end_tag); CommitProperties(object, map, properties); CHECK_LT(properties.size(), std::numeric_limits<uint32_t>::max()); return Just(static_cast<uint32_t>(properties.size())); } // Determine the key to be used and the target map to transition to, if // possible. Transitioning may abort if the key is not a string, or if no // transition was found. Handle<Object> key; Handle<Map> target; Handle<String> expected_key; { TransitionsAccessor transitions(isolate_, *map); expected_key = transitions.ExpectedTransitionKey(); if (!expected_key.is_null()) { target = transitions.ExpectedTransitionTarget(); } } if (!expected_key.is_null() && ReadExpectedString(expected_key)) { key = expected_key; } else { if (!ReadObject().ToHandle(&key) || !IsValidObjectKey(*key, isolate_)) { return Nothing<uint32_t>(); } if (IsString(*key, isolate_)) { key = isolate_->factory()->InternalizeString(Handle<String>::cast(key)); // Don't reuse |transitions| because it could be stale. transitioning = TransitionsAccessor(isolate_, *map) .FindTransitionToField(Handle<String>::cast(key)) .ToHandle(&target); } else { transitioning = false; } } // Read the value that corresponds to it. Handle<Object> value; if (!ReadObject().ToHandle(&value)) return Nothing<uint32_t>(); // If still transitioning and the value fits the field representation // (though generalization may be required), store the property value so // that we can copy them all at once. Otherwise, stop transitioning. if (transitioning) { // Deserializaton of |value| might have deprecated current |target|, // ensure we are working with the up-to-date version. target = Map::Update(isolate_, target); if (!target->is_dictionary_map()) { InternalIndex descriptor(properties.size()); PropertyDetails details = target->instance_descriptors(isolate_)->GetDetails(descriptor); Representation expected_representation = details.representation(); if (Object::FitsRepresentation(*value, expected_representation)) { if (expected_representation.IsHeapObject() && !FieldType::NowContains( target->instance_descriptors(isolate_)->GetFieldType( descriptor), value)) { Handle<FieldType> value_type = Object::OptimalType( *value, isolate_, expected_representation); MapUpdater::GeneralizeField(isolate_, target, descriptor, details.constness(), expected_representation, value_type); } DCHECK(FieldType::NowContains( target->instance_descriptors(isolate_)->GetFieldType( descriptor), value)); properties.push_back(value); map = target; continue; } } transitioning = false; } // Fell out of transitioning fast path. Commit the properties gathered so // far, and then start setting properties slowly instead. DCHECK(!transitioning); CHECK_LT(properties.size(), std::numeric_limits<uint32_t>::max()); CHECK(!map->is_dictionary_map()); CommitProperties(object, map, properties); num_properties = static_cast<uint32_t>(properties.size()); // We checked earlier that IsValidObjectKey(key). PropertyKey lookup_key(isolate_, key); LookupIterator it(isolate_, object, lookup_key, LookupIterator::OWN); if (it.state() != LookupIterator::NOT_FOUND || JSObject::DefineOwnPropertyIgnoreAttributes(&it, value, NONE) .is_null()) { return Nothing<uint32_t>(); } num_properties++; } // At this point, transitioning should be done, but at least one property // should have been written (in the zero-property case, there is an early // return). DCHECK(!transitioning); DCHECK_GE(num_properties, 1u); } // Slow path. for (;; num_properties++) { SerializationTag tag; if (!PeekTag().To(&tag)) return Nothing<uint32_t>(); if (tag == end_tag) { ConsumeTag(end_tag); return Just(num_properties); } Handle<Object> key; if (!ReadObject().ToHandle(&key) || !IsValidObjectKey(*key, isolate_)) { return Nothing<uint32_t>(); } Handle<Object> value; if (!ReadObject().ToHandle(&value)) return Nothing<uint32_t>(); // We checked earlier that IsValidObjectKey(key). PropertyKey lookup_key(isolate_, key); LookupIterator it(isolate_, object, lookup_key, LookupIterator::OWN); if (it.state() != LookupIterator::NOT_FOUND || JSObject::DefineOwnPropertyIgnoreAttributes(&it, value, NONE) .is_null()) { return Nothing<uint32_t>(); } } } bool ValueDeserializer::HasObjectWithID(uint32_t id) { return id < static_cast<unsigned>(id_map_->length()) && !IsTheHole(id_map_->get(id), isolate_); } MaybeHandle<JSReceiver> ValueDeserializer::GetObjectWithID(uint32_t id) { if (id >= static_cast<unsigned>(id_map_->length())) { return MaybeHandle<JSReceiver>(); } Tagged<Object> value = id_map_->get(id); if (IsTheHole(value, isolate_)) return MaybeHandle<JSReceiver>(); DCHECK(IsJSReceiver(value)); return Handle<JSReceiver>(JSReceiver::cast(value), isolate_); } void ValueDeserializer::AddObjectWithID(uint32_t id, Handle<JSReceiver> object) { DCHECK(!HasObjectWithID(id)); Handle<FixedArray> new_array = FixedArray::SetAndGrow(isolate_, id_map_, id, object); // If the dictionary was reallocated, update the global handle. if (!new_array.is_identical_to(id_map_)) { GlobalHandles::Destroy(id_map_.location()); id_map_ = isolate_->global_handles()->Create(*new_array); } } static Maybe<bool> SetPropertiesFromKeyValuePairs(Isolate* isolate, Handle<JSObject> object, Handle<Object>* data, uint32_t num_properties) { for (unsigned i = 0; i < 2 * num_properties; i += 2) { Handle<Object> key = data[i]; if (!IsValidObjectKey(*key, isolate)) return Nothing<bool>(); Handle<Object> value = data[i + 1]; PropertyKey lookup_key(isolate, key); LookupIterator it(isolate, object, lookup_key, LookupIterator::OWN); if (it.state() != LookupIterator::NOT_FOUND || JSObject::DefineOwnPropertyIgnoreAttributes(&it, value, NONE) .is_null()) { return Nothing<bool>(); } } return Just(true); } MaybeHandle<Object> ValueDeserializer::ReadObjectUsingEntireBufferForLegacyFormat() { DCHECK_EQ(version_, 0u); HandleScope scope(isolate_); std::vector<Handle<Object>> stack; while (position_ < end_) { SerializationTag tag; if (!PeekTag().To(&tag)) break; Handle<Object> new_object; switch (tag) { case SerializationTag::kEndJSObject: { ConsumeTag(SerializationTag::kEndJSObject); // JS Object: Read the last 2*n values from the stack and use them as // key-value pairs. uint32_t num_properties; if (!ReadVarint<uint32_t>().To(&num_properties) || stack.size() / 2 < num_properties) { isolate_->Throw(*isolate_->factory()->NewError( MessageTemplate::kDataCloneDeserializationError)); return MaybeHandle<Object>(); } size_t begin_properties = stack.size() - 2 * static_cast<size_t>(num_properties); Handle<JSObject> js_object = isolate_->factory()->NewJSObject(isolate_->object_function()); if (num_properties && !SetPropertiesFromKeyValuePairs( isolate_, js_object, &stack[begin_properties], num_properties) .FromMaybe(false)) { ThrowDeserializationExceptionIfNonePending(isolate_); return MaybeHandle<Object>(); } stack.resize(begin_properties); new_object = js_object; break; } case SerializationTag::kEndSparseJSArray: { ConsumeTag(SerializationTag::kEndSparseJSArray); // Sparse JS Array: Read the last 2*|num_properties| from the stack. uint32_t num_properties; uint32_t length; if (!ReadVarint<uint32_t>().To(&num_properties) || !ReadVarint<uint32_t>().To(&length) || stack.size() / 2 < num_properties) { isolate_->Throw(*isolate_->factory()->NewError( MessageTemplate::kDataCloneDeserializationError)); return MaybeHandle<Object>(); } Handle<JSArray> js_array = isolate_->factory()->NewJSArray(0, TERMINAL_FAST_ELEMENTS_KIND); MAYBE_RETURN_NULL(JSArray::SetLength(js_array, length)); size_t begin_properties = stack.size() - 2 * static_cast<size_t>(num_properties); if (num_properties && !SetPropertiesFromKeyValuePairs( isolate_, js_array, &stack[begin_properties], num_properties) .FromMaybe(false)) { ThrowDeserializationExceptionIfNonePending(isolate_); return MaybeHandle<Object>(); } stack.resize(begin_properties); new_object = js_array; break; } case SerializationTag::kEndDenseJSArray: { // This was already broken in Chromium, and apparently wasn't missed. isolate_->Throw(*isolate_->factory()->NewError( MessageTemplate::kDataCloneDeserializationError)); return MaybeHandle<Object>(); } default: if (!ReadObject().ToHandle(&new_object)) return MaybeHandle<Object>(); break; } stack.push_back(new_object); } // Nothing remains but padding. #ifdef DEBUG while (position_ < end_) { DCHECK(*position_++ == static_cast<uint8_t>(SerializationTag::kPadding)); } #endif position_ = end_; if (stack.size() != 1) { isolate_->Throw(*isolate_->factory()->NewError( MessageTemplate::kDataCloneDeserializationError)); return MaybeHandle<Object>(); } return scope.CloseAndEscape(stack[0]); } } // namespace internal } // namespace v8