bce455a3f0
Writing out the number of functions in the module is unnecessary. That number is only used for validation when reading back the value, but only validating that number is pretty arbitrary and does not protect against bugs or attacks. Hence skip these two header fields. R=thibaudm@chromium.org Bug: v8:11164 Change-Id: I083075e2c8959f99690fd1478d0950a25eb7311f Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2644946 Commit-Queue: Clemens Backes <clemensb@chromium.org> Reviewed-by: Thibaud Michaud <thibaudm@chromium.org> Cr-Commit-Position: refs/heads/master@{#72326}
339 lines
12 KiB
C++
339 lines
12 KiB
C++
// 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 <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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<WasmModuleBuilder>(zone);
|
|
TestSignatures sigs;
|
|
|
|
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_i());
|
|
byte code[] = {WASM_LOCAL_GET(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<uint32_t*>(
|
|
const_cast<uint8_t*>(serialized_bytes_.data()) +
|
|
WasmSerializer::kVersionHashOffset);
|
|
*slot = Version::Hash() + 1;
|
|
}
|
|
|
|
void InvalidateWireBytes() {
|
|
memset(const_cast<uint8_t*>(wire_bytes_.data()), 0, wire_bytes_.size() / 2);
|
|
}
|
|
|
|
MaybeHandle<WasmModuleObject> Deserialize(
|
|
Vector<const char> source_url = {}) {
|
|
return DeserializeNativeModule(CcTest::i_isolate(),
|
|
VectorOf(serialized_bytes_),
|
|
VectorOf(wire_bytes_), source_url);
|
|
}
|
|
|
|
void DeserializeAndRun() {
|
|
ErrorThrower thrower(CcTest::i_isolate(), "");
|
|
Handle<WasmModuleObject> module_object;
|
|
CHECK(Deserialize().ToHandle(&module_object));
|
|
{
|
|
DisallowGarbageCollection assume_no_gc;
|
|
Vector<const byte> 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<WasmInstanceObject> instance =
|
|
CcTest::i_isolate()
|
|
->wasm_engine()
|
|
->SyncInstantiate(CcTest::i_isolate(), &thrower, module_object,
|
|
Handle<JSReceiver>::null(),
|
|
MaybeHandle<JSArrayBuffer>())
|
|
.ToHandleChecked();
|
|
Handle<Object> params[1] = {
|
|
Handle<Object>(Smi::FromInt(41), CcTest::i_isolate())};
|
|
int32_t result = testing::CallWasmFunctionForTesting(
|
|
CcTest::i_isolate(), instance, 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<Isolate*>(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<NativeModule> weak_native_module;
|
|
{
|
|
HandleScope scope(serialization_isolate);
|
|
v8::Local<v8::Context> serialization_context =
|
|
v8::Context::New(serialization_v8_isolate);
|
|
serialization_context->Enter();
|
|
|
|
auto enabled_features = WasmFeatures::FromIsolate(serialization_isolate);
|
|
MaybeHandle<WasmModuleObject> maybe_module_object =
|
|
serialization_isolate->wasm_engine()->SyncCompile(
|
|
serialization_isolate, enabled_features, &thrower,
|
|
ModuleWireBytes(buffer.begin(), buffer.end()));
|
|
Handle<WasmModuleObject> 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());
|
|
|
|
auto* native_module = module_object->native_module();
|
|
native_module->compilation_state()->WaitForTopTierFinished();
|
|
DCHECK(!native_module->compilation_state()->failed());
|
|
|
|
v8::Local<v8::Object> v8_module_obj =
|
|
v8::Utils::ToLocal(Handle<JSObject>::cast(module_object));
|
|
CHECK(v8_module_obj->IsWasmModuleObject());
|
|
|
|
v8::Local<v8::WasmModuleObject> v8_module_object =
|
|
v8_module_obj.As<v8::WasmModuleObject>();
|
|
v8::CompiledWasmModule compiled_module =
|
|
v8_module_object->GetCompiledModule();
|
|
v8::MemorySpan<const uint8_t> uncompiled_bytes =
|
|
compiled_module.GetWireBytesRef();
|
|
uint8_t* bytes_copy = zone()->NewArray<uint8_t>(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();
|
|
CHECK_LT(0, data_.size);
|
|
}
|
|
// 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<v8::Context> deserialization_context =
|
|
v8::Context::New(CcTest::isolate());
|
|
deserialization_context->Enter();
|
|
}
|
|
|
|
v8::internal::AccountingAllocator allocator_;
|
|
Zone zone_;
|
|
v8::OwnedBuffer data_;
|
|
v8::MemorySpan<const uint8_t> wire_bytes_ = {nullptr, 0};
|
|
v8::MemorySpan<const uint8_t> 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<WasmModuleObject> 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(DeserializeWireBytesAndSerializedDataInvalid) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(CcTest::i_isolate());
|
|
test.InvalidateVersion();
|
|
test.InvalidateWireBytes();
|
|
CHECK(test.Deserialize().is_null());
|
|
}
|
|
test.CollectGarbage();
|
|
}
|
|
|
|
bool False(v8::Local<v8::Context> context, v8::Local<v8::String> 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<v8::CompiledWasmModule> store;
|
|
std::shared_ptr<NativeModule> original_native_module;
|
|
{
|
|
v8::HandleScope scope(from_isolate);
|
|
LocalContext env(from_isolate);
|
|
|
|
Isolate* from_i_isolate = reinterpret_cast<Isolate*>(from_isolate);
|
|
testing::SetupIsolateForWasmModule(from_i_isolate);
|
|
ErrorThrower thrower(from_i_isolate, "TestCompiledWasmModulesTransfer");
|
|
auto enabled_features = WasmFeatures::FromIsolate(from_i_isolate);
|
|
MaybeHandle<WasmModuleObject> maybe_module_object =
|
|
from_i_isolate->wasm_engine()->SyncCompile(
|
|
from_i_isolate, enabled_features, &thrower,
|
|
ModuleWireBytes(buffer.begin(), buffer.end()));
|
|
Handle<WasmModuleObject> module_object =
|
|
maybe_module_object.ToHandleChecked();
|
|
v8::Local<v8::WasmModuleObject> v8_module =
|
|
v8::Local<v8::WasmModuleObject>::Cast(
|
|
v8::Utils::ToLocal(Handle<JSObject>::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<v8::WasmModuleObject> transferred_module =
|
|
v8::WasmModuleObject::FromCompiledModule(to_isolate, store[0]);
|
|
CHECK(!transferred_module.IsEmpty());
|
|
Handle<WasmModuleObject> module_object = Handle<WasmModuleObject>::cast(
|
|
v8::Utils::OpenHandle(*transferred_module.ToLocalChecked()));
|
|
std::shared_ptr<NativeModule> 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();
|
|
}
|
|
|
|
TEST(TierDownAfterDeserialization) {
|
|
WasmSerializationTest test;
|
|
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
HandleScope scope(isolate);
|
|
Handle<WasmModuleObject> module_object;
|
|
CHECK(test.Deserialize().ToHandle(&module_object));
|
|
|
|
auto* native_module = module_object->native_module();
|
|
CHECK_EQ(1, native_module->module()->functions.size());
|
|
WasmCodeRefScope code_ref_scope;
|
|
auto* turbofan_code = native_module->GetCode(0);
|
|
CHECK_NOT_NULL(turbofan_code);
|
|
CHECK_EQ(ExecutionTier::kTurbofan, turbofan_code->tier());
|
|
|
|
isolate->wasm_engine()->TierDownAllModulesPerIsolate(isolate);
|
|
|
|
auto* liftoff_code = native_module->GetCode(0);
|
|
CHECK_EQ(ExecutionTier::kLiftoff, liftoff_code->tier());
|
|
}
|
|
|
|
} // namespace test_wasm_serialization
|
|
} // namespace wasm
|
|
} // namespace internal
|
|
} // namespace v8
|