From 3ab6dbad4549badbc80f59d04779123f23190bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marja=20H=C3=B6ltt=C3=A4?= Date: Thu, 16 Dec 2021 14:55:06 +0100 Subject: [PATCH] [web snapshots] Implement __proto__ support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Serialize and restore objects' __proto__s. Bug: v8:11525 Change-Id: I241de1f8b716b2a1cc3cd45ce83759e07759202a Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3345002 Reviewed-by: Camillo Bruni Commit-Queue: Marja Hölttä Cr-Commit-Position: refs/heads/main@{#78641} --- src/web-snapshot/web-snapshot.cc | 57 +++++++++++++- test/mjsunit/web-snapshot/web-snapshot.js | 90 +++++++++++++++++++++++ 2 files changed, 145 insertions(+), 2 deletions(-) diff --git a/src/web-snapshot/web-snapshot.cc b/src/web-snapshot/web-snapshot.cc index cf7e40a758..c5d56710db 100644 --- a/src/web-snapshot/web-snapshot.cc +++ b/src/web-snapshot/web-snapshot.cc @@ -411,9 +411,13 @@ void WebSnapshotSerializer::SerializeString(Handle string, } // 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, uint32_t& id) { if (InsertIntoIndexMap(map_ids_, map, id)) { return; @@ -453,6 +457,20 @@ void WebSnapshotSerializer::SerializeMap(Handle map, uint32_t& id) { map_serializer_.WriteUint32(first_custom_index == -1 ? PropertyAttributesType::DEFAULT : PropertyAttributesType::CUSTOM); + + if (map->prototype() == isolate_->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(string_ids.size())); uint32_t default_flags = GetDefaultAttributeFlags(); @@ -676,6 +694,13 @@ void WebSnapshotSerializer::DiscoverObject(Handle object) { JSObject::MigrateSlowToFast(object, 0, "Web snapshot"); Handle map(object->map(), isolate_); + + // Discover __proto__. + if (map->prototype() != isolate_->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); @@ -1185,6 +1210,13 @@ void WebSnapshotDeserializer::DeserializeMaps() { return; } + uint32_t prototype_id; + if (!deserializer_->ReadUint32(&prototype_id) || + prototype_id > kMaxItemCount) { + Throw("Malformed shape"); + return; + } + uint32_t property_count; if (!deserializer_->ReadUint32(&property_count)) { Throw("Malformed shape"); @@ -1232,8 +1264,22 @@ void WebSnapshotDeserializer::DeserializeMaps() { JS_OBJECT_TYPE, JSObject::kHeaderSize * kTaggedSize, HOLEY_ELEMENTS, 0); map->InitializeDescriptors(isolate_, *descriptors); // TODO(v8:11525): Set 'constructor'. - // TODO(v8:11525): Set the correct prototype. + if (prototype_id == 0) { + // Use Object.prototype as the prototype. + map->set_prototype(isolate_->context().initial_object_prototype(), + UPDATE_WRITE_BARRIER); + } else { + // TODO(v8::11525): Implement stricter checks, e.g., disallow cycles. + --prototype_id; + if (prototype_id < current_object_count_) { + map->set_prototype(HeapObject::cast(objects_->get(prototype_id)), + UPDATE_WRITE_BARRIER); + } else { + // The object hasn't been deserialized yet. + AddDeferredReference(map, 0, OBJECT_ID, prototype_id); + } + } maps_->set(i, *map); } } @@ -1882,7 +1928,8 @@ void WebSnapshotDeserializer::AddDeferredReference(Handle container, ValueType target_type, uint32_t target_index) { DCHECK(container->IsPropertyArray() || container->IsContext() || - container->IsFixedArray() || container->IsJSFunction()); + container->IsFixedArray() || container->IsJSFunction() || + container->IsMap()); deferred_references_ = ArrayList::Add( isolate_, deferred_references_, container, Smi::FromInt(index), Smi::FromInt(target_type), Smi::FromInt(target_index)); @@ -1966,6 +2013,12 @@ void WebSnapshotDeserializer::ProcessDeferredReferences() { Throw("Can't reuse function prototype"); return; } + } else if (container.IsMap()) { + // The only deferred reference allowed for a Map is the __proto__. + DCHECK_EQ(index, 0); + DCHECK(target.IsJSReceiver()); + Map::cast(container).set_prototype(HeapObject::cast(target), + UPDATE_WRITE_BARRIER); } else { UNREACHABLE(); } diff --git a/test/mjsunit/web-snapshot/web-snapshot.js b/test/mjsunit/web-snapshot/web-snapshot.js index d202258c11..50366ff745 100644 --- a/test/mjsunit/web-snapshot/web-snapshot.js +++ b/test/mjsunit/web-snapshot/web-snapshot.js @@ -35,6 +35,17 @@ function takeAndUseWebSnapshot(createObjects, exports) { assertEquals(42, foo.n); })(); +(function TestDefaultObjectProto() { + function createObjects() { + globalThis.foo = { + str: 'hello', + n: 42, + }; + } + const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']); + assertEquals(Object.prototype, Object.getPrototypeOf(foo)); +})(); + (function TestEmptyObject() { function createObjects() { globalThis.foo = {}; @@ -43,6 +54,38 @@ function takeAndUseWebSnapshot(createObjects, exports) { assertEquals([], Object.keys(foo)); })(); +(function TestEmptyObjectProto() { + function createObjects() { + globalThis.foo = {}; + } + const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']); + assertEquals(Object.prototype, Object.getPrototypeOf(foo)); +})(); + +(function TestObjectProto() { + function createObjects() { + globalThis.foo = { + __proto__ : {x : 10}, + y: 11 + }; + } + const { foo } = takeAndUseWebSnapshot(createObjects, ['foo']); + assertEquals(10, Object.getPrototypeOf(foo).x); +})(); + +(function TestObjectProtoInSnapshot() { + function createObjects() { + globalThis.o1 = { x: 10}; + globalThis.o2 = { + __proto__ : o1, + y: 11 + }; + } + const { o1, o2 } = takeAndUseWebSnapshot(createObjects, ['o1', 'o2']); + assertEquals(o1, Object.getPrototypeOf(o2)); + assertEquals(Object.prototype, Object.getPrototypeOf(o1)); +})(); + (function TestNumbers() { function createObjects() { globalThis.foo = { @@ -413,3 +456,50 @@ function takeAndUseWebSnapshot(createObjects, exports) { assertEquals(3, o.x); assertEquals(10, o.m(3, 4)); })(); + +(function TestFunctionPrototypeBecomesProto() { + function createObjects() { + globalThis.F = function() {} + globalThis.F.prototype.x = 100; + } + const { F } = takeAndUseWebSnapshot(createObjects, ['F']); + const o = new F(); + assertEquals(100, Object.getPrototypeOf(o).x); +})(); + +(function TestFunctionCtorCallsFunctionInPrototype() { + function createObjects() { + globalThis.F = function() { + this.fooCalled = false; + this.foo(); + } + globalThis.F.prototype.foo = function() { this.fooCalled = true; }; + } + const { F } = takeAndUseWebSnapshot(createObjects, ['F']); + const o = new F(); + assertTrue(o.fooCalled); +})(); + +(function TestFunctionPrototypeConnectedToObjectPrototype() { + function createObjects() { + globalThis.F = function() {} + } + const { F } = takeAndUseWebSnapshot(createObjects, ['F']); + const o = new F(); + assertEquals(Object.prototype, + Object.getPrototypeOf(Object.getPrototypeOf(o))); +})(); + +(function TestFunctionInheritance() { + function createObjects() { + globalThis.Super = function() {} + globalThis.Super.prototype.superfunc = function() { return 'superfunc'; }; + globalThis.Sub = function() {} + globalThis.Sub.prototype = Object.create(Super.prototype); + globalThis.Sub.prototype.subfunc = function() { return 'subfunc'; }; + } + const { Sub } = takeAndUseWebSnapshot(createObjects, ['Sub']); + const o = new Sub(); + assertEquals('superfunc', o.superfunc()); + assertEquals('subfunc', o.subfunc()); +})();