Reland of [wasm] Transferrable modules (patchset #1 id:1 of https://codereview.chromium.org/2762163002/ )
Reason for revert: Temporarily disabled tests on chromium side (https://codereview.chromium.org/2764933002) Original issue's description: > Revert of [wasm] Transferrable modules (patchset #13 id:280001 of https://codereview.chromium.org/2748473004/ ) > > Reason for revert: > Breaks layout tests: > https://build.chromium.org/p/client.v8.fyi/builders/V8-Blink%20Linux%2064/builds/14312 > > See https://github.com/v8/v8/wiki/Blink-layout-tests > > Original issue's description: > > [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} > > Committed:99743ad460
> > TBR=jbroman@chromium.org,bradnelson@chromium.org,mtrofin@chromium.org > # Skipping CQ checks because original CL landed less than 1 days ago. > NOPRESUBMIT=true > NOTREECHECKS=true > NOTRY=true > BUG=v8:6079 > > Review-Url: https://codereview.chromium.org/2762163002 > Cr-Commit-Position: refs/heads/master@{#43981} > Committed:e538b70e1a
TBR=jbroman@chromium.org,bradnelson@chromium.org,machenbach@chromium.org # Skipping CQ checks because original CL landed less than 1 days ago. NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true BUG=v8:6079 Review-Url: https://codereview.chromium.org/2762273002 Cr-Commit-Position: refs/heads/master@{#43994}
This commit is contained in:
parent
154369bb5a
commit
9dfa46395a
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