// Copyright 2015 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 #include #include "src/api/api-inl.h" #include "src/objects/objects-inl.h" #include "src/snapshot/code-serializer.h" #include "src/utils/version.h" #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-module-builder.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects-inl.h" #include "src/wasm/wasm-opcodes.h" #include "src/wasm/wasm-serialization.h" #include "test/cctest/cctest.h" #include "test/common/wasm/flag-utils.h" #include "test/common/wasm/test-signatures.h" #include "test/common/wasm/wasm-macro-gen.h" #include "test/common/wasm/wasm-module-runner.h" namespace v8 { namespace internal { namespace wasm { namespace test_wasm_serialization { // Approximate gtest TEST_F style, in case we adopt gtest. class WasmSerializationTest { public: WasmSerializationTest() : zone_(&allocator_, ZONE_NAME) { // Don't call here if we move to gtest. SetUp(); } static void BuildWireBytes(Zone* zone, ZoneBuffer* buffer) { WasmModuleBuilder* builder = zone->New(zone); TestSignatures sigs; WasmFunctionBuilder* f = builder->AddFunction(sigs.i_i()); byte code[] = {WASM_GET_LOCAL(0), kExprI32Const, 1, kExprI32Add, kExprEnd}; f->EmitCode(code, sizeof(code)); builder->AddExport(CStrVector(kFunctionName), f); builder->WriteTo(buffer); } void ClearSerializedData() { serialized_bytes_ = {nullptr, 0}; } void InvalidateVersion() { uint32_t* slot = reinterpret_cast( const_cast(serialized_bytes_.data()) + WasmSerializer::kVersionHashOffset); *slot = Version::Hash() + 1; } void InvalidateWireBytes() { memset(const_cast(wire_bytes_.data()), 0, wire_bytes_.size() / 2); } void InvalidateNumFunctions() { Address num_functions_slot = reinterpret_cast
(serialized_bytes_.data()) + WasmSerializer::kHeaderSize; CHECK_EQ(1, base::ReadUnalignedValue(num_functions_slot)); base::WriteUnalignedValue(num_functions_slot, 0); } MaybeHandle Deserialize( Vector source_url = {}) { return DeserializeNativeModule(CcTest::i_isolate(), VectorOf(serialized_bytes_), VectorOf(wire_bytes_), source_url); } void DeserializeAndRun() { ErrorThrower thrower(CcTest::i_isolate(), ""); Handle module_object; CHECK(Deserialize().ToHandle(&module_object)); { DisallowHeapAllocation assume_no_gc; Vector deserialized_module_wire_bytes = module_object->native_module()->wire_bytes(); CHECK_EQ(deserialized_module_wire_bytes.size(), wire_bytes_.size()); CHECK_EQ(memcmp(deserialized_module_wire_bytes.begin(), wire_bytes_.data(), wire_bytes_.size()), 0); } Handle instance = CcTest::i_isolate() ->wasm_engine() ->SyncInstantiate(CcTest::i_isolate(), &thrower, module_object, Handle::null(), MaybeHandle()) .ToHandleChecked(); Handle params[1] = { Handle(Smi::FromInt(41), CcTest::i_isolate())}; int32_t result = testing::CallWasmFunctionForTesting( CcTest::i_isolate(), instance, &thrower, kFunctionName, 1, params); CHECK_EQ(42, result); } void CollectGarbage() { // Try hard to collect all garbage and will therefore also invoke all weak // callbacks of actually unreachable persistent handles. CcTest::i_isolate()->heap()->CollectAllAvailableGarbage( GarbageCollectionReason::kTesting); } private: static const char* kFunctionName; Zone* zone() { return &zone_; } void SetUp() { CcTest::InitIsolateOnce(); ZoneBuffer buffer(&zone_); WasmSerializationTest::BuildWireBytes(zone(), &buffer); v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::i_isolate()->array_buffer_allocator(); v8::Isolate* serialization_v8_isolate = v8::Isolate::New(create_params); Isolate* serialization_isolate = reinterpret_cast(serialization_v8_isolate); ErrorThrower thrower(serialization_isolate, ""); // Keep a weak pointer so we can check that the native module dies after // serialization (when the isolate is disposed). std::weak_ptr weak_native_module; { HandleScope scope(serialization_isolate); v8::Local serialization_context = v8::Context::New(serialization_v8_isolate); serialization_context->Enter(); auto enabled_features = WasmFeatures::FromIsolate(serialization_isolate); MaybeHandle maybe_module_object = serialization_isolate->wasm_engine()->SyncCompile( serialization_isolate, enabled_features, &thrower, ModuleWireBytes(buffer.begin(), buffer.end())); Handle module_object = maybe_module_object.ToHandleChecked(); weak_native_module = module_object->shared_native_module(); // Check that the native module exists at this point. CHECK(weak_native_module.lock()); v8::Local v8_module_obj = v8::Utils::ToLocal(Handle::cast(module_object)); CHECK(v8_module_obj->IsWasmModuleObject()); v8::Local v8_module_object = v8_module_obj.As(); v8::CompiledWasmModule compiled_module = v8_module_object->GetCompiledModule(); v8::MemorySpan uncompiled_bytes = compiled_module.GetWireBytesRef(); uint8_t* bytes_copy = zone()->NewArray(uncompiled_bytes.size()); memcpy(bytes_copy, uncompiled_bytes.data(), uncompiled_bytes.size()); wire_bytes_ = {bytes_copy, uncompiled_bytes.size()}; // keep alive data_ until the end data_ = compiled_module.Serialize(); } // Dispose of serialization isolate to destroy the reference to the // NativeModule, which removes it from the module cache in the wasm engine // and forces de-serialization in the new isolate. serialization_v8_isolate->Dispose(); // Busy-wait for the NativeModule to really die. Background threads might // temporarily keep it alive (happens very rarely, see // https://crbug.com/v8/10148). while (weak_native_module.lock()) { } serialized_bytes_ = {data_.buffer.get(), data_.size}; v8::HandleScope new_scope(CcTest::isolate()); v8::Local deserialization_context = v8::Context::New(CcTest::isolate()); deserialization_context->Enter(); } v8::internal::AccountingAllocator allocator_; Zone zone_; v8::OwnedBuffer data_; v8::MemorySpan wire_bytes_ = {nullptr, 0}; v8::MemorySpan serialized_bytes_ = {nullptr, 0}; }; const char* WasmSerializationTest::kFunctionName = "increment"; TEST(DeserializeValidModule) { WasmSerializationTest test; { HandleScope scope(CcTest::i_isolate()); test.DeserializeAndRun(); } test.CollectGarbage(); } TEST(DeserializeWithSourceUrl) { WasmSerializationTest test; { HandleScope scope(CcTest::i_isolate()); const std::string url = "http://example.com/example.wasm"; Handle module_object; CHECK(test.Deserialize(VectorOf(url)).ToHandle(&module_object)); String source_url = String::cast(module_object->script().source_url()); CHECK_EQ(url, source_url.ToCString().get()); } test.CollectGarbage(); } TEST(DeserializeMismatchingVersion) { WasmSerializationTest test; { HandleScope scope(CcTest::i_isolate()); test.InvalidateVersion(); CHECK(test.Deserialize().is_null()); } test.CollectGarbage(); } TEST(DeserializeNoSerializedData) { WasmSerializationTest test; { HandleScope scope(CcTest::i_isolate()); test.ClearSerializedData(); CHECK(test.Deserialize().is_null()); } test.CollectGarbage(); } TEST(DeserializeInvalidNumFunctions) { WasmSerializationTest test; { HandleScope scope(CcTest::i_isolate()); test.InvalidateNumFunctions(); CHECK(test.Deserialize().is_null()); } test.CollectGarbage(); } TEST(DeserializeWireBytesAndSerializedDataInvalid) { WasmSerializationTest test; { HandleScope scope(CcTest::i_isolate()); test.InvalidateVersion(); test.InvalidateWireBytes(); CHECK(test.Deserialize().is_null()); } test.CollectGarbage(); } bool False(v8::Local context, v8::Local source) { return false; } TEST(BlockWasmCodeGenAtDeserialization) { WasmSerializationTest test; { HandleScope scope(CcTest::i_isolate()); CcTest::isolate()->SetAllowWasmCodeGenerationCallback(False); CHECK(test.Deserialize().is_null()); } test.CollectGarbage(); } UNINITIALIZED_TEST(CompiledWasmModulesTransfer) { i::wasm::WasmEngine::InitializeOncePerProcess(); v8::internal::AccountingAllocator allocator; Zone zone(&allocator, ZONE_NAME); ZoneBuffer buffer(&zone); WasmSerializationTest::BuildWireBytes(&zone, &buffer); v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); v8::Isolate* from_isolate = v8::Isolate::New(create_params); std::vector store; std::shared_ptr original_native_module; { v8::HandleScope scope(from_isolate); LocalContext env(from_isolate); Isolate* from_i_isolate = reinterpret_cast(from_isolate); testing::SetupIsolateForWasmModule(from_i_isolate); ErrorThrower thrower(from_i_isolate, "TestCompiledWasmModulesTransfer"); auto enabled_features = WasmFeatures::FromIsolate(from_i_isolate); MaybeHandle maybe_module_object = from_i_isolate->wasm_engine()->SyncCompile( from_i_isolate, enabled_features, &thrower, ModuleWireBytes(buffer.begin(), buffer.end())); Handle module_object = maybe_module_object.ToHandleChecked(); v8::Local v8_module = v8::Local::Cast( v8::Utils::ToLocal(Handle::cast(module_object))); store.push_back(v8_module->GetCompiledModule()); original_native_module = module_object->shared_native_module(); } { v8::Isolate* to_isolate = v8::Isolate::New(create_params); { v8::HandleScope scope(to_isolate); LocalContext env(to_isolate); v8::MaybeLocal transferred_module = v8::WasmModuleObject::FromCompiledModule(to_isolate, store[0]); CHECK(!transferred_module.IsEmpty()); Handle module_object = Handle::cast( v8::Utils::OpenHandle(*transferred_module.ToLocalChecked())); std::shared_ptr transferred_native_module = module_object->shared_native_module(); CHECK_EQ(original_native_module, transferred_native_module); } to_isolate->Dispose(); } original_native_module.reset(); from_isolate->Dispose(); } } // namespace test_wasm_serialization } // namespace wasm } // namespace internal } // namespace v8