From ad5b005e38e78275a1bb8962ecb6cc048481ac82 Mon Sep 17 00:00:00 2001 From: Jakob Gruber Date: Tue, 28 Apr 2020 10:45:52 +0200 Subject: [PATCH] [snapshot] Expose the serializer through %SerializeDeserializeNow ... in order to exercise the snapshot/ component from mjsunit tests and fuzzers. * Since the serializer and deserializer can now be called at any time instead of only in a tightly controlled environment, several assumptions (such as an empty execution stack, no microtasks, no handles) no longer hold and had to be made configurable through SerializerFlags. * Root iteration now skips more root categories which were previously guaranteed to be empty (e.g. the stack, microtask queue, handles). * The %SerializeDeserializeNow runtime function triggers serialization, deserialization, and heap verification on the current isolate and native context. Support is not yet complete and will be extended in future work. Once all mjsunit tests successfully run, we can add a new test mode to stress serialization. Bug: v8:10416 Change-Id: Ie7ff441a761257dd7f256d0a33e73227850074ac Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2159495 Commit-Queue: Jakob Gruber Reviewed-by: Ulan Degenbaev Reviewed-by: Dan Elphick Cr-Commit-Position: refs/heads/master@{#67423} --- src/heap/heap.cc | 103 +++++++++++++--------- src/init/bootstrapper.h | 14 +++ src/runtime/runtime-test.cc | 17 ++++ src/runtime/runtime.h | 1 + src/snapshot/code-serializer.cc | 3 +- src/snapshot/context-serializer.cc | 20 +++-- src/snapshot/context-serializer.h | 3 +- src/snapshot/read-only-serializer.cc | 8 +- src/snapshot/read-only-serializer.h | 2 +- src/snapshot/roots-serializer.cc | 3 +- src/snapshot/roots-serializer.h | 3 +- src/snapshot/serializer.cc | 57 ++++++++---- src/snapshot/serializer.h | 19 +++- src/snapshot/snapshot.cc | 64 ++++++++++++-- src/snapshot/snapshot.h | 36 +++++++- src/snapshot/startup-serializer.cc | 6 +- src/snapshot/startup-serializer.h | 3 +- test/cctest/test-serialize.cc | 28 +++--- test/mjsunit/mjsunit.status | 4 + test/mjsunit/serialize-deserialize-now.js | 17 ++++ 20 files changed, 312 insertions(+), 99 deletions(-) create mode 100644 test/mjsunit/serialize-deserialize-now.js diff --git a/src/heap/heap.cc b/src/heap/heap.cc index b9f8a4e47e..8dc7e50fba 100644 --- a/src/heap/heap.cc +++ b/src/heap/heap.cc @@ -4456,11 +4456,6 @@ void Heap::IterateStrongRoots(RootVisitor* v, VisitMode mode) { isolate_->bootstrapper()->Iterate(v); v->Synchronize(VisitorSynchronization::kBootstrapper); - if (mode != VISIT_ONLY_STRONG_IGNORE_STACK) { - isolate_->Iterate(v); - isolate_->global_handles()->IterateStrongStackRoots(v); - v->Synchronize(VisitorSynchronization::kTop); - } Relocatable::Iterate(isolate_, v); v->Synchronize(VisitorSynchronization::kRelocatable); isolate_->debug()->Iterate(v); @@ -4469,22 +4464,6 @@ void Heap::IterateStrongRoots(RootVisitor* v, VisitMode mode) { isolate_->compilation_cache()->Iterate(v); v->Synchronize(VisitorSynchronization::kCompilationCache); - // Iterate over local handles in handle scopes. - FixStaleLeftTrimmedHandlesVisitor left_trim_visitor(this); - isolate_->handle_scope_implementer()->Iterate(&left_trim_visitor); - isolate_->handle_scope_implementer()->Iterate(v); - - if (FLAG_local_heaps) { - safepoint_->Iterate(&left_trim_visitor); - safepoint_->Iterate(v); - isolate_->persistent_handles_list()->Iterate(&left_trim_visitor); - isolate_->persistent_handles_list()->Iterate(v); - } - - isolate_->IterateDeferredHandles(&left_trim_visitor); - isolate_->IterateDeferredHandles(v); - v->Synchronize(VisitorSynchronization::kHandleScope); - // Iterate over the builtin code objects in the heap. Note that it is not // necessary to iterate over code objects on scavenge collections. if (!isMinorGC) { @@ -4516,17 +4495,6 @@ void Heap::IterateStrongRoots(RootVisitor* v, VisitMode mode) { } v->Synchronize(VisitorSynchronization::kGlobalHandles); - // Iterate over eternal handles. Eternal handles are not iterated by the - // serializer. Values referenced by eternal handles need to be added manually. - if (mode != VISIT_FOR_SERIALIZATION) { - if (isMinorGC) { - isolate_->eternal_handles()->IterateYoungRoots(v); - } else { - isolate_->eternal_handles()->IterateAllRoots(v); - } - } - v->Synchronize(VisitorSynchronization::kEternalHandles); - // Iterate over pointers being held by inactive threads. isolate_->thread_manager()->Iterate(v); v->Synchronize(VisitorSynchronization::kThreadManager); @@ -4537,18 +4505,67 @@ void Heap::IterateStrongRoots(RootVisitor* v, VisitMode mode) { } v->Synchronize(VisitorSynchronization::kStrongRoots); - // Iterate over pending Microtasks stored in MicrotaskQueues. - MicrotaskQueue* default_microtask_queue = isolate_->default_microtask_queue(); - if (default_microtask_queue) { - MicrotaskQueue* microtask_queue = default_microtask_queue; - do { - microtask_queue->IterateMicrotasks(v); - microtask_queue = microtask_queue->next(); - } while (microtask_queue != default_microtask_queue); - } - - // Iterate over the startup object cache unless serializing or deserializing. + // Visitors in this block only run when not serializing. These include: + // + // - Thread-local and stack. + // - Handles. + // - Microtasks. + // - The startup object cache. + // + // When creating real startup snapshot, these areas are expected to be empty. + // It is also possible to create a snapshot of a *running* isolate for testing + // purposes. In this case, these areas are likely not empty and will simply be + // skipped. + // + // The general guideline for adding visitors to this section vs. adding them + // above is that non-transient heap state is always visited, transient heap + // state is visited only when not serializing. if (mode != VISIT_FOR_SERIALIZATION) { + if (mode != VISIT_ONLY_STRONG_IGNORE_STACK) { + isolate_->Iterate(v); + isolate_->global_handles()->IterateStrongStackRoots(v); + v->Synchronize(VisitorSynchronization::kTop); + } + + // Iterate over local handles in handle scopes. + FixStaleLeftTrimmedHandlesVisitor left_trim_visitor(this); + isolate_->handle_scope_implementer()->Iterate(&left_trim_visitor); + isolate_->handle_scope_implementer()->Iterate(v); + + if (FLAG_local_heaps) { + safepoint_->Iterate(&left_trim_visitor); + safepoint_->Iterate(v); + isolate_->persistent_handles_list()->Iterate(&left_trim_visitor); + isolate_->persistent_handles_list()->Iterate(v); + } + + isolate_->IterateDeferredHandles(&left_trim_visitor); + isolate_->IterateDeferredHandles(v); + v->Synchronize(VisitorSynchronization::kHandleScope); + + // Iterate over eternal handles. Eternal handles are not iterated by the + // serializer. Values referenced by eternal handles need to be added + // manually. + if (isMinorGC) { + isolate_->eternal_handles()->IterateYoungRoots(v); + } else { + isolate_->eternal_handles()->IterateAllRoots(v); + } + v->Synchronize(VisitorSynchronization::kEternalHandles); + + // Iterate over pending Microtasks stored in MicrotaskQueues. + MicrotaskQueue* default_microtask_queue = + isolate_->default_microtask_queue(); + if (default_microtask_queue) { + MicrotaskQueue* microtask_queue = default_microtask_queue; + do { + microtask_queue->IterateMicrotasks(v); + microtask_queue = microtask_queue->next(); + } while (microtask_queue != default_microtask_queue); + } + + // Iterate over the startup object cache unless serializing or + // deserializing. SerializerDeserializer::Iterate(isolate_, v); v->Synchronize(VisitorSynchronization::kStartupObjectCache); } diff --git a/src/init/bootstrapper.h b/src/init/bootstrapper.h index aaa1997270..e51ef0cd10 100644 --- a/src/init/bootstrapper.h +++ b/src/init/bootstrapper.h @@ -55,6 +55,20 @@ class Bootstrapper final { v8::DeserializeEmbedderFieldsCallback embedder_fields_deserializer, v8::MicrotaskQueue* microtask_queue); + // Used for testing context deserialization. No code runs in the generated + // context. It only needs to pass heap verification. + Handle CreateEnvironmentForTesting() { + MaybeHandle no_global_proxy; + v8::Local no_global_object_template; + ExtensionConfiguration no_extensions; + static constexpr int kDefaultContextIndex = 0; + v8::DeserializeEmbedderFieldsCallback no_callback; + v8::MicrotaskQueue* no_microtask_queue = nullptr; + return CreateEnvironment(no_global_proxy, no_global_object_template, + &no_extensions, kDefaultContextIndex, no_callback, + no_microtask_queue); + } + Handle NewRemoteContext( MaybeHandle maybe_global_proxy, v8::Local global_object_template); diff --git a/src/runtime/runtime-test.cc b/src/runtime/runtime-test.cc index 6f8c7e3227..2078a7ca9e 100644 --- a/src/runtime/runtime-test.cc +++ b/src/runtime/runtime-test.cc @@ -28,6 +28,7 @@ #include "src/objects/js-array-inl.h" #include "src/objects/js-regexp-inl.h" #include "src/objects/smi.h" +#include "src/snapshot/snapshot.h" #include "src/trap-handler/trap-handler.h" #include "src/utils/ostreams.h" #include "src/wasm/memory-tracing.h" @@ -1187,6 +1188,22 @@ RUNTIME_FUNCTION(Runtime_StringIteratorProtector) { Protectors::IsStringIteratorLookupChainIntact(isolate)); } +// For use by tests and fuzzers. It +// +// 1. serializes a snapshot of the current isolate, +// 2. deserializes the snapshot, +// 3. and runs VerifyHeap on the resulting isolate. +// +// The current isolate should not be modified by this call and can keep running +// once it completes. +RUNTIME_FUNCTION(Runtime_SerializeDeserializeNow) { + HandleScope scope(isolate); + DCHECK_EQ(0, args.length()); + Snapshot::SerializeDeserializeAndVerifyForTesting(isolate, + isolate->native_context()); + return ReadOnlyRoots(isolate).undefined_value(); +} + // Take a compiled wasm module and serialize it into an array buffer, which is // then returned. RUNTIME_FUNCTION(Runtime_SerializeWasmModule) { diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index dc563aef4f..8cf2b91b74 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -522,6 +522,7 @@ namespace internal { F(RedirectToWasmInterpreter, 2, 1) \ F(RunningInSimulator, 0, 1) \ F(RuntimeEvaluateREPL, 1, 1) \ + F(SerializeDeserializeNow, 0, 1) \ F(SerializeWasmModule, 1, 1) \ F(SetAllocationTimeout, -1 /* 2 || 3 */, 1) \ F(SetForceSlowPath, 1, 1) \ diff --git a/src/snapshot/code-serializer.cc b/src/snapshot/code-serializer.cc index bb6c58504c..eca334d4a9 100644 --- a/src/snapshot/code-serializer.cc +++ b/src/snapshot/code-serializer.cc @@ -32,7 +32,8 @@ ScriptData::ScriptData(const byte* data, int length) } CodeSerializer::CodeSerializer(Isolate* isolate, uint32_t source_hash) - : Serializer(isolate), source_hash_(source_hash) { + : Serializer(isolate, Snapshot::kDefaultSerializerFlags), + source_hash_(source_hash) { allocator()->UseCustomChunkSize(FLAG_serialization_chunk_size); } diff --git a/src/snapshot/context-serializer.cc b/src/snapshot/context-serializer.cc index af3f685795..d97241e9fa 100644 --- a/src/snapshot/context-serializer.cc +++ b/src/snapshot/context-serializer.cc @@ -16,9 +16,10 @@ namespace v8 { namespace internal { ContextSerializer::ContextSerializer( - Isolate* isolate, StartupSerializer* startup_serializer, + Isolate* isolate, Snapshot::SerializerFlags flags, + StartupSerializer* startup_serializer, v8::SerializeEmbedderFieldsCallback callback) - : Serializer(isolate), + : Serializer(isolate, flags), startup_serializer_(startup_serializer), serialize_embedder_fields_(callback), can_be_rehashed_(true) { @@ -46,12 +47,14 @@ void ContextSerializer::Serialize(Context* o, bool include_global_proxy) { // Reset math random cache to get fresh random numbers. MathRandom::ResetContext(context_); -#ifdef DEBUG MicrotaskQueue* microtask_queue = context_.native_context().microtask_queue(); - DCHECK_EQ(0, microtask_queue->size()); - DCHECK(!microtask_queue->HasMicrotasksSuppressions()); - DCHECK_EQ(0, microtask_queue->GetMicrotasksScopeDepth()); - DCHECK(microtask_queue->DebugMicrotasksScopeDepthIsZero()); +#ifdef DEBUG + if (!allow_microtasks_for_testing()) { + DCHECK_EQ(0, microtask_queue->size()); + DCHECK(!microtask_queue->HasMicrotasksSuppressions()); + DCHECK_EQ(0, microtask_queue->GetMicrotasksScopeDepth()); + DCHECK(microtask_queue->DebugMicrotasksScopeDepthIsZero()); + } #endif context_.native_context().set_microtask_queue(nullptr); @@ -66,6 +69,9 @@ void ContextSerializer::Serialize(Context* o, bool include_global_proxy) { } Pad(); + + // Restore the microtask queue. + context_.native_context().set_microtask_queue(microtask_queue); } void ContextSerializer::SerializeObject(HeapObject obj) { diff --git a/src/snapshot/context-serializer.h b/src/snapshot/context-serializer.h index 250c948e73..95ab7bbcaf 100644 --- a/src/snapshot/context-serializer.h +++ b/src/snapshot/context-serializer.h @@ -16,7 +16,8 @@ class StartupSerializer; class V8_EXPORT_PRIVATE ContextSerializer : public Serializer { public: - ContextSerializer(Isolate* isolate, StartupSerializer* startup_serializer, + ContextSerializer(Isolate* isolate, Snapshot::SerializerFlags flags, + StartupSerializer* startup_serializer, v8::SerializeEmbedderFieldsCallback callback); ~ContextSerializer() override; diff --git a/src/snapshot/read-only-serializer.cc b/src/snapshot/read-only-serializer.cc index 41e6188154..54c23c42ab 100644 --- a/src/snapshot/read-only-serializer.cc +++ b/src/snapshot/read-only-serializer.cc @@ -16,8 +16,9 @@ namespace v8 { namespace internal { -ReadOnlySerializer::ReadOnlySerializer(Isolate* isolate) - : RootsSerializer(isolate, RootIndex::kFirstReadOnlyRoot) { +ReadOnlySerializer::ReadOnlySerializer(Isolate* isolate, + Snapshot::SerializerFlags flags) + : RootsSerializer(isolate, flags, RootIndex::kFirstReadOnlyRoot) { STATIC_ASSERT(RootIndex::kFirstReadOnlyRoot == RootIndex::kFirstRoot); allocator()->UseCustomChunkSize(FLAG_serialization_chunk_size); } @@ -50,7 +51,8 @@ void ReadOnlySerializer::SerializeReadOnlyRoots() { // No active threads. CHECK_NULL(isolate()->thread_manager()->FirstThreadStateInUse()); // No active or weak handles. - CHECK(isolate()->handle_scope_implementer()->blocks()->empty()); + CHECK_IMPLIES(!allow_open_handles_for_testing(), + isolate()->handle_scope_implementer()->blocks()->empty()); ReadOnlyRoots(isolate()).Iterate(this); } diff --git a/src/snapshot/read-only-serializer.h b/src/snapshot/read-only-serializer.h index c73c397647..f30b2c30ba 100644 --- a/src/snapshot/read-only-serializer.h +++ b/src/snapshot/read-only-serializer.h @@ -17,7 +17,7 @@ class SnapshotByteSink; class V8_EXPORT_PRIVATE ReadOnlySerializer : public RootsSerializer { public: - explicit ReadOnlySerializer(Isolate* isolate); + ReadOnlySerializer(Isolate* isolate, Snapshot::SerializerFlags flags); ~ReadOnlySerializer() override; void SerializeReadOnlyRoots(); diff --git a/src/snapshot/roots-serializer.cc b/src/snapshot/roots-serializer.cc index a48f6e76f5..6a8f2bb05e 100644 --- a/src/snapshot/roots-serializer.cc +++ b/src/snapshot/roots-serializer.cc @@ -13,8 +13,9 @@ namespace v8 { namespace internal { RootsSerializer::RootsSerializer(Isolate* isolate, + Snapshot::SerializerFlags flags, RootIndex first_root_to_be_serialized) - : Serializer(isolate), + : Serializer(isolate, flags), first_root_to_be_serialized_(first_root_to_be_serialized), can_be_rehashed_(true) { for (size_t i = 0; i < static_cast(first_root_to_be_serialized); diff --git a/src/snapshot/roots-serializer.h b/src/snapshot/roots-serializer.h index cfb59dd75e..be41d7220f 100644 --- a/src/snapshot/roots-serializer.h +++ b/src/snapshot/roots-serializer.h @@ -24,7 +24,8 @@ class RootsSerializer : public Serializer { public: // The serializer expects that all roots before |first_root_to_be_serialized| // are already serialized. - RootsSerializer(Isolate* isolate, RootIndex first_root_to_be_serialized); + RootsSerializer(Isolate* isolate, Snapshot::SerializerFlags flags, + RootIndex first_root_to_be_serialized); bool can_be_rehashed() const { return can_be_rehashed_; } bool root_has_been_serialized(RootIndex root_index) const { diff --git a/src/snapshot/serializer.cc b/src/snapshot/serializer.cc index b8e3ee291b..bfb3b8ecf6 100644 --- a/src/snapshot/serializer.cc +++ b/src/snapshot/serializer.cc @@ -14,15 +14,15 @@ #include "src/objects/map.h" #include "src/objects/slots-inl.h" #include "src/objects/smi.h" -#include "src/snapshot/snapshot.h" namespace v8 { namespace internal { -Serializer::Serializer(Isolate* isolate) +Serializer::Serializer(Isolate* isolate, Snapshot::SerializerFlags flags) : isolate_(isolate), external_reference_encoder_(isolate), root_index_map_(isolate), + flags_(flags), allocator_(this) { #ifdef OBJECT_PRINT if (FLAG_serialization_statistics) { @@ -718,32 +718,53 @@ void Serializer::ObjectSerializer::VisitEmbeddedPointer(Code host, bytes_processed_so_far_ += rinfo->target_address_size(); } -void Serializer::ObjectSerializer::VisitExternalReference(Foreign host, - Address* p) { - auto encoded_reference = - serializer_->EncodeExternalReference(host.foreign_address()); - if (encoded_reference.is_from_api()) { +void Serializer::ObjectSerializer::OutputExternalReference(Address target, + int target_size) { + DCHECK_LE(target_size, sizeof(target)); // Must fit in Address. + ExternalReferenceEncoder::Value encoded_reference; + bool encoded_successfully; + + if (serializer_->allow_unknown_external_references_for_testing()) { + encoded_successfully = + serializer_->TryEncodeExternalReference(target).To(&encoded_reference); + } else { + encoded_reference = serializer_->EncodeExternalReference(target); + encoded_successfully = true; + } + + if (!encoded_successfully) { + // In this case the serialized snapshot will not be used in a different + // Isolate and thus the target address will not change between + // serialization and deserialization. We can serialize seen external + // references verbatim. + CHECK(serializer_->allow_unknown_external_references_for_testing()); + CHECK(IsAligned(target_size, kObjectAlignment)); + CHECK_LE(target_size, kNumberOfFixedRawData * kTaggedSize); + int size_in_tagged = target_size >> kTaggedSizeLog2; + sink_->PutSection(kFixedRawDataStart + size_in_tagged, "FixedRawData"); + sink_->PutRaw(reinterpret_cast(&target), target_size, "Bytes"); + } else if (encoded_reference.is_from_api()) { sink_->Put(kApiReference, "ApiRef"); + sink_->PutInt(encoded_reference.index(), "reference index"); } else { sink_->Put(kExternalReference, "ExternalRef"); + sink_->PutInt(encoded_reference.index(), "reference index"); } - sink_->PutInt(encoded_reference.index(), "reference index"); - bytes_processed_so_far_ += kSystemPointerSize; + bytes_processed_so_far_ += target_size; +} + +void Serializer::ObjectSerializer::VisitExternalReference(Foreign host, + Address* p) { + OutputExternalReference(host.foreign_address(), kSystemPointerSize); } void Serializer::ObjectSerializer::VisitExternalReference(Code host, RelocInfo* rinfo) { Address target = rinfo->target_external_reference(); - auto encoded_reference = serializer_->EncodeExternalReference(target); - if (encoded_reference.is_from_api()) { - DCHECK(!rinfo->IsCodedSpecially()); - sink_->Put(kApiReference, "ApiRef"); - } else { - sink_->Put(kExternalReference, "ExternalRef"); - } DCHECK_NE(target, kNullAddress); // Code does not reference null. - sink_->PutInt(encoded_reference.index(), "reference index"); - bytes_processed_so_far_ += rinfo->target_address_size(); + DCHECK_IMPLIES(serializer_->EncodeExternalReference(target).is_from_api(), + !rinfo->IsCodedSpecially()); + OutputExternalReference(target, rinfo->target_address_size()); } void Serializer::ObjectSerializer::VisitInternalReference(Code host, diff --git a/src/snapshot/serializer.h b/src/snapshot/serializer.h index 8abdfec48c..cb6b25b89b 100644 --- a/src/snapshot/serializer.h +++ b/src/snapshot/serializer.h @@ -15,6 +15,7 @@ #include "src/snapshot/serializer-allocator.h" #include "src/snapshot/serializer-deserializer.h" #include "src/snapshot/snapshot-source-sink.h" +#include "src/snapshot/snapshot.h" namespace v8 { namespace internal { @@ -158,7 +159,7 @@ class ObjectCacheIndexMap { class Serializer : public SerializerDeserializer { public: - explicit Serializer(Isolate* isolate); + Serializer(Isolate* isolate, Snapshot::SerializerFlags flags); std::vector EncodeReservations() const { return allocator_.EncodeReservations(); @@ -224,6 +225,10 @@ class Serializer : public SerializerDeserializer { ExternalReferenceEncoder::Value EncodeExternalReference(Address addr) { return external_reference_encoder_.Encode(addr); } + Maybe TryEncodeExternalReference( + Address addr) { + return external_reference_encoder_.TryEncode(addr); + } // GetInt reads 4 bytes at once, requiring padding at the end. // Use padding_offset to specify the space you want to use after padding. @@ -260,6 +265,16 @@ class Serializer : public SerializerDeserializer { SnapshotByteSink sink_; // Used directly by subclasses. + bool allow_unknown_external_references_for_testing() const { + return (flags_ & Snapshot::kAllowUnknownExternalReferencesForTesting) != 0; + } + bool allow_open_handles_for_testing() const { + return (flags_ & Snapshot::kAllowOpenHandlesForTesting) != 0; + } + bool allow_microtasks_for_testing() const { + return (flags_ & Snapshot::kAllowMicrotasksForTesting) != 0; + } + private: Isolate* isolate_; SerializerReferenceMap reference_map_; @@ -269,6 +284,7 @@ class Serializer : public SerializerDeserializer { std::vector code_buffer_; std::vector deferred_objects_; // To handle stack overflow. int recursion_depth_ = 0; + const Snapshot::SerializerFlags flags_; SerializerAllocator allocator_; #ifdef OBJECT_PRINT @@ -327,6 +343,7 @@ class Serializer::ObjectSerializer : public ObjectVisitor { // This function outputs or skips the raw data between the last pointer and // up to the current position. void SerializeContent(Map map, int size); + void OutputExternalReference(Address target, int target_size); void OutputRawData(Address up_to); void OutputCode(int size); uint32_t SerializeBackingStore(void* backing_store, int32_t byte_length); diff --git a/src/snapshot/snapshot.cc b/src/snapshot/snapshot.cc index 3ac0b4fc93..1cc7d0959e 100644 --- a/src/snapshot/snapshot.cc +++ b/src/snapshot/snapshot.cc @@ -7,6 +7,8 @@ #include "src/snapshot/snapshot.h" #include "src/base/platform/platform.h" +#include "src/execution/isolate-inl.h" +#include "src/init/bootstrapper.h" #include "src/logging/counters.h" #include "src/snapshot/context-deserializer.h" #include "src/snapshot/context-serializer.h" @@ -186,6 +188,52 @@ MaybeHandle Snapshot::NewContextFromSnapshot( return result; } +// static +void Snapshot::SerializeDeserializeAndVerifyForTesting( + Isolate* isolate, Handle default_context) { + StartupData serialized_data; + std::unique_ptr auto_delete_serialized_data; + + isolate->heap()->CollectAllAvailableGarbage( + i::GarbageCollectionReason::kSnapshotCreator); + + // Test serialization. + { + DisallowHeapAllocation no_gc; + + Snapshot::SerializerFlags flags( + Snapshot::kAllowUnknownExternalReferencesForTesting | + Snapshot::kAllowOpenHandlesForTesting | + Snapshot::kAllowMicrotasksForTesting); + serialized_data = + Snapshot::Create(isolate, *default_context, &no_gc, flags); + auto_delete_serialized_data.reset(serialized_data.data); + } + + // Test deserialization. + Isolate* new_isolate = Isolate::New(); + { + // Set serializer_enabled() to not install extensions and experimental + // natives on the new isolate. + // TODO(v8:10416): This should be a separate setting on the isolate. + new_isolate->enable_serializer(); + new_isolate->Enter(); + new_isolate->set_snapshot_blob(&serialized_data); + CHECK(Snapshot::Initialize(new_isolate)); + + HandleScope scope(new_isolate); + Handle new_native_context = + new_isolate->bootstrapper()->CreateEnvironmentForTesting(); + CHECK(new_native_context->IsNativeContext()); + +#ifdef VERIFY_HEAP + new_isolate->heap()->Verify(); +#endif // VERIFY_HEAP + } + new_isolate->Exit(); + Isolate::Delete(new_isolate); +} + void ProfileDeserialization( const SnapshotData* read_only_snapshot, const SnapshotData* startup_snapshot, @@ -210,19 +258,22 @@ void ProfileDeserialization( } } +// static +constexpr Snapshot::SerializerFlags Snapshot::kDefaultSerializerFlags; + // static v8::StartupData Snapshot::Create( Isolate* isolate, std::vector* contexts, const std::vector& embedder_fields_serializers, - const DisallowHeapAllocation* no_gc) { + const DisallowHeapAllocation* no_gc, SerializerFlags flags) { DCHECK_EQ(contexts->size(), embedder_fields_serializers.size()); DCHECK_GT(contexts->size(), 0); - ReadOnlySerializer read_only_serializer(isolate); + ReadOnlySerializer read_only_serializer(isolate, flags); read_only_serializer.SerializeReadOnlyRoots(); - StartupSerializer startup_serializer(isolate, &read_only_serializer); + StartupSerializer startup_serializer(isolate, flags, &read_only_serializer); startup_serializer.SerializeStrongReferences(); // Serialize each context with a new serializer. @@ -236,7 +287,7 @@ v8::StartupData Snapshot::Create( for (int i = 0; i < num_contexts; i++) { const bool is_default_context = (i == 0); const bool include_global_proxy = !is_default_context; - ContextSerializer context_serializer(isolate, &startup_serializer, + ContextSerializer context_serializer(isolate, flags, &startup_serializer, embedder_fields_serializers[i]); context_serializer.Serialize(&contexts->at(i), include_global_proxy); can_be_rehashed = can_be_rehashed && context_serializer.can_be_rehashed(); @@ -265,10 +316,11 @@ v8::StartupData Snapshot::Create( // static v8::StartupData Snapshot::Create(Isolate* isolate, Context default_context, - const DisallowHeapAllocation* no_gc) { + const DisallowHeapAllocation* no_gc, + SerializerFlags flags) { std::vector contexts{default_context}; std::vector callbacks{{}}; - return Snapshot::Create(isolate, &contexts, callbacks, no_gc); + return Snapshot::Create(isolate, &contexts, callbacks, no_gc, flags); } v8::StartupData SnapshotImpl::CreateSnapshotBlob( diff --git a/src/snapshot/snapshot.h b/src/snapshot/snapshot.h index 6d740e5c25..a71d824bb4 100644 --- a/src/snapshot/snapshot.h +++ b/src/snapshot/snapshot.h @@ -21,6 +21,24 @@ class Snapshot : public AllStatic { public: // ---------------- Serialization ------------------------------------------- + enum SerializerFlag { + // If set, serializes unknown external references as verbatim data. This + // usually leads to invalid state if the snapshot is deserialized in a + // different isolate or a different process. + // If unset, all external references must be known to the encoder. + kAllowUnknownExternalReferencesForTesting = 1 << 0, + // If set, serialization can succeed even with open handles. The + // contents of open handle scopes are *not* serialized. + // If unset, no open handles are allowed to ensure the snapshot + // contains no unexpected objects. + kAllowOpenHandlesForTesting = 1 << 1, + // As above, if set we allow but do *not* serialize existing microtasks. + // If unset, the microtask queue must be empty. + kAllowMicrotasksForTesting = 1 << 2, + }; + using SerializerFlags = base::Flags; + static constexpr SerializerFlags kDefaultSerializerFlags = {}; + // Serializes the given isolate and contexts. Each context may have an // associated callback to serialize internal fields. The default context must // be passed at index 0. @@ -28,11 +46,14 @@ class Snapshot : public AllStatic { Isolate* isolate, std::vector* contexts, const std::vector& embedder_fields_serializers, - const DisallowHeapAllocation* no_gc); + const DisallowHeapAllocation* no_gc, + SerializerFlags flags = kDefaultSerializerFlags); // Convenience helper for the above when only serializing a single context. - static v8::StartupData Create(Isolate* isolate, Context default_context, - const DisallowHeapAllocation* no_gc); + static v8::StartupData Create( + Isolate* isolate, Context default_context, + const DisallowHeapAllocation* no_gc, + SerializerFlags flags = kDefaultSerializerFlags); // ---------------- Deserialization ----------------------------------------- @@ -46,6 +67,15 @@ class Snapshot : public AllStatic { size_t context_index, v8::DeserializeEmbedderFieldsCallback embedder_fields_deserializer); + // ---------------- Testing ------------------------------------------------- + + // This function is used to stress the snapshot component. It serializes the + // current isolate and context into a snapshot, deserializes the snapshot into + // a new isolate and context, and finally runs VerifyHeap on the fresh + // isolate. + static void SerializeDeserializeAndVerifyForTesting( + Isolate* isolate, Handle default_context); + // ---------------- Helper methods ------------------------------------------ static bool HasContextSnapshot(Isolate* isolate, size_t index); diff --git a/src/snapshot/startup-serializer.cc b/src/snapshot/startup-serializer.cc index 22e2f2195b..22cfe4e3e5 100644 --- a/src/snapshot/startup-serializer.cc +++ b/src/snapshot/startup-serializer.cc @@ -20,8 +20,9 @@ namespace v8 { namespace internal { StartupSerializer::StartupSerializer(Isolate* isolate, + Snapshot::SerializerFlags flags, ReadOnlySerializer* read_only_serializer) - : RootsSerializer(isolate, RootIndex::kFirstStrongRoot), + : RootsSerializer(isolate, flags, RootIndex::kFirstStrongRoot), read_only_serializer_(read_only_serializer) { allocator()->UseCustomChunkSize(FLAG_serialization_chunk_size); InitializeCodeAddressMap(); @@ -141,7 +142,8 @@ void StartupSerializer::SerializeStrongReferences() { // No active threads. CHECK_NULL(isolate->thread_manager()->FirstThreadStateInUse()); // No active or weak handles. - CHECK(isolate->handle_scope_implementer()->blocks()->empty()); + CHECK_IMPLIES(!allow_open_handles_for_testing(), + isolate->handle_scope_implementer()->blocks()->empty()); // Visit smi roots and immortal immovables first to make sure they end up in // the first page. diff --git a/src/snapshot/startup-serializer.h b/src/snapshot/startup-serializer.h index 5f481c820f..5452daa557 100644 --- a/src/snapshot/startup-serializer.h +++ b/src/snapshot/startup-serializer.h @@ -18,7 +18,8 @@ class ReadOnlySerializer; class V8_EXPORT_PRIVATE StartupSerializer : public RootsSerializer { public: - StartupSerializer(Isolate* isolate, ReadOnlySerializer* read_only_serializer); + StartupSerializer(Isolate* isolate, Snapshot::SerializerFlags flags, + ReadOnlySerializer* read_only_serializer); ~StartupSerializer() override; // Serialize the current state of the heap. The order is: diff --git a/test/cctest/test-serialize.cc b/test/cctest/test-serialize.cc index f408fa505f..5d8310b127 100644 --- a/test/cctest/test-serialize.cc +++ b/test/cctest/test-serialize.cc @@ -172,10 +172,12 @@ static StartupBlobs Serialize(v8::Isolate* isolate) { internal_isolate->heap()->CollectAllAvailableGarbage( i::GarbageCollectionReason::kTesting); - ReadOnlySerializer read_only_serializer(internal_isolate); + ReadOnlySerializer read_only_serializer(internal_isolate, + Snapshot::kDefaultSerializerFlags); read_only_serializer.SerializeReadOnlyRoots(); - StartupSerializer ser(internal_isolate, &read_only_serializer); + StartupSerializer ser(internal_isolate, Snapshot::kDefaultSerializerFlags, + &read_only_serializer); ser.SerializeStrongReferences(); ser.SerializeWeakReferencesAndDeferred(); @@ -385,16 +387,19 @@ static void SerializeContext(Vector* startup_blob_out, env.Reset(); SnapshotByteSink read_only_sink; - ReadOnlySerializer read_only_serializer(isolate); + ReadOnlySerializer read_only_serializer(isolate, + Snapshot::kDefaultSerializerFlags); read_only_serializer.SerializeReadOnlyRoots(); SnapshotByteSink startup_sink; - StartupSerializer startup_serializer(isolate, &read_only_serializer); + StartupSerializer startup_serializer( + isolate, Snapshot::kDefaultSerializerFlags, &read_only_serializer); startup_serializer.SerializeStrongReferences(); SnapshotByteSink context_sink; - ContextSerializer context_serializer(isolate, &startup_serializer, - v8::SerializeInternalFieldsCallback()); + ContextSerializer context_serializer( + isolate, Snapshot::kDefaultSerializerFlags, &startup_serializer, + v8::SerializeInternalFieldsCallback()); context_serializer.Serialize(&raw_context, false); startup_serializer.SerializeWeakReferencesAndDeferred(); @@ -532,16 +537,19 @@ static void SerializeCustomContext(Vector* startup_blob_out, env.Reset(); SnapshotByteSink read_only_sink; - ReadOnlySerializer read_only_serializer(isolate); + ReadOnlySerializer read_only_serializer(isolate, + Snapshot::kDefaultSerializerFlags); read_only_serializer.SerializeReadOnlyRoots(); SnapshotByteSink startup_sink; - StartupSerializer startup_serializer(isolate, &read_only_serializer); + StartupSerializer startup_serializer( + isolate, Snapshot::kDefaultSerializerFlags, &read_only_serializer); startup_serializer.SerializeStrongReferences(); SnapshotByteSink context_sink; - ContextSerializer context_serializer(isolate, &startup_serializer, - v8::SerializeInternalFieldsCallback()); + ContextSerializer context_serializer( + isolate, Snapshot::kDefaultSerializerFlags, &startup_serializer, + v8::SerializeInternalFieldsCallback()); context_serializer.Serialize(&raw_context, false); startup_serializer.SerializeWeakReferencesAndDeferred(); diff --git a/test/mjsunit/mjsunit.status b/test/mjsunit/mjsunit.status index 4a1619f249..dd83beab74 100644 --- a/test/mjsunit/mjsunit.status +++ b/test/mjsunit/mjsunit.status @@ -65,6 +65,10 @@ # BUG(v8:10197) 'regress/regress-748069': [SKIP], + # https://crbug.com/1043058 + # Enable once serializing a running isolate is fully implemented. + 'serialize-deserialize-now': [SKIP], + ############################################################################## # Tests where variants make no sense. 'd8/enable-tracing': [PASS, NO_VARIANTS], diff --git a/test/mjsunit/serialize-deserialize-now.js b/test/mjsunit/serialize-deserialize-now.js new file mode 100644 index 0000000000..b906328eff --- /dev/null +++ b/test/mjsunit/serialize-deserialize-now.js @@ -0,0 +1,17 @@ +// Copyright 2020 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. +// +// Flags: --allow-natives-syntax + +%SerializeDeserializeNow(); + +const xs = [0, 1, 2]; +var o = { a: 0, b: 1, c: 2 }; + +%SerializeDeserializeNow(); + +const p = new Promise((resolve, reject) => { resolve("Promise"); }); +p.then((msg) => console.log(msg)); + +%SerializeDeserializeNow();