Commit 9be698ff authored by Camillo Bruni's avatar Camillo Bruni Committed by V8 LUCI CQ

[web-snapshot] Collect unsupported objects in the externals JSArray

With this change we can easily track and filter unsupported objects
for full-page snapshots.

Bug: v8:11525
Change-Id: Id75b6f4edf68b47d6dfbe79aed2b686aeec61068
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3484320Reviewed-by: 's avatarMarja Hölttä <marja@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79252}
parent 88ddce15
...@@ -1559,23 +1559,35 @@ RUNTIME_FUNCTION(Runtime_WebSnapshotSerialize) { ...@@ -1559,23 +1559,35 @@ RUNTIME_FUNCTION(Runtime_WebSnapshotSerialize) {
} }
Handle<Object> object = args.at<Object>(0); Handle<Object> object = args.at<Object>(0);
Handle<FixedArray> block_list = isolate->factory()->empty_fixed_array(); Handle<FixedArray> block_list = isolate->factory()->empty_fixed_array();
Handle<JSArray> block_list_js_array;
if (args.length() == 2) { if (args.length() == 2) {
if (!args[1].IsJSArray()) { if (!args[1].IsJSArray()) {
THROW_NEW_ERROR_RETURN_FAILURE( THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kInvalidArgument)); isolate, NewTypeError(MessageTemplate::kInvalidArgument));
} }
auto raw_blocklist = args.at<JSArray>(1); block_list_js_array = args.at<JSArray>(1);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION( ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, block_list, isolate, block_list,
JSReceiver::GetOwnValues(raw_blocklist, JSReceiver::GetOwnValues(block_list_js_array,
PropertyFilter::ENUMERABLE_STRINGS)); PropertyFilter::ENUMERABLE_STRINGS));
} }
auto snapshot_data = std::make_shared<WebSnapshotData>(); auto snapshot_data = std::make_shared<WebSnapshotData>();
WebSnapshotSerializer serializer(isolate); WebSnapshotSerializer serializer(isolate);
if (!serializer.TakeSnapshot(object, block_list, *snapshot_data)) { if (!serializer.TakeSnapshot(object, block_list, *snapshot_data)) {
DCHECK(isolate->has_pending_exception());
return ReadOnlyRoots(isolate).exception(); return ReadOnlyRoots(isolate).exception();
} }
if (!block_list_js_array.is_null() &&
static_cast<uint32_t>(block_list->length()) <
serializer.external_objects_count()) {
Handle<FixedArray> externals = serializer.GetExternals();
Handle<Map> map = JSObject::GetElementsTransitionMap(block_list_js_array,
PACKED_ELEMENTS);
block_list_js_array->set_elements(*externals);
block_list_js_array->set_length(Smi::FromInt(externals->length()));
block_list_js_array->set_map(*map);
}
i::Handle<i::Object> managed_object = Managed<WebSnapshotData>::FromSharedPtr( i::Handle<i::Object> managed_object = Managed<WebSnapshotData>::FromSharedPtr(
isolate, snapshot_data->buffer_size, snapshot_data); isolate, snapshot_data->buffer_size, snapshot_data);
return *managed_object; return *managed_object;
...@@ -1613,8 +1625,8 @@ RUNTIME_FUNCTION(Runtime_WebSnapshotDeserialize) { ...@@ -1613,8 +1625,8 @@ RUNTIME_FUNCTION(Runtime_WebSnapshotDeserialize) {
WebSnapshotDeserializer deserializer(v8_isolate, data->buffer, WebSnapshotDeserializer deserializer(v8_isolate, data->buffer,
data->buffer_size); data->buffer_size);
if (!deserializer.Deserialize(injected_references)) { if (!deserializer.Deserialize(injected_references)) {
THROW_NEW_ERROR_RETURN_FAILURE( DCHECK(isolate->has_pending_exception());
isolate, NewTypeError(MessageTemplate::kWebSnapshotError)); return ReadOnlyRoots(isolate).exception();
} }
Handle<Object> object; Handle<Object> object;
if (!deserializer.value().ToHandle(&object)) { if (!deserializer.value().ToHandle(&object)) {
......
...@@ -142,8 +142,6 @@ namespace internal { ...@@ -142,8 +142,6 @@ namespace internal {
F(HandleDebuggerStatement, 0, 1) \ F(HandleDebuggerStatement, 0, 1) \
F(IsBreakOnException, 1, 1) \ F(IsBreakOnException, 1, 1) \
F(LiveEditPatchScript, 2, 1) \ F(LiveEditPatchScript, 2, 1) \
F(WebSnapshotDeserialize, -1, 1) \
F(WebSnapshotSerialize, -1, 1) \
F(ProfileCreateSnapshotDataBlob, 0, 1) \ F(ProfileCreateSnapshotDataBlob, 0, 1) \
F(ScheduleBreak, 0, 1) \ F(ScheduleBreak, 0, 1) \
F(ScriptLocationFromLine2, 4, 1) \ F(ScriptLocationFromLine2, 4, 1) \
...@@ -481,11 +479,11 @@ namespace internal { ...@@ -481,11 +479,11 @@ namespace internal {
F(DebugTrace, 0, 1) \ F(DebugTrace, 0, 1) \
F(DebugTrackRetainingPath, -1, 1) \ F(DebugTrackRetainingPath, -1, 1) \
F(DeoptimizeFunction, 1, 1) \ F(DeoptimizeFunction, 1, 1) \
F(DisableOptimizationFinalization, 0, 1) \
F(DisallowCodegenFromStrings, 1, 1) \ F(DisallowCodegenFromStrings, 1, 1) \
F(DisassembleFunction, 1, 1) \ F(DisassembleFunction, 1, 1) \
F(EnableCodeLoggingForTesting, 0, 1) \ F(EnableCodeLoggingForTesting, 0, 1) \
F(EnsureFeedbackVectorForFunction, 1, 1) \ F(EnsureFeedbackVectorForFunction, 1, 1) \
F(DisableOptimizationFinalization, 0, 1) \
F(FinalizeOptimization, 0, 1) \ F(FinalizeOptimization, 0, 1) \
F(GetCallable, 0, 1) \ F(GetCallable, 0, 1) \
F(GetInitializerFunction, 1, 1) \ F(GetInitializerFunction, 1, 1) \
...@@ -539,10 +537,10 @@ namespace internal { ...@@ -539,10 +537,10 @@ namespace internal {
F(PretenureAllocationSite, 1, 1) \ F(PretenureAllocationSite, 1, 1) \
F(PrintWithNameForAssert, 2, 1) \ F(PrintWithNameForAssert, 2, 1) \
F(PromiseSpeciesProtector, 0, 1) \ F(PromiseSpeciesProtector, 0, 1) \
F(RegExpSpeciesProtector, 0, 1) \
F(RegexpHasBytecode, 2, 1) \ F(RegexpHasBytecode, 2, 1) \
F(RegexpHasNativeCode, 2, 1) \ F(RegexpHasNativeCode, 2, 1) \
F(RegexpIsUnmodified, 1, 1) \ F(RegexpIsUnmodified, 1, 1) \
F(RegExpSpeciesProtector, 0, 1) \
F(RegexpTypeTag, 1, 1) \ F(RegexpTypeTag, 1, 1) \
F(RunningInSimulator, 0, 1) \ F(RunningInSimulator, 0, 1) \
F(RuntimeEvaluateREPL, 1, 1) \ F(RuntimeEvaluateREPL, 1, 1) \
...@@ -561,6 +559,8 @@ namespace internal { ...@@ -561,6 +559,8 @@ namespace internal {
F(TurbofanStaticAssert, 1, 1) \ F(TurbofanStaticAssert, 1, 1) \
F(TypedArraySpeciesProtector, 0, 1) \ F(TypedArraySpeciesProtector, 0, 1) \
F(WaitForBackgroundOptimization, 0, 1) \ F(WaitForBackgroundOptimization, 0, 1) \
F(WebSnapshotDeserialize, -1, 1) \
F(WebSnapshotSerialize, -1, 1) \
I(DeoptimizeNow, 0, 1) I(DeoptimizeNow, 0, 1)
#define FOR_EACH_INTRINSIC_TYPEDARRAY(F, I) \ #define FOR_EACH_INTRINSIC_TYPEDARRAY(F, I) \
......
...@@ -1255,5 +1255,21 @@ Serializer::HotObjectsList::~HotObjectsList() { ...@@ -1255,5 +1255,21 @@ Serializer::HotObjectsList::~HotObjectsList() {
heap_->UnregisterStrongRoots(strong_roots_entry_); heap_->UnregisterStrongRoots(strong_roots_entry_);
} }
Handle<FixedArray> ObjectCacheIndexMap::Values(Isolate* isolate) {
if (size() == 0) {
return isolate->factory()->empty_fixed_array();
}
Handle<FixedArray> externals = isolate->factory()->NewFixedArray(size());
DisallowGarbageCollection no_gc;
FixedArray raw = *externals;
IdentityMap<int, base::DefaultAllocationPolicy>::IteratableScope it_scope(
&map_);
for (auto it = it_scope.begin(); it != it_scope.end(); ++it) {
raw.set(*it.entry(), it.key());
}
return externals;
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -162,6 +162,8 @@ class ObjectCacheIndexMap { ...@@ -162,6 +162,8 @@ class ObjectCacheIndexMap {
return true; return true;
} }
Handle<FixedArray> Values(Isolate* isolate);
int size() const { return next_index_; } int size() const { return next_index_; }
private: private:
......
...@@ -653,6 +653,9 @@ void WebSnapshotSerializer::Discover(Handle<HeapObject> start_object) { ...@@ -653,6 +653,9 @@ void WebSnapshotSerializer::Discover(Handle<HeapObject> start_object) {
if (object->IsString()) { if (object->IsString()) {
// Can't contain references to other objects. // Can't contain references to other objects.
break; break;
} else if (external_objects_ids_.size() > 0) {
int unused_id;
external_objects_ids_.LookupOrInsert(*object, &unused_id);
} else { } else {
Throw("Unsupported object"); Throw("Unsupported object");
} }
...@@ -963,9 +966,10 @@ void WebSnapshotSerializer::WriteValue(Handle<Object> object, ...@@ -963,9 +966,10 @@ void WebSnapshotSerializer::WriteValue(Handle<Object> object,
} }
DCHECK(object->IsHeapObject()); DCHECK(object->IsHeapObject());
switch (HeapObject::cast(*object).map().instance_type()) { Handle<HeapObject> heap_object = Handle<HeapObject>::cast(object);
switch ((*heap_object).map().instance_type()) {
case ODDBALL_TYPE: case ODDBALL_TYPE:
switch (Oddball::cast(*object).kind()) { switch (Oddball::cast(*heap_object).kind()) {
case Oddball::kFalse: case Oddball::kFalse:
serializer.WriteUint32(ValueType::FALSE_CONSTANT); serializer.WriteUint32(ValueType::FALSE_CONSTANT);
return; return;
...@@ -984,26 +988,26 @@ void WebSnapshotSerializer::WriteValue(Handle<Object> object, ...@@ -984,26 +988,26 @@ void WebSnapshotSerializer::WriteValue(Handle<Object> object,
case HEAP_NUMBER_TYPE: case HEAP_NUMBER_TYPE:
// TODO(v8:11525): Handle possible endianness mismatch. // TODO(v8:11525): Handle possible endianness mismatch.
serializer.WriteUint32(ValueType::DOUBLE); serializer.WriteUint32(ValueType::DOUBLE);
serializer.WriteDouble(HeapNumber::cast(*object).value()); serializer.WriteDouble(HeapNumber::cast(*heap_object).value());
break; break;
case JS_FUNCTION_TYPE: case JS_FUNCTION_TYPE:
serializer.WriteUint32(ValueType::FUNCTION_ID); serializer.WriteUint32(ValueType::FUNCTION_ID);
serializer.WriteUint32(GetFunctionId(JSFunction::cast(*object))); serializer.WriteUint32(GetFunctionId(JSFunction::cast(*heap_object)));
break; break;
case JS_CLASS_CONSTRUCTOR_TYPE: case JS_CLASS_CONSTRUCTOR_TYPE:
serializer.WriteUint32(ValueType::CLASS_ID); serializer.WriteUint32(ValueType::CLASS_ID);
serializer.WriteUint32(GetClassId(JSFunction::cast(*object))); serializer.WriteUint32(GetClassId(JSFunction::cast(*heap_object)));
break; break;
case JS_OBJECT_TYPE: case JS_OBJECT_TYPE:
serializer.WriteUint32(ValueType::OBJECT_ID); serializer.WriteUint32(ValueType::OBJECT_ID);
serializer.WriteUint32(GetObjectId(JSObject::cast(*object))); serializer.WriteUint32(GetObjectId(JSObject::cast(*heap_object)));
break; break;
case JS_ARRAY_TYPE: case JS_ARRAY_TYPE:
serializer.WriteUint32(ValueType::ARRAY_ID); serializer.WriteUint32(ValueType::ARRAY_ID);
serializer.WriteUint32(GetArrayId(JSArray::cast(*object))); serializer.WriteUint32(GetArrayId(JSArray::cast(*heap_object)));
break; break;
case JS_REG_EXP_TYPE: { case JS_REG_EXP_TYPE: {
Handle<JSRegExp> regexp = Handle<JSRegExp>::cast(object); Handle<JSRegExp> regexp = Handle<JSRegExp>::cast(heap_object);
if (regexp->map() != isolate_->regexp_function()->initial_map()) { if (regexp->map() != isolate_->regexp_function()->initial_map()) {
Throw("Unsupported RegExp map"); Throw("Unsupported RegExp map");
return; return;
...@@ -1020,8 +1024,8 @@ void WebSnapshotSerializer::WriteValue(Handle<Object> object, ...@@ -1020,8 +1024,8 @@ void WebSnapshotSerializer::WriteValue(Handle<Object> object,
break; break;
} }
default: default:
if (object->IsString()) { if (heap_object->IsString()) {
SerializeString(Handle<String>::cast(object), id); SerializeString(Handle<String>::cast(heap_object), id);
serializer.WriteUint32(ValueType::STRING_ID); serializer.WriteUint32(ValueType::STRING_ID);
serializer.WriteUint32(id); serializer.WriteUint32(id);
} else { } else {
...@@ -1079,6 +1083,10 @@ uint32_t WebSnapshotSerializer::GetExternalId(HeapObject object) { ...@@ -1079,6 +1083,10 @@ uint32_t WebSnapshotSerializer::GetExternalId(HeapObject object) {
return static_cast<uint32_t>(id); return static_cast<uint32_t>(id);
} }
Handle<FixedArray> WebSnapshotSerializer::GetExternals() {
return external_objects_ids_.Values(isolate_);
}
WebSnapshotDeserializer::WebSnapshotDeserializer(v8::Isolate* isolate, WebSnapshotDeserializer::WebSnapshotDeserializer(v8::Isolate* isolate,
const uint8_t* data, const uint8_t* data,
size_t buffer_size) size_t buffer_size)
...@@ -1215,7 +1223,6 @@ bool WebSnapshotDeserializer::Deserialize( ...@@ -1215,7 +1223,6 @@ bool WebSnapshotDeserializer::Deserialize(
timer.Start(); timer.Start();
} }
if (!DeserializeSnapshot()) { if (!DeserializeSnapshot()) {
isolate_->ReportPendingMessages();
return false; return false;
} }
if (!DeserializeScript()) { if (!DeserializeScript()) {
...@@ -2051,8 +2058,8 @@ Object WebSnapshotDeserializer::ReadRegexp() { ...@@ -2051,8 +2058,8 @@ Object WebSnapshotDeserializer::ReadRegexp() {
Object WebSnapshotDeserializer::ReadExternalReference() { Object WebSnapshotDeserializer::ReadExternalReference() {
uint32_t ref_id; uint32_t ref_id;
if (!deserializer_.ReadUint32(&ref_id) || if (!deserializer_.ReadUint32(&ref_id) ||
ref_id > static_cast<uint32_t>(external_references_.length())) { ref_id >= static_cast<uint32_t>(external_references_.length())) {
Throw("Malformed class / function"); Throw("Invalid external reference");
return Smi::zero(); return Smi::zero();
} }
return external_references_.get(ref_id); return external_references_.get(ref_id);
......
...@@ -152,6 +152,12 @@ class V8_EXPORT WebSnapshotSerializer ...@@ -152,6 +152,12 @@ class V8_EXPORT WebSnapshotSerializer
return static_cast<uint32_t>(object_ids_.size()); return static_cast<uint32_t>(object_ids_.size());
} }
uint32_t external_objects_count() const {
return static_cast<uint32_t>(external_objects_ids_.size());
}
Handle<FixedArray> GetExternals();
private: private:
WebSnapshotSerializer(const WebSnapshotSerializer&) = delete; WebSnapshotSerializer(const WebSnapshotSerializer&) = delete;
WebSnapshotSerializer& operator=(const WebSnapshotSerializer&) = delete; WebSnapshotSerializer& operator=(const WebSnapshotSerializer&) = delete;
......
...@@ -15,7 +15,6 @@ const object = { ...@@ -15,7 +15,6 @@ const object = {
d: { d_a: external_2 } d: { d_a: external_2 }
}; };
(function testNoExternals() { (function testNoExternals() {
const snapshot = %WebSnapshotSerialize(object); const snapshot = %WebSnapshotSerialize(object);
const deserialized = %WebSnapshotDeserialize(snapshot); const deserialized = %WebSnapshotDeserialize(snapshot);
...@@ -55,3 +54,30 @@ const object = { ...@@ -55,3 +54,30 @@ const object = {
assertSame(deserialized.c[1], replaced_externals[1]); assertSame(deserialized.c[1], replaced_externals[1]);
assertSame(deserialized.d.d_a, replaced_externals[1]); assertSame(deserialized.d.d_a, replaced_externals[1]);
})(); })();
(function testApiObject() {
const api_object = new d8.dom.Div();
const source_1 = [{}, api_object];
assertThrows(() => %WebSnapshotSerialize(source_1));
let externals = [external_1]
const source_2 = [{}, external_1, api_object, api_object];
const snapshot_2 = %WebSnapshotSerialize(source_2, externals);
%HeapObjectVerify(externals);
// Check that the unhandled api object is added to the externals.
assertArrayEquals(externals, [external_1, api_object]);
assertThrows(() => %WebSnapshotDeserialize(snapshot_2));
assertThrows(() => %WebSnapshotDeserialize(snapshot_2, []));
assertThrows(() => %WebSnapshotDeserialize(snapshot_2, [external_1]));
const result_2 = %WebSnapshotDeserialize(snapshot_2, [external_1, api_object]);
%HeapObjectVerify(externals);
%HeapObjectVerify(result_2);
assertArrayEquals(result_2, source_2);
assertNotSame(result_2[0], source_2[0]);
assertSame(result_2[1], external_1);
assertSame(result_2[2], api_object);
assertSame(result_2[3], api_object);
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment