// 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-inl.h" #include "src/snapshot/code-serializer.h" #include "src/version.h" #include "src/wasm/module-decoder.h" #include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-memory.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 "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 { namespace { void Cleanup(Isolate* isolate = CcTest::InitIsolateOnce()) { // By sending a low memory notifications, we will try hard to collect all // garbage and will therefore also invoke all weak callbacks of actually // unreachable persistent handles. reinterpret_cast(isolate)->LowMemoryNotification(); } #define EMIT_CODE_WITH_END(f, code) \ do { \ f->EmitCode(code, sizeof(code)); \ f->Emit(kExprEnd); \ } while (false) } // namespace // 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 = new (zone) WasmModuleBuilder(zone); TestSignatures sigs; WasmFunctionBuilder* f = builder->AddFunction(sigs.i_i()); byte code[] = {WASM_GET_LOCAL(0), kExprI32Const, 1, kExprI32Add}; EMIT_CODE_WITH_END(f, 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()) + SerializedCodeData::kVersionHashOffset); *slot = Version::Hash() + 1; } void InvalidateWireBytes() { memset(const_cast(wire_bytes_.data()), 0, wire_bytes_.size() / 2); } void InvalidateLength() { uint32_t* slot = reinterpret_cast( const_cast(serialized_bytes_.data()) + SerializedCodeData::kPayloadLengthOffset); *slot = 0u; } v8::MaybeLocal Deserialize() { ErrorThrower thrower(current_isolate(), ""); v8::MaybeLocal deserialized = v8::WasmModuleObject::DeserializeOrCompile( current_isolate_v8(), serialized_bytes_, wire_bytes_); return deserialized; } void DeserializeAndRun() { ErrorThrower thrower(current_isolate(), ""); v8::Local deserialized_module; CHECK(Deserialize().ToLocal(&deserialized_module)); Handle module_object = Handle::cast( v8::Utils::OpenHandle(*deserialized_module)); { 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 = current_isolate() ->wasm_engine() ->SyncInstantiate(current_isolate(), &thrower, module_object, Handle::null(), MaybeHandle()) .ToHandleChecked(); Handle params[1] = { Handle(Smi::FromInt(41), current_isolate())}; int32_t result = testing::CallWasmFunctionForTesting( current_isolate(), instance, &thrower, kFunctionName, 1, params); CHECK_EQ(42, result); } Isolate* current_isolate() { return reinterpret_cast(current_isolate_v8_); } ~WasmSerializationTest() { // Don't call from here if we move to gtest TearDown(); } v8::Isolate* current_isolate_v8() { return current_isolate_v8_; } private: static const char* kFunctionName; Zone* zone() { return &zone_; } void SetUp() { ZoneBuffer buffer(&zone_); WasmSerializationTest::BuildWireBytes(zone(), &buffer); Isolate* serialization_isolate = CcTest::InitIsolateOnce(); ErrorThrower thrower(serialization_isolate, ""); { HandleScope scope(serialization_isolate); testing::SetupIsolateForWasmModule(serialization_isolate); auto enabled_features = WasmFeaturesFromIsolate(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(); v8::Local v8_module_obj = v8::Utils::ToLocal(Handle::cast(module_object)); CHECK(v8_module_obj->IsWebAssemblyCompiledModule()); 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(); } serialized_bytes_ = {data_.buffer.get(), data_.size}; v8::Isolate::CreateParams create_params; create_params.array_buffer_allocator = serialization_isolate->array_buffer_allocator(); current_isolate_v8_ = v8::Isolate::New(create_params); v8::HandleScope new_scope(current_isolate_v8()); v8::Local deserialization_context = v8::Context::New(current_isolate_v8()); deserialization_context->Enter(); testing::SetupIsolateForWasmModule(current_isolate()); } void TearDown() { current_isolate_v8()->Dispose(); current_isolate_v8_ = nullptr; } v8::internal::AccountingAllocator allocator_; Zone zone_; v8::OwnedBuffer data_; v8::MemorySpan wire_bytes_ = {nullptr, 0}; v8::MemorySpan serialized_bytes_ = {nullptr, 0}; v8::Isolate* current_isolate_v8_; }; const char* WasmSerializationTest::kFunctionName = "increment"; TEST(DeserializeValidModule) { WasmSerializationTest test; { HandleScope scope(test.current_isolate()); test.DeserializeAndRun(); } Cleanup(test.current_isolate()); Cleanup(); } TEST(DeserializeMismatchingVersion) { WasmSerializationTest test; { HandleScope scope(test.current_isolate()); test.InvalidateVersion(); test.DeserializeAndRun(); } Cleanup(test.current_isolate()); Cleanup(); } TEST(DeserializeNoSerializedData) { WasmSerializationTest test; { HandleScope scope(test.current_isolate()); test.ClearSerializedData(); test.DeserializeAndRun(); } Cleanup(test.current_isolate()); Cleanup(); } TEST(DeserializeInvalidLength) { WasmSerializationTest test; { HandleScope scope(test.current_isolate()); test.InvalidateLength(); test.DeserializeAndRun(); } Cleanup(test.current_isolate()); Cleanup(); } TEST(DeserializeWireBytesAndSerializedDataInvalid) { WasmSerializationTest test; { HandleScope scope(test.current_isolate()); test.InvalidateVersion(); test.InvalidateWireBytes(); test.Deserialize(); } Cleanup(test.current_isolate()); Cleanup(); } bool False(v8::Local context, v8::Local source) { return false; } TEST(BlockWasmCodeGenAtDeserialization) { WasmSerializationTest test; { HandleScope scope(test.current_isolate()); test.current_isolate_v8()->SetAllowCodeGenerationFromStringsCallback(False); v8::MaybeLocal nothing = test.Deserialize(); CHECK(nothing.IsEmpty()); } Cleanup(test.current_isolate()); Cleanup(); } namespace { void TestTransferrableWasmModules(bool should_share) { 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, "TestTransferrableWasmModules"); auto enabled_features = WasmFeaturesFromIsolate(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->GetTransferrableModule()); 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::FromTransferrableModule(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(); bool is_sharing = (original_native_module == transferred_native_module); CHECK_EQ(should_share, is_sharing); } to_isolate->Dispose(); } original_native_module.reset(); from_isolate->Dispose(); } } // namespace UNINITIALIZED_TEST(TransferrableWasmModulesCloned) { FlagScope flag_scope_code(&FLAG_wasm_shared_code, false); TestTransferrableWasmModules(false); } UNINITIALIZED_TEST(TransferrableWasmModulesShared) { FlagScope flag_scope_engine(&FLAG_wasm_shared_engine, true); FlagScope flag_scope_code(&FLAG_wasm_shared_code, true); TestTransferrableWasmModules(true); } #undef EMIT_CODE_WITH_END } // namespace test_wasm_serialization } // namespace wasm } // namespace internal } // namespace v8