[wasm] Transferrable modules

We want to restrict structured cloning in Chrome to:
- postMessage senders and receivers that are co-located
in the same process
- indexedDB (just https).

For context, on the Chrome side, we will achieve the postMessage part
by using a mechanism similar to transferrables: the
SerializedScriptValue will have a list of wasm modules, separate from
the serialized data stream; and this list won't be copied cross
process boundaries. The IDB part is achieved by explicitly opting in
reading/writing to the serialization stream. To block attack vectors
in IPC cases, the default for deserialization will be to expect data
in the wasm transfers list.

This change is the V8 side necessary to enabling this design. We
introduce TransferrableModule, an opaque datatype exposed to the
embedder. Internally, TransferrableModules are just serialized data,
because we don't have a better mechanism, at the moment, for
de-contextualizing/re-contextualizing wasm modules (wrt Isolate and
Context).

The chrome defaults will be implemented in the
serialization/deserialization delegates on that side. For the v8 side
of things, in the absence of a serialization delegate, the V8
serializer will write to serialization stream. In the absence of a
deserialization delegate, the deserializer won't work. This asymmetry
is intentional - it communicates to the embedder the need to make a
policy decision, otherwise wasm serialization/deserialization won't
work "out of the box".

BUG=v8:6079

Review-Url: https://codereview.chromium.org/2748473004
Cr-Commit-Position: refs/heads/master@{#43955}
This commit is contained in:
mtrofin 2017-03-20 12:03:23 -07:00 committed by Commit bot
parent b1841eecb4
commit 99743ad460
6 changed files with 464 additions and 24 deletions

View File

@ -108,6 +108,7 @@ class Private;
class Uint32;
class Utils;
class Value;
class WasmCompiledModule;
template <class T> class Local;
template <class T>
class MaybeLocal;
@ -1709,6 +1710,8 @@ class V8_EXPORT ValueSerializer {
virtual Maybe<uint32_t> GetSharedArrayBufferId(
Isolate* isolate, Local<SharedArrayBuffer> shared_array_buffer);
virtual Maybe<uint32_t> GetWasmModuleTransferId(
Isolate* isolate, Local<WasmCompiledModule> module);
/*
* Allocates memory for the buffer of at least the size provided. The actual
* size (which may be greater or equal) is written to |actual_size|. If no
@ -1819,6 +1822,13 @@ class V8_EXPORT ValueDeserializer {
* MaybeLocal<Object>() returned.
*/
virtual MaybeLocal<Object> ReadHostObject(Isolate* isolate);
/*
* Get a WasmCompiledModule given a transfer_id previously provided
* by ValueSerializer::GetWasmModuleTransferId
*/
virtual MaybeLocal<WasmCompiledModule> GetWasmModuleFromId(
Isolate* isolate, uint32_t transfer_id);
};
ValueDeserializer(Isolate* isolate, const uint8_t* data, size_t size);
@ -1861,6 +1871,11 @@ class V8_EXPORT ValueDeserializer {
*/
void SetSupportsLegacyWireFormat(bool supports_legacy_wire_format);
/*
* Expect inline wasm in the data stream (rather than in-memory transfer)
*/
void SetExpectInlineWasm(bool allow_inline_wasm);
/*
* Reads the underlying wire format version. Likely mostly to be useful to
* legacy code reading old wire format versions. Must be called after
@ -3903,6 +3918,37 @@ class V8_EXPORT WasmCompiledModule : public Object {
typedef std::pair<std::unique_ptr<const uint8_t[]>, size_t> SerializedModule;
// A buffer that is owned by the caller.
typedef std::pair<const uint8_t*, size_t> CallerOwnedBuffer;
// An opaque, native heap object for transferring wasm modules. It
// supports move semantics, and does not support copy semantics.
class TransferrableModule final {
public:
TransferrableModule(TransferrableModule&& src) = default;
TransferrableModule(const TransferrableModule& src) = delete;
TransferrableModule& operator=(TransferrableModule&& src) = default;
TransferrableModule& operator=(const TransferrableModule& src) = delete;
private:
typedef std::pair<std::unique_ptr<const uint8_t[]>, size_t> OwnedBuffer;
friend class WasmCompiledModule;
TransferrableModule(OwnedBuffer&& code, OwnedBuffer&& bytes)
: compiled_code(std::move(code)), wire_bytes(std::move(bytes)) {}
OwnedBuffer compiled_code = {nullptr, 0};
OwnedBuffer wire_bytes = {nullptr, 0};
};
// Get an in-memory, non-persistable, and context-independent (meaning,
// suitable for transfer to another Isolate and Context) representation
// of this wasm compiled module.
TransferrableModule GetTransferrableModule();
// Efficiently re-create a WasmCompiledModule, without recompiling, from
// a TransferrableModule.
static MaybeLocal<WasmCompiledModule> FromTransferrableModule(
Isolate* isolate, const TransferrableModule&);
// Get the wasm-encoded bytes that were used to compile this module.
Local<String> GetWasmWireBytes();
@ -3924,6 +3970,11 @@ class V8_EXPORT WasmCompiledModule : public Object {
static MaybeLocal<WasmCompiledModule> Compile(Isolate* isolate,
const uint8_t* start,
size_t length);
static CallerOwnedBuffer AsCallerOwned(
const TransferrableModule::OwnedBuffer& buff) {
return {buff.first.get(), buff.second};
}
WasmCompiledModule();
static void CheckCast(Value* obj);
};

View File

@ -3125,6 +3125,11 @@ Maybe<uint32_t> ValueSerializer::Delegate::GetSharedArrayBufferId(
return Nothing<uint32_t>();
}
Maybe<uint32_t> ValueSerializer::Delegate::GetWasmModuleTransferId(
Isolate* v8_isolate, Local<WasmCompiledModule> module) {
return Nothing<uint32_t>();
}
void* ValueSerializer::Delegate::ReallocateBufferMemory(void* old_buffer,
size_t size,
size_t* actual_size) {
@ -3213,6 +3218,15 @@ MaybeLocal<Object> ValueDeserializer::Delegate::ReadHostObject(
return MaybeLocal<Object>();
}
MaybeLocal<WasmCompiledModule> ValueDeserializer::Delegate::GetWasmModuleFromId(
Isolate* v8_isolate, uint32_t id) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
isolate->ScheduleThrow(*isolate->factory()->NewError(
isolate->error_function(),
i::MessageTemplate::kDataCloneDeserializationError));
return MaybeLocal<WasmCompiledModule>();
}
struct ValueDeserializer::PrivateData {
PrivateData(i::Isolate* i, i::Vector<const uint8_t> data, Delegate* delegate)
: isolate(i), deserializer(i, data, delegate) {}
@ -3275,6 +3289,10 @@ void ValueDeserializer::SetSupportsLegacyWireFormat(
private_->supports_legacy_wire_format = supports_legacy_wire_format;
}
void ValueDeserializer::SetExpectInlineWasm(bool expect_inline_wasm) {
private_->deserializer.set_expect_inline_wasm(expect_inline_wasm);
}
uint32_t ValueDeserializer::GetWireFormatVersion() const {
CHECK(!private_->has_aborted);
return private_->deserializer.GetWireFormatVersion();
@ -7506,6 +7524,36 @@ Local<String> WasmCompiledModule::GetWasmWireBytes() {
return Local<String>::Cast(Utils::ToLocal(wire_bytes));
}
// Currently, wasm modules are bound, both to Isolate and to
// the Context they were created in. The currently-supported means to
// decontextualize and then re-contextualize a module is via
// serialization/deserialization.
WasmCompiledModule::TransferrableModule
WasmCompiledModule::GetTransferrableModule() {
i::DisallowHeapAllocation no_gc;
WasmCompiledModule::SerializedModule compiled_part = Serialize();
Local<String> wire_bytes = GetWasmWireBytes();
size_t wire_size = static_cast<size_t>(wire_bytes->Length());
uint8_t* bytes = new uint8_t[wire_size];
wire_bytes->WriteOneByte(bytes, 0, wire_bytes->Length());
return TransferrableModule(
std::move(compiled_part),
std::make_pair(
std::unique_ptr<const uint8_t[]>(const_cast<const uint8_t*>(bytes)),
wire_size));
}
MaybeLocal<WasmCompiledModule> WasmCompiledModule::FromTransferrableModule(
Isolate* isolate,
const WasmCompiledModule::TransferrableModule& transferrable_module) {
MaybeLocal<WasmCompiledModule> ret =
Deserialize(isolate, AsCallerOwned(transferrable_module.compiled_code),
AsCallerOwned(transferrable_module.wire_bytes));
return ret;
}
WasmCompiledModule::SerializedModule WasmCompiledModule::Serialize() {
i::Handle<i::JSObject> obj =
i::Handle<i::JSObject>::cast(Utils::OpenHandle(this));

View File

@ -126,6 +126,8 @@ enum class SerializationTag : uint8_t {
// wasmWireByteLength:uint32_t, then raw data
// compiledDataLength:uint32_t, then raw data
kWasmModule = 'W',
// A wasm module object transfer. next value is its index.
kWasmModuleTransfer = 'w',
// The delegate is responsible for processing all following data.
// This "escapes" to whatever wire format the delegate chooses.
kHostObject = '\\',
@ -803,6 +805,19 @@ Maybe<bool> ValueSerializer::WriteJSArrayBufferView(JSArrayBufferView* view) {
}
Maybe<bool> ValueSerializer::WriteWasmModule(Handle<JSObject> object) {
if (delegate_ != nullptr) {
Maybe<uint32_t> transfer_id = delegate_->GetWasmModuleTransferId(
reinterpret_cast<v8::Isolate*>(isolate_),
v8::Local<v8::WasmCompiledModule>::Cast(Utils::ToLocal(object)));
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
uint32_t id = 0;
if (transfer_id.To(&id)) {
WriteTag(SerializationTag::kWasmModuleTransfer);
WriteVarint<uint32_t>(id);
return Just(true);
}
}
Handle<WasmCompiledModule> compiled_part(
WasmCompiledModule::cast(object->GetEmbedderField(0)), isolate_);
WasmEncodingTag encoding_tag = WasmEncodingTag::kRawBytes;
@ -1150,6 +1165,8 @@ MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() {
}
case SerializationTag::kWasmModule:
return ReadWasmModule();
case SerializationTag::kWasmModuleTransfer:
return ReadWasmModuleTransfer();
case SerializationTag::kHostObject:
return ReadHostObject();
default:
@ -1595,8 +1612,32 @@ MaybeHandle<JSArrayBufferView> ValueDeserializer::ReadJSArrayBufferView(
return typed_array;
}
MaybeHandle<JSObject> ValueDeserializer::ReadWasmModuleTransfer() {
if (FLAG_wasm_disable_structured_cloning || expect_inline_wasm()) {
return MaybeHandle<JSObject>();
}
uint32_t transfer_id = 0;
Local<Value> module_value;
if (!ReadVarint<uint32_t>().To(&transfer_id) || delegate_ == nullptr ||
!delegate_
->GetWasmModuleFromId(reinterpret_cast<v8::Isolate*>(isolate_),
transfer_id)
.ToLocal(&module_value)) {
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, JSObject);
return MaybeHandle<JSObject>();
}
uint32_t id = next_id_++;
Handle<JSObject> module =
Handle<JSObject>::cast(Utils::OpenHandle(*module_value));
AddObjectWithID(id, module);
return module;
}
MaybeHandle<JSObject> ValueDeserializer::ReadWasmModule() {
if (FLAG_wasm_disable_structured_cloning) return MaybeHandle<JSObject>();
if (FLAG_wasm_disable_structured_cloning || !expect_inline_wasm()) {
return MaybeHandle<JSObject>();
}
Vector<const uint8_t> encoding_tag;
if (!ReadRawBytes(sizeof(WasmEncodingTag)).To(&encoding_tag) ||
@ -1625,21 +1666,22 @@ MaybeHandle<JSObject> ValueDeserializer::ReadWasmModule() {
// Try to deserialize the compiled module first.
ScriptData script_data(compiled_bytes.start(), compiled_bytes.length());
Handle<FixedArray> compiled_part;
MaybeHandle<JSObject> result;
if (WasmCompiledModuleSerializer::DeserializeWasmModule(
isolate_, &script_data, wire_bytes)
.ToHandle(&compiled_part)) {
return WasmModuleObject::New(
result = WasmModuleObject::New(
isolate_, Handle<WasmCompiledModule>::cast(compiled_part));
}
// If that fails, recompile.
MaybeHandle<JSObject> result;
{
} else {
wasm::ErrorThrower thrower(isolate_, "ValueDeserializer::ReadWasmModule");
result = wasm::SyncCompile(isolate_, &thrower,
wasm::ModuleWireBytes(wire_bytes));
}
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, JSObject);
uint32_t id = next_id_++;
if (!result.is_null()) {
AddObjectWithID(id, result.ToHandleChecked());
}
return result;
}

View File

@ -31,6 +31,7 @@ class JSValue;
class Object;
class Oddball;
class Smi;
class WasmModuleObject;
enum class SerializationTag : uint8_t;
@ -218,6 +219,9 @@ class ValueDeserializer {
bool ReadUint64(uint64_t* value) WARN_UNUSED_RESULT;
bool ReadDouble(double* value) WARN_UNUSED_RESULT;
bool ReadRawBytes(size_t length, const void** data) WARN_UNUSED_RESULT;
void set_expect_inline_wasm(bool expect_inline_wasm) {
expect_inline_wasm_ = expect_inline_wasm;
}
private:
// Reading the wire format.
@ -230,6 +234,7 @@ class ValueDeserializer {
Maybe<T> ReadZigZag() WARN_UNUSED_RESULT;
Maybe<double> ReadDouble() WARN_UNUSED_RESULT;
Maybe<Vector<const uint8_t>> ReadRawBytes(int size) WARN_UNUSED_RESULT;
bool expect_inline_wasm() const { return expect_inline_wasm_; }
// Reads a string if it matches the one provided.
// Returns true if this was the case. Otherwise, nothing is consumed.
@ -263,6 +268,7 @@ class ValueDeserializer {
MaybeHandle<JSArrayBufferView> ReadJSArrayBufferView(
Handle<JSArrayBuffer> buffer) WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadWasmModule() WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadWasmModuleTransfer() WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadHostObject() WARN_UNUSED_RESULT;
/*
@ -285,6 +291,7 @@ class ValueDeserializer {
PretenureFlag pretenure_;
uint32_t version_ = 0;
uint32_t next_id_ = 0;
bool expect_inline_wasm_ = false;
// Always global handles.
Handle<FixedArray> id_map_;

View File

@ -469,6 +469,43 @@ TEST(BlockWasmCodeGenAtDeserialization) {
Cleanup();
}
TEST(TransferrableWasmModules) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
ZoneBuffer buffer(&zone);
WasmSerializationTest::BuildWireBytes(&zone, &buffer);
Isolate* from_isolate = CcTest::InitIsolateOnce();
ErrorThrower thrower(from_isolate, "");
std::vector<v8::WasmCompiledModule::TransferrableModule> store;
{
HandleScope scope(from_isolate);
testing::SetupIsolateForWasmModule(from_isolate);
MaybeHandle<WasmModuleObject> module_object = SyncCompile(
from_isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end()));
v8::Local<v8::WasmCompiledModule> v8_module =
v8::Local<v8::WasmCompiledModule>::Cast(v8::Utils::ToLocal(
Handle<JSObject>::cast(module_object.ToHandleChecked())));
store.push_back(v8_module->GetTransferrableModule());
}
{
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator =
from_isolate->array_buffer_allocator();
v8::Isolate* to_isolate = v8::Isolate::New(create_params);
v8::HandleScope new_scope(to_isolate);
v8::Local<v8::Context> deserialization_context =
v8::Context::New(to_isolate);
deserialization_context->Enter();
v8::MaybeLocal<v8::WasmCompiledModule> mod =
v8::WasmCompiledModule::FromTransferrableModule(to_isolate, store[0]);
CHECK(!mod.IsEmpty());
}
}
TEST(MemorySize) {
{
// Initial memory size is 16, see wasm-module-builder.cc

View File

@ -74,6 +74,9 @@ class ValueSerializerTest : public TestWithIsolate {
return deserialization_context_;
}
bool ExpectInlineWasm() const { return expect_inline_wasm_; }
void SetExpectInlineWasm(bool value) { expect_inline_wasm_ = value; }
// Overridden in more specific fixtures.
virtual ValueSerializer::Delegate* GetSerializerDelegate() { return nullptr; }
virtual void BeforeEncode(ValueSerializer*) {}
@ -172,6 +175,7 @@ class ValueSerializerTest : public TestWithIsolate {
static_cast<int>(data.size()),
GetDeserializerDelegate());
deserializer.SetSupportsLegacyWireFormat(true);
deserializer.SetExpectInlineWasm(ExpectInlineWasm());
BeforeDecode(&deserializer);
ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
Local<Value> result;
@ -196,6 +200,7 @@ class ValueSerializerTest : public TestWithIsolate {
static_cast<int>(data.size()),
GetDeserializerDelegate());
deserializer.SetSupportsLegacyWireFormat(true);
deserializer.SetExpectInlineWasm(ExpectInlineWasm());
BeforeDecode(&deserializer);
ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
ASSERT_EQ(0u, deserializer.GetWireFormatVersion());
@ -219,6 +224,7 @@ class ValueSerializerTest : public TestWithIsolate {
static_cast<int>(data.size()),
GetDeserializerDelegate());
deserializer.SetSupportsLegacyWireFormat(true);
deserializer.SetExpectInlineWasm(ExpectInlineWasm());
BeforeDecode(&deserializer);
Maybe<bool> header_result = deserializer.ReadHeader(context);
if (header_result.IsNothing()) {
@ -275,6 +281,7 @@ class ValueSerializerTest : public TestWithIsolate {
Local<Context> deserialization_context_;
Local<FunctionTemplate> host_object_constructor_template_;
i::Isolate* isolate_;
bool expect_inline_wasm_ = false;
DISALLOW_COPY_AND_ASSIGN(ValueSerializerTest);
};
@ -2594,7 +2601,44 @@ TEST_F(ValueSerializerTestWithHostArrayBufferView, RoundTripUint8ArrayInput) {
// mostly checks that the logic to embed it in structured clone serialization
// works correctly.
// A simple module which exports an "increment" function.
// Copied from test/mjsunit/wasm/incrementer.wasm.
const unsigned char kIncrementerWasm[] = {
0, 97, 115, 109, 1, 0, 0, 0, 1, 6, 1, 96, 1, 127, 1, 127,
3, 2, 1, 0, 7, 13, 1, 9, 105, 110, 99, 114, 101, 109, 101, 110,
116, 0, 0, 10, 9, 1, 7, 0, 32, 0, 65, 1, 106, 11,
};
class ValueSerializerTestWithWasm : public ValueSerializerTest {
public:
static const char* kUnsupportedSerialization;
ValueSerializerTestWithWasm()
: serialize_delegate_(&transfer_modules_),
deserialize_delegate_(&transfer_modules_) {}
void Reset() {
current_serializer_delegate_ = nullptr;
transfer_modules_.clear();
SetExpectInlineWasm(false);
}
void EnableTransferSerialization() {
current_serializer_delegate_ = &serialize_delegate_;
}
void EnableTransferDeserialization() {
current_deserializer_delegate_ = &deserialize_delegate_;
}
void EnableThrowingSerializer() {
current_serializer_delegate_ = &throwing_serializer_;
}
void EnableDefaultDeserializer() {
current_deserializer_delegate_ = &default_deserializer_;
}
protected:
static void SetUpTestCase() {
g_saved_flag = i::FLAG_expose_wasm;
@ -2608,32 +2652,243 @@ class ValueSerializerTestWithWasm : public ValueSerializerTest {
g_saved_flag = false;
}
class ThrowingSerializer : public ValueSerializer::Delegate {
public:
Maybe<uint32_t> GetWasmModuleTransferId(
Isolate* isolate, Local<WasmCompiledModule> module) override {
isolate->ThrowException(Exception::Error(
String::NewFromOneByte(
isolate,
reinterpret_cast<const uint8_t*>(kUnsupportedSerialization),
NewStringType::kNormal)
.ToLocalChecked()));
return Nothing<uint32_t>();
}
void ThrowDataCloneError(Local<String> message) override { UNREACHABLE(); }
};
class SerializeToTransfer : public ValueSerializer::Delegate {
public:
SerializeToTransfer(
std::vector<WasmCompiledModule::TransferrableModule>* modules)
: modules_(modules) {}
Maybe<uint32_t> GetWasmModuleTransferId(
Isolate* isolate, Local<WasmCompiledModule> module) override {
modules_->push_back(module->GetTransferrableModule());
return Just(static_cast<uint32_t>(modules_->size()) - 1);
}
void ThrowDataCloneError(Local<String> message) override { UNREACHABLE(); }
private:
std::vector<WasmCompiledModule::TransferrableModule>* modules_;
};
class DeserializeFromTransfer : public ValueDeserializer::Delegate {
public:
DeserializeFromTransfer(
std::vector<WasmCompiledModule::TransferrableModule>* modules)
: modules_(modules) {}
MaybeLocal<WasmCompiledModule> GetWasmModuleFromId(Isolate* isolate,
uint32_t id) override {
return WasmCompiledModule::FromTransferrableModule(isolate,
modules_->at(id));
}
private:
std::vector<WasmCompiledModule::TransferrableModule>* modules_;
};
ValueSerializer::Delegate* GetSerializerDelegate() override {
return current_serializer_delegate_;
}
ValueDeserializer::Delegate* GetDeserializerDelegate() override {
return current_deserializer_delegate_;
}
Local<WasmCompiledModule> MakeWasm() {
return WasmCompiledModule::DeserializeOrCompile(
isolate(), {nullptr, 0},
{kIncrementerWasm, sizeof(kIncrementerWasm)})
.ToLocalChecked();
}
void ExpectPass() {
RoundTripTest(
[this]() { return MakeWasm(); },
[this](Local<Value> value) {
ASSERT_TRUE(value->IsWebAssemblyCompiledModule());
EXPECT_TRUE(EvaluateScriptForResultBool(
"new WebAssembly.Instance(result).exports.increment(8) === 9"));
});
}
void ExpectFail() {
EncodeTest(
[this]() { return MakeWasm(); },
[this](const std::vector<uint8_t>& data) { InvalidDecodeTest(data); });
}
Local<Value> GetComplexObjectWithDuplicate() {
Local<Value> wasm_module = MakeWasm();
serialization_context()
->Global()
->CreateDataProperty(serialization_context(),
StringFromUtf8("wasm_module"), wasm_module)
.FromMaybe(false);
Local<Script> script =
Script::Compile(
serialization_context(),
StringFromUtf8("({mod1: wasm_module, num: 2, mod2: wasm_module})"))
.ToLocalChecked();
return script->Run(serialization_context()).ToLocalChecked();
}
void VerifyComplexObject(Local<Value> value) {
ASSERT_TRUE(value->IsObject());
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.mod1 instanceof WebAssembly.Module"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.mod2 instanceof WebAssembly.Module"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.num === 2"));
}
Local<Value> GetComplexObjectWithMany() {
Local<Value> wasm_module1 = MakeWasm();
Local<Value> wasm_module2 = MakeWasm();
serialization_context()
->Global()
->CreateDataProperty(serialization_context(),
StringFromUtf8("wasm_module1"), wasm_module1)
.FromMaybe(false);
serialization_context()
->Global()
->CreateDataProperty(serialization_context(),
StringFromUtf8("wasm_module2"), wasm_module2)
.FromMaybe(false);
Local<Script> script =
Script::Compile(
serialization_context(),
StringFromUtf8(
"({mod1: wasm_module1, num: 2, mod2: wasm_module2})"))
.ToLocalChecked();
return script->Run(serialization_context()).ToLocalChecked();
}
private:
static bool g_saved_flag;
std::vector<WasmCompiledModule::TransferrableModule> transfer_modules_;
SerializeToTransfer serialize_delegate_;
DeserializeFromTransfer deserialize_delegate_;
ValueSerializer::Delegate* current_serializer_delegate_ = nullptr;
ValueDeserializer::Delegate* current_deserializer_delegate_ = nullptr;
ThrowingSerializer throwing_serializer_;
ValueDeserializer::Delegate default_deserializer_;
};
bool ValueSerializerTestWithWasm::g_saved_flag = false;
const char* ValueSerializerTestWithWasm::kUnsupportedSerialization =
"Wasm Serialization Not Supported";
// A simple module which exports an "increment" function.
// Copied from test/mjsunit/wasm/incrementer.wasm.
const unsigned char kIncrementerWasm[] = {
0, 97, 115, 109, 1, 0, 0, 0, 1, 6, 1, 96, 1, 127, 1, 127,
3, 2, 1, 0, 7, 13, 1, 9, 105, 110, 99, 114, 101, 109, 101, 110,
116, 0, 0, 10, 9, 1, 7, 0, 32, 0, 65, 1, 106, 11,
};
// The default implementation of the serialization
// delegate throws when trying to serialize wasm. The
// embedder must decide serialization policy.
TEST_F(ValueSerializerTestWithWasm, DefaultSerializationDelegate) {
EnableThrowingSerializer();
InvalidEncodeTest(
[this]() { return MakeWasm(); },
[](Local<Message> message) {
size_t msg_len = static_cast<size_t>(message->Get()->Length());
std::unique_ptr<char[]> buff(new char[msg_len + 1]);
message->Get()->WriteOneByte(reinterpret_cast<uint8_t*>(buff.get()));
// the message ends with the custom error string
size_t custom_msg_len = strlen(kUnsupportedSerialization);
ASSERT_GE(msg_len, custom_msg_len);
size_t start_pos = msg_len - custom_msg_len;
ASSERT_EQ(strcmp(&buff.get()[start_pos], kUnsupportedSerialization), 0);
});
}
TEST_F(ValueSerializerTestWithWasm, RoundTripWasmModule) {
// The default deserializer throws if wasm transfer is attempted
TEST_F(ValueSerializerTestWithWasm, DefaultDeserializationDelegate) {
EnableTransferSerialization();
EnableDefaultDeserializer();
EncodeTest(
[this]() { return MakeWasm(); },
[this](const std::vector<uint8_t>& data) { InvalidDecodeTest(data); });
}
// We only want to allow deserialization through
// transferred modules - which requres both serializer
// and deserializer to understand that - or through
// explicitly allowing inlined data, which requires
// deserializer opt-in (we default the serializer to
// inlined data because we don't trust that data on the
// receiving end anyway).
TEST_F(ValueSerializerTestWithWasm, RoundtripWasmTransfer) {
EnableTransferSerialization();
EnableTransferDeserialization();
ExpectPass();
}
TEST_F(ValueSerializerTestWithWasm, RountripWasmInline) {
SetExpectInlineWasm(true);
ExpectPass();
}
TEST_F(ValueSerializerTestWithWasm, CannotDeserializeWasmInlineData) {
ExpectFail();
}
TEST_F(ValueSerializerTestWithWasm, CannotTransferWasmWhenExpectingInline) {
EnableTransferSerialization();
SetExpectInlineWasm(true);
ExpectFail();
}
TEST_F(ValueSerializerTestWithWasm, ComplexObjectDuplicateTransfer) {
EnableTransferSerialization();
EnableTransferDeserialization();
RoundTripTest(
[this]() {
return WasmCompiledModule::DeserializeOrCompile(
isolate(), {nullptr, 0},
{kIncrementerWasm, sizeof(kIncrementerWasm)})
.ToLocalChecked();
},
[this]() { return GetComplexObjectWithDuplicate(); },
[this](Local<Value> value) {
ASSERT_TRUE(value->IsWebAssemblyCompiledModule());
EXPECT_TRUE(EvaluateScriptForResultBool(
"new WebAssembly.Instance(result).exports.increment(8) === 9"));
VerifyComplexObject(value);
EXPECT_TRUE(EvaluateScriptForResultBool("result.mod1 === result.mod2"));
});
}
TEST_F(ValueSerializerTestWithWasm, ComplexObjectDuplicateInline) {
SetExpectInlineWasm(true);
RoundTripTest(
[this]() { return GetComplexObjectWithDuplicate(); },
[this](Local<Value> value) {
VerifyComplexObject(value);
EXPECT_TRUE(EvaluateScriptForResultBool("result.mod1 === result.mod2"));
});
}
TEST_F(ValueSerializerTestWithWasm, ComplexObjectWithManyTransfer) {
EnableTransferSerialization();
EnableTransferDeserialization();
RoundTripTest(
[this]() { return GetComplexObjectWithMany(); },
[this](Local<Value> value) {
VerifyComplexObject(value);
EXPECT_TRUE(EvaluateScriptForResultBool("result.mod1 != result.mod2"));
});
}
TEST_F(ValueSerializerTestWithWasm, ComplexObjectWithManyInline) {
SetExpectInlineWasm(true);
RoundTripTest(
[this]() { return GetComplexObjectWithMany(); },
[this](Local<Value> value) {
VerifyComplexObject(value);
EXPECT_TRUE(EvaluateScriptForResultBool("result.mod1 != result.mod2"));
});
}