[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:
parent
b1841eecb4
commit
99743ad460
51
include/v8.h
51
include/v8.h
@ -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);
|
||||
};
|
||||
|
48
src/api.cc
48
src/api.cc
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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_;
|
||||
|
@ -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
|
||||
|
@ -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"));
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user