// Copyright 2021 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/web-snapshot/web-snapshot.h" #include #include "include/v8-isolate.h" #include "include/v8-local-handle.h" #include "include/v8-object.h" #include "include/v8-primitive.h" #include "include/v8-script.h" #include "src/api/api-inl.h" #include "src/base/platform/wrappers.h" #include "src/handles/handles.h" #include "src/logging/runtime-call-stats-scope.h" #include "src/objects/contexts.h" #include "src/objects/js-regexp-inl.h" #include "src/objects/script.h" namespace v8 { namespace internal { constexpr uint8_t WebSnapshotSerializerDeserializer::kMagicNumber[4]; // When encountering an error during deserializing, we note down the error but // don't bail out from processing the snapshot further. This is to speed up // deserialization; the error case is now slower since we don't bail out, but // the non-error case is faster, since we don't repeatedly check for errors. // (Invariant: we might fill our internal data structures with arbitrary data, // but it shouldn't have an observable effect.) // This doesn't increase the complexity of processing the data in a robust and // secure way. We cannot trust the data anyway, so every upcoming byte can have // an arbitrary value, not depending on whether or not we've encountered an // error before. void WebSnapshotSerializerDeserializer::Throw(const char* message) { if (error_message_ != nullptr) { return; } error_message_ = message; if (!isolate_->has_pending_exception()) { isolate_->Throw(*factory()->NewError( MessageTemplate::kWebSnapshotError, factory()->NewStringFromAsciiChecked(error_message_))); } } uint32_t WebSnapshotSerializerDeserializer::FunctionKindToFunctionFlags( FunctionKind kind) { // TODO(v8:11525): Support more function kinds. switch (kind) { case FunctionKind::kNormalFunction: case FunctionKind::kArrowFunction: case FunctionKind::kGeneratorFunction: case FunctionKind::kAsyncFunction: case FunctionKind::kAsyncArrowFunction: case FunctionKind::kAsyncGeneratorFunction: case FunctionKind::kBaseConstructor: case FunctionKind::kDefaultBaseConstructor: case FunctionKind::kConciseMethod: case FunctionKind::kAsyncConciseMethod: break; default: Throw("Unsupported function kind"); } auto flags = AsyncFunctionBitField::encode(IsAsyncFunction(kind)) | GeneratorFunctionBitField::encode(IsGeneratorFunction(kind)) | ArrowFunctionBitField::encode(IsArrowFunction(kind)) | MethodBitField::encode(IsConciseMethod(kind)) | StaticBitField::encode(IsStatic(kind)) | ClassConstructorBitField::encode(IsClassConstructor(kind)) | DefaultConstructorBitField::encode(IsDefaultConstructor(kind)) | DerivedConstructorBitField::encode(IsDerivedConstructor(kind)); return flags; } // TODO(v8:11525): Optionally, use an enum instead. FunctionKind WebSnapshotSerializerDeserializer::FunctionFlagsToFunctionKind( uint32_t flags) { FunctionKind kind; if (IsFunctionOrMethod(flags)) { if (ArrowFunctionBitField::decode(flags) && MethodBitField::decode(flags)) { kind = FunctionKind::kInvalid; } else { uint32_t index = AsyncFunctionBitField::decode(flags) << 0 | GeneratorFunctionBitField::decode(flags) << 1 | (ArrowFunctionBitField::decode(flags) || StaticBitField::decode(flags)) << 2 | MethodBitField::decode(flags) << 3; static const FunctionKind kFunctionKinds[] = { // kNormalFunction // is_generator = false FunctionKind::kNormalFunction, // is_async = false FunctionKind::kAsyncFunction, // is_async = true // is_generator = true FunctionKind::kGeneratorFunction, // is_async = false FunctionKind::kAsyncGeneratorFunction, // is_async = true // kArrowFunction // is_generator = false FunctionKind::kArrowFunction, // is_async = false FunctionKind::kAsyncArrowFunction, // is_async = true // is_generator = true FunctionKind::kInvalid, // is_async = false FunctionKind::kInvalid, // is_async = true // kNonStaticMethod // is_generator = false FunctionKind::kConciseMethod, // is_async = false FunctionKind::kAsyncConciseMethod, // is_async = true // is_generator = true // TODO(v8::11525) Support FunctionKind::kConciseGeneratorMethod. FunctionKind::kInvalid, // is_async = false // TODO(v8::11525) Support FunctionKind::kAsyncConciseGeneratorMethod. FunctionKind::kInvalid, // is_async = true // kStaticMethod // is_generator = false // TODO(v8::11525) Support FunctionKind::kStaticConciseMethod. FunctionKind::kInvalid, // is_async = false // TODO(v8::11525) Support FunctionKind::kStaticAsyncConciseMethod. FunctionKind::kInvalid, // is_async = true // is_generator = true // TODO(v8::11525) Support // FunctionKind::kStaticConciseGeneratorMethod. FunctionKind::kInvalid, // is_async = false // TODO(v8::11525) Support // FunctionKind::kStaticAsyncConciseGeneratorMethod. FunctionKind::kInvalid // is_async = true }; kind = kFunctionKinds[index]; } } else if (IsConstructor(flags)) { static const FunctionKind kFunctionKinds[] = { // is_derived = false FunctionKind::kBaseConstructor, // is_default = false FunctionKind::kDefaultBaseConstructor, // is_default = true // is_derived = true FunctionKind::kDerivedConstructor, // is_default = false FunctionKind::kDefaultDerivedConstructor // is_default = true }; kind = kFunctionKinds[flags >> DefaultConstructorBitField::kShift]; } else { kind = FunctionKind::kInvalid; } if (kind == FunctionKind::kInvalid) { Throw("Invalid function flags\n"); } return kind; } bool WebSnapshotSerializerDeserializer::IsFunctionOrMethod(uint32_t flags) { uint32_t mask = AsyncFunctionBitField::kMask | GeneratorFunctionBitField::kMask | ArrowFunctionBitField::kMask | MethodBitField::kMask | StaticBitField::kMask; return (flags & mask) == flags; } bool WebSnapshotSerializerDeserializer::IsConstructor(uint32_t flags) { uint32_t mask = ClassConstructorBitField::kMask | DefaultConstructorBitField::kMask | DerivedConstructorBitField::kMask; return ClassConstructorBitField::decode(flags) && (flags & mask) == flags; } uint32_t WebSnapshotSerializerDeserializer::GetDefaultAttributeFlags() { auto flags = ReadOnlyBitField::encode(false) | ConfigurableBitField::encode(true) | EnumerableBitField::encode(true); return flags; } uint32_t WebSnapshotSerializerDeserializer::AttributesToFlags( PropertyDetails details) { auto flags = ReadOnlyBitField::encode(details.IsReadOnly()) | ConfigurableBitField::encode(details.IsConfigurable()) | EnumerableBitField::encode(details.IsEnumerable()); return flags; } PropertyAttributes WebSnapshotSerializerDeserializer::FlagsToAttributes( uint32_t flags) { int attributes = ReadOnlyBitField::decode(flags) * READ_ONLY + !ConfigurableBitField::decode(flags) * DONT_DELETE + !EnumerableBitField::decode(flags) * DONT_ENUM; return PropertyAttributesFromInt(attributes); } WebSnapshotSerializer::WebSnapshotSerializer(v8::Isolate* isolate) : WebSnapshotSerializer(reinterpret_cast(isolate)) { } WebSnapshotSerializer::WebSnapshotSerializer(Isolate* isolate) : WebSnapshotSerializerDeserializer(isolate), string_serializer_(isolate_, nullptr), map_serializer_(isolate_, nullptr), context_serializer_(isolate_, nullptr), function_serializer_(isolate_, nullptr), class_serializer_(isolate_, nullptr), array_serializer_(isolate_, nullptr), object_serializer_(isolate_, nullptr), export_serializer_(isolate_, nullptr), external_objects_ids_(isolate_->heap()), string_ids_(isolate_->heap()), map_ids_(isolate_->heap()), context_ids_(isolate_->heap()), function_ids_(isolate_->heap()), class_ids_(isolate_->heap()), array_ids_(isolate_->heap()), object_ids_(isolate_->heap()), all_strings_(isolate_->heap()) { auto empty_array_list = factory()->empty_array_list(); contexts_ = empty_array_list; functions_ = empty_array_list; classes_ = empty_array_list; arrays_ = empty_array_list; objects_ = empty_array_list; strings_ = empty_array_list; maps_ = empty_array_list; } WebSnapshotSerializer::~WebSnapshotSerializer() {} bool WebSnapshotSerializer::TakeSnapshot( Handle object, MaybeHandle maybe_externals, WebSnapshotData& data_out) { if (string_ids_.size() > 0) { Throw("Can't reuse WebSnapshotSerializer"); return false; } if (!maybe_externals.is_null()) { ShallowDiscoverExternals(*maybe_externals.ToHandleChecked()); } if (object->IsHeapObject()) Discover(Handle::cast(object)); ConstructSource(); // The export is serialized with the empty string as name; we need to // "discover" the name here. DiscoverString(factory()->empty_string()); SerializeExport(object, factory()->empty_string()); WriteSnapshot(data_out.buffer, data_out.buffer_size); if (has_error()) { isolate_->ReportPendingMessages(); return false; } return true; } bool WebSnapshotSerializer::TakeSnapshot(v8::Local context, v8::Local exports, WebSnapshotData& data_out) { if (string_ids_.size() > 0) { Throw("Can't reuse WebSnapshotSerializer"); return false; } v8::Isolate* v8_isolate = reinterpret_cast(isolate_); std::unique_ptr[]> export_objects( new Handle[exports->Length()]); for (int i = 0, length = exports->Length(); i < length; ++i) { v8::Local str = exports->Get(v8_isolate, i)->ToString(context).ToLocalChecked(); if (str->Length() == 0) { continue; } // Discover the export name. DiscoverString(Handle::cast(Utils::OpenHandle(*str))); v8::ScriptCompiler::Source source(str); auto script = ScriptCompiler::Compile(context, &source).ToLocalChecked(); v8::MaybeLocal script_result = script->Run(context); v8::Local v8_object; if (script_result.IsEmpty() || !script_result.ToLocalChecked()->ToObject(context).ToLocal( &v8_object)) { Throw("Exported object not found"); return false; } export_objects[i] = Handle::cast(Utils::OpenHandle(*v8_object)); Discover(export_objects[i]); } ConstructSource(); for (int i = 0, length = exports->Length(); i < length; ++i) { v8::Local str = exports->Get(v8_isolate, i)->ToString(context).ToLocalChecked(); if (str->Length() == 0) { continue; } SerializeExport(export_objects[i], Handle::cast(Utils::OpenHandle(*str))); } WriteSnapshot(data_out.buffer, data_out.buffer_size); if (has_error()) { isolate_->ReportPendingMessages(); return false; } return true; } void WebSnapshotSerializer::SerializePendingItems() { // The information about string reference counts is now complete. The strings // in strings_ are not in place and can be serialized now. The in-place // strings will be serialized as part of their respective objects. for (int i = 0; i < strings_->Length(); ++i) { Handle string = handle(String::cast(strings_->Get(i)), isolate_); SerializeString(string, string_serializer_); } for (int i = 0; i < maps_->Length(); ++i) { Handle map = handle(Map::cast(maps_->Get(i)), isolate_); SerializeMap(map); } // Serialize the items in the reverse order. The items at the end of the // contexts_ etc get lower IDs and vice versa. IDs which items use for // referring to each other are reversed by GetId functions(). for (int i = contexts_->Length() - 1; i >= 0; --i) { Handle context = handle(Context::cast(contexts_->Get(i)), isolate_); SerializeContext(context); } for (int i = functions_->Length() - 1; i >= 0; --i) { Handle function = handle(JSFunction::cast(functions_->Get(i)), isolate_); SerializeFunction(function); } for (int i = classes_->Length() - 1; i >= 0; --i) { Handle function = handle(JSFunction::cast(classes_->Get(i)), isolate_); SerializeClass(function); } for (int i = arrays_->Length() - 1; i >= 0; --i) { Handle array = handle(JSArray::cast(arrays_->Get(i)), isolate_); SerializeArray(array); } for (int i = objects_->Length() - 1; i >= 0; --i) { Handle object = handle(JSObject::cast(objects_->Get(i)), isolate_); SerializeObject(object); } } // Format (full snapshot): // - Magic number (4 bytes) // - String count // - For each string: // - Serialized string // - Shape count // - For each shape: // - Serialized shape // - Context count // - For each context: // - Serialized context // - Function count // - For each function: // - Serialized function // - Object count // - For each object: // - Serialized object // - Export count // - For each export: // - Serialized export void WebSnapshotSerializer::WriteSnapshot(uint8_t*& buffer, size_t& buffer_size) { if (has_error()) { return; } SerializePendingItems(); ValueSerializer total_serializer(isolate_, nullptr); size_t needed_size = sizeof(kMagicNumber) + string_serializer_.buffer_size_ + map_serializer_.buffer_size_ + context_serializer_.buffer_size_ + function_serializer_.buffer_size_ + class_serializer_.buffer_size_ + array_serializer_.buffer_size_ + object_serializer_.buffer_size_ + export_serializer_.buffer_size_ + 8 * sizeof(uint32_t); if (total_serializer.ExpandBuffer(needed_size).IsNothing()) { Throw("Out of memory"); return; } total_serializer.WriteRawBytes(kMagicNumber, 4); WriteObjects(total_serializer, string_count(), string_serializer_, "strings"); WriteObjects(total_serializer, map_count(), map_serializer_, "maps"); WriteObjects(total_serializer, context_count(), context_serializer_, "contexts"); WriteObjects(total_serializer, function_count(), function_serializer_, "functions"); WriteObjects(total_serializer, array_count(), array_serializer_, "arrays"); WriteObjects(total_serializer, object_count(), object_serializer_, "objects"); WriteObjects(total_serializer, class_count(), class_serializer_, "classes"); WriteObjects(total_serializer, export_count_, export_serializer_, "exports"); if (has_error()) { return; } auto result = total_serializer.Release(); buffer = result.first; buffer_size = result.second; } void WebSnapshotSerializer::WriteObjects(ValueSerializer& destination, size_t count, ValueSerializer& source, const char* name) { if (count > std::numeric_limits::max()) { Throw("Too many objects"); return; } destination.WriteUint32(static_cast(count)); destination.WriteRawBytes(source.buffer_, source.buffer_size_); } bool WebSnapshotSerializer::InsertIntoIndexMap(ObjectCacheIndexMap& map, HeapObject heap_object, uint32_t& id) { DisallowGarbageCollection no_gc; int index_out; if (external_objects_ids_.Lookup(heap_object, &index_out)) { return true; } bool found = map.LookupOrInsert(heap_object, &index_out); id = static_cast(index_out); return found; } // Format: // - Length // - Raw bytes (data) void WebSnapshotSerializer::SerializeString(Handle string, ValueSerializer& serializer) { DisallowGarbageCollection no_gc; String::FlatContent flat = string->GetFlatContent(no_gc); DCHECK(flat.IsFlat()); if (flat.IsOneByte()) { base::Vector chars = flat.ToOneByteVector(); serializer.WriteUint32(chars.length()); serializer.WriteRawBytes(chars.begin(), chars.length() * sizeof(uint8_t)); } else if (flat.IsTwoByte()) { v8::Isolate* v8_isolate = reinterpret_cast(isolate_); v8::Local api_string = Utils::ToLocal(string); int length = api_string->Utf8Length(v8_isolate); std::unique_ptr buffer(new char[length]); api_string->WriteUtf8(v8_isolate, buffer.get(), length); serializer.WriteUint32(length); serializer.WriteRawBytes(buffer.get(), length * sizeof(uint8_t)); } else { UNREACHABLE(); } } // Format (serialized shape): // - PropertyAttributesType // - 0 if the __proto__ is Object.prototype, 1 + object id for the __proto__ // otherwise // - Property count // - For each property // - String id (name) // - If the PropertyAttributesType is CUSTOM: attributes void WebSnapshotSerializer::SerializeMap(Handle map) { int first_custom_index = -1; std::vector> keys; std::vector attributes; keys.reserve(map->NumberOfOwnDescriptors()); attributes.reserve(map->NumberOfOwnDescriptors()); for (InternalIndex i : map->IterateOwnDescriptors()) { Handle key(map->instance_descriptors(kRelaxedLoad).GetKey(i), isolate_); keys.push_back(Handle::cast(key)); PropertyDetails details = map->instance_descriptors(kRelaxedLoad).GetDetails(i); if (details.location() != PropertyLocation::kField) { Throw("Properties which are not fields not supported"); return; } if (first_custom_index >= 0 || details.IsReadOnly() || !details.IsConfigurable() || details.IsDontEnum()) { if (first_custom_index == -1) first_custom_index = i.as_int(); attributes.push_back(AttributesToFlags(details)); } } map_serializer_.WriteUint32(first_custom_index == -1 ? PropertyAttributesType::DEFAULT : PropertyAttributesType::CUSTOM); if (map->prototype() == isolate_->native_context()->initial_object_prototype()) { map_serializer_.WriteUint32(0); } else { // TODO(v8:11525): Support non-JSObject prototypes, at least null. Recognize // well-known objects to that we don't end up encoding them in the snapshot. if (!map->prototype().IsJSObject()) { Throw("Non-JSObject __proto__s not supported"); return; } uint32_t prototype_id = GetObjectId(JSObject::cast(map->prototype())); map_serializer_.WriteUint32(prototype_id + 1); } map_serializer_.WriteUint32(static_cast(keys.size())); uint32_t default_flags = GetDefaultAttributeFlags(); for (size_t i = 0; i < keys.size(); ++i) { if (first_custom_index >= 0) { if (static_cast(i) < first_custom_index) { map_serializer_.WriteUint32(default_flags); } else { map_serializer_.WriteUint32(attributes[i - first_custom_index]); } } WriteStringId(keys[i], map_serializer_); } } // Construct the minimal source string to be included in the snapshot. Maintain // the "inner function is textually inside its outer function" relationship. // Example: // Input: // Full source: abcdefghijklmnopqrstuvwxyzåäö // Functions: 11111111 22222222 3 // Inner functions: 44 55 666 // Output: // Constructed source: defghijkstuvwxyzö // Functions: 11111111222222223 // Inner functions 44 55 666 void WebSnapshotSerializer::ConstructSource() { if (source_intervals_.empty()) { return; } Handle source_string = factory()->empty_string(); int current_interval_start = 0; int current_interval_end = 0; for (const auto& interval : source_intervals_) { DCHECK_LE(current_interval_start, interval.first); // Iterated in order. DCHECK_LE(interval.first, interval.second); if (interval.second <= current_interval_end) { // This interval is fully within the current interval. We don't need to // include any new source code, just record the position conversion. auto offset_within_parent = interval.first - current_interval_start; source_offset_to_compacted_source_offset_[interval.first] = source_offset_to_compacted_source_offset_[current_interval_start] + offset_within_parent; continue; } // Start a new interval. current_interval_start = interval.first; current_interval_end = interval.second; source_offset_to_compacted_source_offset_[current_interval_start] = source_string->length(); MaybeHandle new_source_string = factory()->NewConsString( source_string, factory()->NewSubString(full_source_, current_interval_start, current_interval_end)); if (!new_source_string.ToHandle(&source_string)) { Throw("Cannot construct source string"); return; } } DiscoverString(source_string); bool in_place = false; source_id_ = GetStringId(source_string, in_place); DCHECK(!in_place); } void WebSnapshotSerializer::SerializeFunctionInfo(ValueSerializer* serializer, Handle function) { if (!function->shared().HasSourceCode()) { Throw("Function without source code"); return; } { DisallowGarbageCollection no_gc; Context context = function->context(); if (context.IsNativeContext() || context.IsScriptContext()) { serializer->WriteUint32(0); } else { DCHECK(context.IsFunctionContext() || context.IsBlockContext()); uint32_t context_id = GetContextId(context); serializer->WriteUint32(context_id + 1); } } serializer->WriteUint32(source_id_); int start = function->shared().StartPosition(); int end = function->shared().EndPosition(); serializer->WriteUint32(source_offset_to_compacted_source_offset_[start]); serializer->WriteUint32(end - start); serializer->WriteUint32( function->shared().internal_formal_parameter_count_without_receiver()); serializer->WriteUint32( FunctionKindToFunctionFlags(function->shared().kind())); if (function->has_prototype_slot() && function->has_instance_prototype()) { DisallowGarbageCollection no_gc; JSObject prototype = JSObject::cast(function->instance_prototype()); uint32_t prototype_id = GetObjectId(prototype); serializer->WriteUint32(prototype_id + 1); } else { serializer->WriteUint32(0); } } void WebSnapshotSerializer::ShallowDiscoverExternals(FixedArray externals) { DisallowGarbageCollection no_gc; for (int i = 0; i < externals.length(); i++) { Object object = externals.get(i); if (!object.IsHeapObject()) continue; uint32_t unused_id = 0; InsertIntoIndexMap(external_objects_ids_, HeapObject::cast(object), unused_id); } } void WebSnapshotSerializer::Discover(Handle start_object) { // The object discovery phase assigns IDs for objects / functions / classes / // arrays and discovers outgoing references from them. This is needed so that // e.g., we know all functions upfront and can construct the source code that // covers them before serializing the functions. discovery_queue_.push(start_object); while (!discovery_queue_.empty()) { const Handle& object = discovery_queue_.front(); switch (object->map().instance_type()) { case JS_FUNCTION_TYPE: DiscoverFunction(Handle::cast(object)); break; case JS_CLASS_CONSTRUCTOR_TYPE: DiscoverClass(Handle::cast(object)); break; case JS_OBJECT_TYPE: DiscoverObject(Handle::cast(object)); break; case JS_ARRAY_TYPE: DiscoverArray(Handle::cast(object)); break; case ODDBALL_TYPE: case HEAP_NUMBER_TYPE: // Can't contain references to other objects. break; case JS_PRIMITIVE_WRAPPER_TYPE: { Handle wrapper = Handle::cast(object); Handle value = handle(wrapper->value(), isolate_); if (value->IsHeapObject()) { discovery_queue_.push(Handle::cast(value)); } break; } case JS_REG_EXP_TYPE: { Handle regexp = Handle::cast(object); Handle pattern = handle(regexp->source(), isolate_); DiscoverString(pattern); Handle flags_string = JSRegExp::StringFromFlags(isolate_, regexp->flags()); DiscoverString(flags_string); break; } default: if (object->IsString()) { // These are array elements / object properties -> allow in place // strings. DiscoverString(Handle::cast(object), AllowInPlace::Yes); break; } else if (external_objects_ids_.size() > 0) { int unused_id; external_objects_ids_.LookupOrInsert(*object, &unused_id); } else { Throw("Unsupported object"); } } discovery_queue_.pop(); } } void WebSnapshotSerializer::DiscoverMap(Handle map) { uint32_t id; if (InsertIntoIndexMap(map_ids_, *map, id)) { return; } DCHECK_EQ(id, maps_->Length()); maps_ = ArrayList::Add(isolate_, maps_, map); for (InternalIndex i : map->IterateOwnDescriptors()) { Handle key(map->instance_descriptors(kRelaxedLoad).GetKey(i), isolate_); if (!key->IsString()) { Throw("Key is not a string"); return; } DiscoverString(Handle::cast(key)); } } void WebSnapshotSerializer::DiscoverString(Handle string, AllowInPlace can_be_in_place) { // Can't contain references to other objects. We only log the existence of the // string itself. Internalize the strings so that we can properly track which // String objects are the same string. string = factory()->InternalizeString(string); auto result = all_strings_.FindOrInsert(string); if (can_be_in_place == AllowInPlace::Yes && !result.already_exists) { // This is the only reference to the string so far. Don't generate and // ID for it yet; only generate it when another reference to the string is // found. return; } // The string is referred to more than two places, or in-placing not allowed // -> not a candidate for writing it in-place. Generate an ID for it. // TODO(v8:11525): Allow in-place strings in more places. Heuristics for // when to make them in place? uint32_t id; if (InsertIntoIndexMap(string_ids_, *string, id)) { return; } DCHECK_EQ(id, strings_->Length()); strings_ = ArrayList::Add(isolate_, strings_, string); } void WebSnapshotSerializer::DiscoverFunction(Handle function) { uint32_t id; if (InsertIntoIndexMap(function_ids_, *function, id)) { return; } DCHECK_EQ(id, functions_->Length()); functions_ = ArrayList::Add(isolate_, functions_, function); DiscoverContextAndPrototype(function); // TODO(v8:11525): Support properties in functions. DiscoverSource(function); } void WebSnapshotSerializer::DiscoverClass(Handle function) { uint32_t id; if (InsertIntoIndexMap(class_ids_, *function, id)) { return; } DCHECK_EQ(id, classes_->Length()); classes_ = ArrayList::Add(isolate_, classes_, function); DiscoverContextAndPrototype(function); // TODO(v8:11525): Support properties in classes. // TODO(v8:11525): Support class members. DiscoverSource(function); } void WebSnapshotSerializer::DiscoverContextAndPrototype( Handle function) { Handle context(function->context(), isolate_); if (context->IsFunctionContext() || context->IsBlockContext()) { DiscoverContext(context); } if (function->has_prototype_slot() && function->map().has_non_instance_prototype()) { Throw("Functions with non-instance prototypes not supported"); return; } if (function->has_prototype_slot() && function->has_instance_prototype()) { Handle prototype = Handle::cast( handle(function->instance_prototype(), isolate_)); discovery_queue_.push(prototype); } } void WebSnapshotSerializer::DiscoverContext(Handle context) { uint32_t id; if (InsertIntoIndexMap(context_ids_, *context, id)) return; DCHECK_EQ(id, contexts_->Length()); contexts_ = ArrayList::Add(isolate_, contexts_, context); Handle scope_info = handle(context->scope_info(), isolate_); int count = scope_info->ContextLocalCount(); for (int i = 0; i < count; ++i) { // TODO(v8:11525): support parameters // TODO(v8:11525): distinguish variable modes Handle name(scope_info->context_local_names(i), isolate_); DiscoverString(name); Object value = context->get(scope_info->ContextHeaderLength() + i); if (!value.IsHeapObject()) continue; discovery_queue_.push(handle(HeapObject::cast(value), isolate_)); } if (!context->previous().IsNativeContext() && !context->previous().IsScriptContext()) { DiscoverContext(handle(context->previous(), isolate_)); } } void WebSnapshotSerializer::DiscoverSource(Handle function) { source_intervals_.emplace(function->shared().StartPosition(), function->shared().EndPosition()); Handle function_script_source = handle(String::cast(Script::cast(function->shared().script()).source()), isolate_); if (full_source_.is_null()) { full_source_ = function_script_source; } else if (!full_source_->Equals(*function_script_source)) { Throw("Cannot include functions from multiple scripts"); } } void WebSnapshotSerializer::DiscoverArray(Handle array) { uint32_t id; if (InsertIntoIndexMap(array_ids_, *array, id)) { return; } DCHECK_EQ(id, arrays_->Length()); arrays_ = ArrayList::Add(isolate_, arrays_, array); auto elements_kind = array->GetElementsKind(); if (elements_kind != PACKED_SMI_ELEMENTS && elements_kind != PACKED_ELEMENTS) { Throw("Unsupported array"); return; } // TODO(v8:11525): Support sparse arrays & arrays with holes. DisallowGarbageCollection no_gc; FixedArray elements = FixedArray::cast(array->elements()); for (int i = 0; i < elements.length(); ++i) { Object object = elements.get(i); if (!object.IsHeapObject()) continue; discovery_queue_.push(handle(HeapObject::cast(object), isolate_)); } } void WebSnapshotSerializer::DiscoverObject(Handle object) { uint32_t id; if (InsertIntoIndexMap(object_ids_, *object, id)) return; DCHECK_EQ(id, objects_->Length()); objects_ = ArrayList::Add(isolate_, objects_, object); // TODO(v8:11525): Support objects with so many properties that they can't be // in fast mode. JSObject::MigrateSlowToFast(object, 0, "Web snapshot"); if (!object->HasFastProperties()) { Throw("Dictionary mode objects not supported"); } Handle map(object->map(), isolate_); DiscoverMap(map); // Discover __proto__. if (map->prototype() != isolate_->native_context()->initial_object_prototype()) { discovery_queue_.push(handle(map->prototype(), isolate_)); } // Discover property values. for (InternalIndex i : map->IterateOwnDescriptors()) { PropertyDetails details = map->instance_descriptors(kRelaxedLoad).GetDetails(i); FieldIndex field_index = FieldIndex::ForDescriptor(*map, i); Handle value = JSObject::FastPropertyAt( isolate_, object, details.representation(), field_index); if (!value->IsHeapObject()) continue; discovery_queue_.push(Handle::cast(value)); } // Discover elements. Handle elements = handle(FixedArray::cast(object->elements()), isolate_); for (int i = 0; i < elements->length(); ++i) { Object object = elements->get(i); if (!object.IsHeapObject()) continue; discovery_queue_.push(handle(HeapObject::cast(object), isolate_)); } } // Format (serialized function): // - 0 if there's no context, 1 + context id otherwise // - String id (source snippet) // - Start position in the source snippet // - Length in the source snippet // - Formal parameter count // - Flags (see FunctionFlags) // - 0 if there's no function prototype, 1 + object id for the function // prototype otherwise // TODO(v8:11525): Investigate whether the length is really needed. void WebSnapshotSerializer::SerializeFunction(Handle function) { SerializeFunctionInfo(&function_serializer_, function); // TODO(v8:11525): Support properties in functions. } // Format (serialized class): // - 1 + context id // - String id (source snippet) // - Start position in the source snippet // - Length in the source snippet // - Formal parameter count // - Flags (see FunctionFlags) // - 1 + object id for the function prototype void WebSnapshotSerializer::SerializeClass(Handle function) { SerializeFunctionInfo(&class_serializer_, function); // TODO(v8:11525): Support properties in classes. // TODO(v8:11525): Support class members. } // Format (serialized context): // - 0 if there's no parent context, 1 + parent context id otherwise // - Variable count // - For each variable: // - String id (name) // - Serialized value void WebSnapshotSerializer::SerializeContext(Handle context) { uint32_t parent_context_id = 0; if (!context->previous().IsNativeContext() && !context->previous().IsScriptContext()) { parent_context_id = GetContextId(context->previous()) + 1; } // TODO(v8:11525): Use less space for encoding the context type. if (context->IsFunctionContext()) { context_serializer_.WriteUint32(ContextType::FUNCTION); } else if (context->IsBlockContext()) { context_serializer_.WriteUint32(ContextType::BLOCK); } else { Throw("Unsupported context type"); return; } context_serializer_.WriteUint32(parent_context_id); Handle scope_info(context->scope_info(), isolate_); int count = scope_info->ContextLocalCount(); context_serializer_.WriteUint32(count); for (int i = 0; i < count; ++i) { // TODO(v8:11525): support parameters // TODO(v8:11525): distinguish variable modes Handle name(scope_info->context_local_names(i), isolate_); WriteStringId(name, context_serializer_); Handle value(context->get(scope_info->ContextHeaderLength() + i), isolate_); WriteValue(value, context_serializer_); } } // Format (serialized object): // - Shape id // - For each property: // - Serialized value // - Max element index + 1 (or 0 if there are no elements) // - For each element: // - Index // - Serialized value // TODO(v8:11525): Support packed elements with a denser format. void WebSnapshotSerializer::SerializeObject(Handle object) { Handle map(object->map(), isolate_); uint32_t map_id = GetMapId(*map); object_serializer_.WriteUint32(map_id); // Properties. for (InternalIndex i : map->IterateOwnDescriptors()) { PropertyDetails details = map->instance_descriptors(kRelaxedLoad).GetDetails(i); FieldIndex field_index = FieldIndex::ForDescriptor(*map, i); Handle value = JSObject::FastPropertyAt( isolate_, object, details.representation(), field_index); WriteValue(value, object_serializer_); } // Elements. ReadOnlyRoots roots(isolate_); Handle elements = handle(FixedArray::cast(object->elements()), isolate_); uint32_t max_element_index = 0; for (int i = 0; i < elements->length(); ++i) { DisallowGarbageCollection no_gc; Object value = elements->get(i); if (value != roots.the_hole_value()) { if (i > static_cast(max_element_index)) { max_element_index = i; } } } if (max_element_index == 0) { object_serializer_.WriteUint32(0); } else { object_serializer_.WriteUint32(max_element_index + 1); } for (int i = 0; i < elements->length(); ++i) { Handle value = handle(elements->get(i), isolate_); if (*value != roots.the_hole_value()) { DCHECK_LE(i, max_element_index); object_serializer_.WriteUint32(i); WriteValue(value, object_serializer_); } } } // Format (serialized array): // - Length // - For each element: // - Serialized value void WebSnapshotSerializer::SerializeArray(Handle array) { auto elements_kind = array->GetElementsKind(); if (elements_kind != PACKED_SMI_ELEMENTS && elements_kind != PACKED_ELEMENTS) { Throw("Unsupported array"); return; } // TODO(v8:11525): Support sparse arrays & arrays with holes. uint32_t length = static_cast(array->length().ToSmi().value()); array_serializer_.WriteUint32(length); Handle elements = handle(FixedArray::cast(array->elements()), isolate_); for (uint32_t i = 0; i < length; ++i) { WriteValue(handle(elements->get(i), isolate_), array_serializer_); } } // Format (serialized export): // - String id (export name) // - Serialized value (export value) void WebSnapshotSerializer::SerializeExport(Handle object, Handle export_name) { ++export_count_; WriteStringId(export_name, export_serializer_); if (object->IsJSPrimitiveWrapper()) { Handle wrapper = Handle::cast(object); Handle export_value = handle(wrapper->value(), isolate_); WriteValue(export_value, export_serializer_); } else { WriteValue(object, export_serializer_); } } // Format (serialized value): // - Type id (ValueType enum) // - Value or id (interpretation depends on the type) void WebSnapshotSerializer::WriteValue(Handle object, ValueSerializer& serializer) { if (object->IsSmi()) { serializer.WriteUint32(ValueType::INTEGER); serializer.WriteZigZag(Smi::cast(*object).value()); return; } int external_id; if (external_objects_ids_.Lookup(HeapObject::cast(*object), &external_id)) { serializer.WriteUint32(ValueType::EXTERNAL_ID); serializer.WriteUint32(static_cast(external_id)); return; } DCHECK(object->IsHeapObject()); Handle heap_object = Handle::cast(object); switch ((*heap_object).map().instance_type()) { case ODDBALL_TYPE: switch (Oddball::cast(*heap_object).kind()) { case Oddball::kFalse: serializer.WriteUint32(ValueType::FALSE_CONSTANT); return; case Oddball::kTrue: serializer.WriteUint32(ValueType::TRUE_CONSTANT); return; case Oddball::kNull: serializer.WriteUint32(ValueType::NULL_CONSTANT); return; case Oddball::kUndefined: serializer.WriteUint32(ValueType::UNDEFINED_CONSTANT); return; default: UNREACHABLE(); } case HEAP_NUMBER_TYPE: // TODO(v8:11525): Handle possible endianness mismatch. serializer.WriteUint32(ValueType::DOUBLE); serializer.WriteDouble(HeapNumber::cast(*heap_object).value()); break; case JS_FUNCTION_TYPE: serializer.WriteUint32(ValueType::FUNCTION_ID); serializer.WriteUint32(GetFunctionId(JSFunction::cast(*heap_object))); break; case JS_CLASS_CONSTRUCTOR_TYPE: serializer.WriteUint32(ValueType::CLASS_ID); serializer.WriteUint32(GetClassId(JSFunction::cast(*heap_object))); break; case JS_OBJECT_TYPE: serializer.WriteUint32(ValueType::OBJECT_ID); serializer.WriteUint32(GetObjectId(JSObject::cast(*heap_object))); break; case JS_ARRAY_TYPE: serializer.WriteUint32(ValueType::ARRAY_ID); serializer.WriteUint32(GetArrayId(JSArray::cast(*heap_object))); break; case JS_REG_EXP_TYPE: { Handle regexp = Handle::cast(heap_object); if (regexp->map() != isolate_->regexp_function()->initial_map()) { Throw("Unsupported RegExp map"); return; } serializer.WriteUint32(ValueType::REGEXP); Handle pattern = handle(regexp->source(), isolate_); WriteStringId(pattern, serializer); Handle flags_string = JSRegExp::StringFromFlags(isolate_, regexp->flags()); WriteStringId(flags_string, serializer); break; } default: if (heap_object->IsString()) { // Write strings which are referred to only once as in-place strings. WriteStringMaybeInPlace(Handle::cast(heap_object), serializer); } else { Throw("Unsupported object"); } } // TODO(v8:11525): Support more types. } void WebSnapshotSerializer::WriteStringMaybeInPlace( Handle string, ValueSerializer& serializer) { // If the string is only referred to by one location, write it in-place. bool in_place = false; uint32_t id = GetStringId(string, in_place); if (in_place) { serializer.WriteUint32(ValueType::IN_PLACE_STRING_ID); SerializeString(string, serializer); } else { serializer.WriteUint32(ValueType::STRING_ID); serializer.WriteUint32(id); } } void WebSnapshotSerializer::WriteStringId(Handle string, ValueSerializer& serializer) { bool in_place = false; uint32_t id = GetStringId(string, in_place); CHECK(!in_place); // The string must have an ID. serializer.WriteUint32(id); } uint32_t WebSnapshotSerializer::GetStringId(Handle string, bool& in_place) { // Internalize strings so that they're unique. string = factory()->InternalizeString(string); // Strings referred to more than one places are inserted in string_ids_. // Strings referred to by only one place aren't. #ifdef DEBUG auto result = all_strings_.FindOrInsert(string); DCHECK(result.already_exists); #endif int id = 0; in_place = !string_ids_.Lookup(*string, &id); return static_cast(id); } uint32_t WebSnapshotSerializer::GetMapId(Map map) { int id; bool return_value = map_ids_.Lookup(map, &id); DCHECK(return_value); USE(return_value); return static_cast(id); } uint32_t WebSnapshotSerializer::GetFunctionId(JSFunction function) { int id; bool return_value = function_ids_.Lookup(function, &id); DCHECK(return_value); USE(return_value); return static_cast(function_ids_.size() - 1 - id); } uint32_t WebSnapshotSerializer::GetClassId(JSFunction function) { int id; bool return_value = class_ids_.Lookup(function, &id); DCHECK(return_value); USE(return_value); return static_cast(class_ids_.size() - 1 - id); } uint32_t WebSnapshotSerializer::GetContextId(Context context) { int id; bool return_value = context_ids_.Lookup(context, &id); DCHECK(return_value); USE(return_value); return static_cast(context_ids_.size() - 1 - id); } uint32_t WebSnapshotSerializer::GetArrayId(JSArray array) { int id; bool return_value = array_ids_.Lookup(array, &id); DCHECK(return_value); USE(return_value); return static_cast(array_ids_.size() - 1 - id); } uint32_t WebSnapshotSerializer::GetObjectId(JSObject object) { int id; bool return_value = object_ids_.Lookup(object, &id); DCHECK(return_value); USE(return_value); return static_cast(object_ids_.size() - 1 - id); } uint32_t WebSnapshotSerializer::GetExternalId(HeapObject object) { int id; bool return_value = external_objects_ids_.Lookup(object, &id); DCHECK(return_value); USE(return_value); return static_cast(id); } Handle WebSnapshotSerializer::GetExternals() { return external_objects_ids_.Values(isolate_); } WebSnapshotDeserializer::WebSnapshotDeserializer(v8::Isolate* isolate, const uint8_t* data, size_t buffer_size) : WebSnapshotDeserializer(reinterpret_cast(isolate), Handle(), {data, buffer_size}) {} WebSnapshotDeserializer::WebSnapshotDeserializer( Isolate* isolate, Handle