aboutsummaryrefslogtreecommitdiff
path: root/deps/v8/test/unittests/value-serializer-unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8/test/unittests/value-serializer-unittest.cc')
-rw-r--r--deps/v8/test/unittests/value-serializer-unittest.cc1098
1 files changed, 1046 insertions, 52 deletions
diff --git a/deps/v8/test/unittests/value-serializer-unittest.cc b/deps/v8/test/unittests/value-serializer-unittest.cc
index f4ed15b644..d88d60a3e6 100644
--- a/deps/v8/test/unittests/value-serializer-unittest.cc
+++ b/deps/v8/test/unittests/value-serializer-unittest.cc
@@ -11,16 +11,48 @@
#include "src/api.h"
#include "src/base/build_config.h"
#include "test/unittests/test-utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace {
+using ::testing::_;
+using ::testing::Invoke;
+
class ValueSerializerTest : public TestWithIsolate {
protected:
ValueSerializerTest()
: serialization_context_(Context::New(isolate())),
- deserialization_context_(Context::New(isolate())) {}
+ deserialization_context_(Context::New(isolate())) {
+ // Create a host object type that can be tested through
+ // serialization/deserialization delegates below.
+ Local<FunctionTemplate> function_template = v8::FunctionTemplate::New(
+ isolate(), [](const FunctionCallbackInfo<Value>& args) {
+ args.Holder()->SetInternalField(0, args[0]);
+ args.Holder()->SetInternalField(1, args[1]);
+ });
+ function_template->InstanceTemplate()->SetInternalFieldCount(2);
+ function_template->InstanceTemplate()->SetAccessor(
+ StringFromUtf8("value"),
+ [](Local<String> property, const PropertyCallbackInfo<Value>& args) {
+ args.GetReturnValue().Set(args.Holder()->GetInternalField(0));
+ });
+ function_template->InstanceTemplate()->SetAccessor(
+ StringFromUtf8("value2"),
+ [](Local<String> property, const PropertyCallbackInfo<Value>& args) {
+ args.GetReturnValue().Set(args.Holder()->GetInternalField(1));
+ });
+ for (Local<Context> context :
+ {serialization_context_, deserialization_context_}) {
+ context->Global()
+ ->CreateDataProperty(
+ context, StringFromUtf8("ExampleHostObject"),
+ function_template->GetFunction(context).ToLocalChecked())
+ .ToChecked();
+ }
+ host_object_constructor_template_ = function_template;
+ }
const Local<Context>& serialization_context() {
return serialization_context_;
@@ -29,6 +61,14 @@ class ValueSerializerTest : public TestWithIsolate {
return deserialization_context_;
}
+ // Overridden in more specific fixtures.
+ virtual ValueSerializer::Delegate* GetSerializerDelegate() { return nullptr; }
+ virtual void BeforeEncode(ValueSerializer*) {}
+ virtual ValueDeserializer::Delegate* GetDeserializerDelegate() {
+ return nullptr;
+ }
+ virtual void BeforeDecode(ValueDeserializer*) {}
+
template <typename InputFunctor, typename OutputFunctor>
void RoundTripTest(const InputFunctor& input_functor,
const OutputFunctor& output_functor) {
@@ -46,20 +86,30 @@ class ValueSerializerTest : public TestWithIsolate {
output_functor);
}
+ // Variant which uses JSON.parse/stringify to check the result.
+ void RoundTripJSON(const char* source) {
+ RoundTripTest(
+ [this, source]() {
+ return JSON::Parse(serialization_context_, StringFromUtf8(source))
+ .ToLocalChecked();
+ },
+ [this, source](Local<Value> value) {
+ ASSERT_TRUE(value->IsObject());
+ EXPECT_EQ(source, Utf8Value(JSON::Stringify(deserialization_context_,
+ value.As<Object>())
+ .ToLocalChecked()));
+ });
+ }
+
Maybe<std::vector<uint8_t>> DoEncode(Local<Value> value) {
- // This approximates what the API implementation would do.
- // TODO(jbroman): Use the public API once it exists.
- i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate());
- i::HandleScope handle_scope(internal_isolate);
- i::ValueSerializer serializer(internal_isolate);
+ Local<Context> context = serialization_context();
+ ValueSerializer serializer(isolate(), GetSerializerDelegate());
+ BeforeEncode(&serializer);
serializer.WriteHeader();
- if (serializer.WriteObject(Utils::OpenHandle(*value)).FromMaybe(false)) {
- return Just(serializer.ReleaseBuffer());
- }
- if (internal_isolate->has_pending_exception()) {
- internal_isolate->OptionalRescheduleException(true);
+ if (!serializer.WriteValue(context, value).FromMaybe(false)) {
+ return Nothing<std::vector<uint8_t>>();
}
- return Nothing<std::vector<uint8_t>>();
+ return Just(serializer.ReleaseBuffer());
}
template <typename InputFunctor, typename EncodedDataFunctor>
@@ -90,24 +140,23 @@ class ValueSerializerTest : public TestWithIsolate {
template <typename OutputFunctor>
void DecodeTest(const std::vector<uint8_t>& data,
const OutputFunctor& output_functor) {
- Context::Scope scope(deserialization_context());
+ Local<Context> context = deserialization_context();
+ Context::Scope scope(context);
TryCatch try_catch(isolate());
- // TODO(jbroman): Use the public API once it exists.
- i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate());
- i::HandleScope handle_scope(internal_isolate);
- i::ValueDeserializer deserializer(
- internal_isolate,
- i::Vector<const uint8_t>(&data[0], static_cast<int>(data.size())));
- ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false));
+ ValueDeserializer deserializer(isolate(), &data[0],
+ static_cast<int>(data.size()),
+ GetDeserializerDelegate());
+ deserializer.SetSupportsLegacyWireFormat(true);
+ BeforeDecode(&deserializer);
+ ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
Local<Value> result;
- ASSERT_TRUE(ToLocal<Value>(deserializer.ReadObject(), &result));
+ ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result));
ASSERT_FALSE(result.IsEmpty());
ASSERT_FALSE(try_catch.HasCaught());
- ASSERT_TRUE(deserialization_context()
- ->Global()
- ->CreateDataProperty(deserialization_context_,
- StringFromUtf8("result"), result)
- .FromMaybe(false));
+ ASSERT_TRUE(
+ context->Global()
+ ->CreateDataProperty(context, StringFromUtf8("result"), result)
+ .FromMaybe(false));
output_functor(result);
ASSERT_FALSE(try_catch.HasCaught());
}
@@ -115,43 +164,45 @@ class ValueSerializerTest : public TestWithIsolate {
template <typename OutputFunctor>
void DecodeTestForVersion0(const std::vector<uint8_t>& data,
const OutputFunctor& output_functor) {
- Context::Scope scope(deserialization_context());
+ Local<Context> context = deserialization_context();
+ Context::Scope scope(context);
TryCatch try_catch(isolate());
- // TODO(jbroman): Use the public API once it exists.
- i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate());
- i::HandleScope handle_scope(internal_isolate);
- i::ValueDeserializer deserializer(
- internal_isolate,
- i::Vector<const uint8_t>(&data[0], static_cast<int>(data.size())));
- // TODO(jbroman): Enable legacy support.
- ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false));
- // TODO(jbroman): Check version 0.
+ ValueDeserializer deserializer(isolate(), &data[0],
+ static_cast<int>(data.size()),
+ GetDeserializerDelegate());
+ deserializer.SetSupportsLegacyWireFormat(true);
+ BeforeDecode(&deserializer);
+ ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
+ ASSERT_EQ(0, deserializer.GetWireFormatVersion());
Local<Value> result;
- ASSERT_TRUE(ToLocal<Value>(
- deserializer.ReadObjectUsingEntireBufferForLegacyFormat(), &result));
+ ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result));
ASSERT_FALSE(result.IsEmpty());
ASSERT_FALSE(try_catch.HasCaught());
- ASSERT_TRUE(deserialization_context()
- ->Global()
- ->CreateDataProperty(deserialization_context_,
- StringFromUtf8("result"), result)
- .FromMaybe(false));
+ ASSERT_TRUE(
+ context->Global()
+ ->CreateDataProperty(context, StringFromUtf8("result"), result)
+ .FromMaybe(false));
output_functor(result);
ASSERT_FALSE(try_catch.HasCaught());
}
void InvalidDecodeTest(const std::vector<uint8_t>& data) {
- Context::Scope scope(deserialization_context());
+ Local<Context> context = deserialization_context();
+ Context::Scope scope(context);
TryCatch try_catch(isolate());
- i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate());
- i::HandleScope handle_scope(internal_isolate);
- i::ValueDeserializer deserializer(
- internal_isolate,
- i::Vector<const uint8_t>(&data[0], static_cast<int>(data.size())));
- Maybe<bool> header_result = deserializer.ReadHeader();
- if (header_result.IsNothing()) return;
+ ValueDeserializer deserializer(isolate(), &data[0],
+ static_cast<int>(data.size()),
+ GetDeserializerDelegate());
+ deserializer.SetSupportsLegacyWireFormat(true);
+ BeforeDecode(&deserializer);
+ Maybe<bool> header_result = deserializer.ReadHeader(context);
+ if (header_result.IsNothing()) {
+ EXPECT_TRUE(try_catch.HasCaught());
+ return;
+ }
ASSERT_TRUE(header_result.ToChecked());
- ASSERT_TRUE(deserializer.ReadObject().is_null());
+ ASSERT_TRUE(deserializer.ReadValue(context).IsEmpty());
+ EXPECT_TRUE(try_catch.HasCaught());
}
Local<Value> EvaluateScriptForInput(const char* utf8_source) {
@@ -179,9 +230,18 @@ class ValueSerializerTest : public TestWithIsolate {
return std::string(*utf8, utf8.length());
}
+ Local<Object> NewHostObject(Local<Context> context, int argc,
+ Local<Value> argv[]) {
+ return host_object_constructor_template_->GetFunction(context)
+ .ToLocalChecked()
+ ->NewInstance(context, argc, argv)
+ .ToLocalChecked();
+ }
+
private:
Local<Context> serialization_context_;
Local<Context> deserialization_context_;
+ Local<FunctionTemplate> host_object_constructor_template_;
DISALLOW_COPY_AND_ASSIGN(ValueSerializerTest);
};
@@ -659,6 +719,31 @@ TEST_F(ValueSerializerTest, RoundTripTrickyGetters) {
});
}
+TEST_F(ValueSerializerTest, RoundTripDictionaryObjectForTransitions) {
+ // A case which should run on the fast path, and should reach all of the
+ // different cases:
+ // 1. no known transition (first time creating this kind of object)
+ // 2. expected transitions match to end
+ // 3. transition partially matches, but falls back due to new property 'w'
+ // 4. transition to 'z' is now a full transition (needs to be looked up)
+ // 5. same for 'w'
+ // 6. new property after complex transition succeeded
+ // 7. new property after complex transition failed (due to new property)
+ RoundTripJSON(
+ "[{\"x\":1,\"y\":2,\"z\":3}"
+ ",{\"x\":4,\"y\":5,\"z\":6}"
+ ",{\"x\":5,\"y\":6,\"w\":7}"
+ ",{\"x\":6,\"y\":7,\"z\":8}"
+ ",{\"x\":0,\"y\":0,\"w\":0}"
+ ",{\"x\":3,\"y\":1,\"w\":4,\"z\":1}"
+ ",{\"x\":5,\"y\":9,\"k\":2,\"z\":6}]");
+ // A simpler case that uses two-byte strings.
+ RoundTripJSON(
+ "[{\"\xF0\x9F\x91\x8A\":1,\"\xF0\x9F\x91\x8B\":2}"
+ ",{\"\xF0\x9F\x91\x8A\":3,\"\xF0\x9F\x91\x8C\":4}"
+ ",{\"\xF0\x9F\x91\x8A\":5,\"\xF0\x9F\x91\x9B\":6}]");
+}
+
TEST_F(ValueSerializerTest, DecodeDictionaryObjectVersion0) {
// Empty object.
DecodeTestForVersion0(
@@ -950,6 +1035,19 @@ TEST_F(ValueSerializerTest, RoundTripArrayWithTrickyGetters) {
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === 1"));
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(2)"));
});
+ // The same is true if the length is shortened, but there are still items
+ // remaining.
+ RoundTripTest(
+ "(() => {"
+ " var x = [1, { get a() { x.length = 3; }}, 3, 4];"
+ " return x;"
+ "})()",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsArray());
+ ASSERT_EQ(4, Array::Cast(*value)->Length());
+ EXPECT_TRUE(EvaluateScriptForResultBool("result[2] === 3"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(3)"));
+ });
// Same for sparse arrays.
RoundTripTest(
"(() => {"
@@ -963,6 +1061,18 @@ TEST_F(ValueSerializerTest, RoundTripArrayWithTrickyGetters) {
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === 1"));
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(2)"));
});
+ RoundTripTest(
+ "(() => {"
+ " var x = [1, { get a() { x.length = 3; }}, 3, 4];"
+ " x.length = 1000;"
+ " return x;"
+ "})()",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsArray());
+ ASSERT_EQ(1000, Array::Cast(*value)->Length());
+ EXPECT_TRUE(EvaluateScriptForResultBool("result[2] === 3"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(3)"));
+ });
// If a getter makes a property non-enumerable, it should still be enumerated
// as enumeration happens once before getters are invoked.
RoundTripTest(
@@ -1364,5 +1474,889 @@ TEST_F(ValueSerializerTest, DecodeRegExp) {
});
}
+TEST_F(ValueSerializerTest, RoundTripMap) {
+ RoundTripTest(
+ "(() => { var m = new Map(); m.set(42, 'foo'); return m; })()",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsMap());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === Map.prototype"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 1"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.get(42) === 'foo'"));
+ });
+ RoundTripTest("(() => { var m = new Map(); m.set(m, m); return m; })()",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsMap());
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 1"));
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "result.get(result) === result"));
+ });
+ // Iteration order must be preserved.
+ RoundTripTest(
+ "(() => {"
+ " var m = new Map();"
+ " m.set(1, 0); m.set('a', 0); m.set(3, 0); m.set(2, 0);"
+ " return m;"
+ "})()",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsMap());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Array.from(result.keys()).toString() === '1,a,3,2'"));
+ });
+}
+
+TEST_F(ValueSerializerTest, DecodeMap) {
+ DecodeTest(
+ {0xff, 0x09, 0x3f, 0x00, 0x3b, 0x3f, 0x01, 0x49, 0x54, 0x3f, 0x01, 0x53,
+ 0x03, 0x66, 0x6f, 0x6f, 0x3a, 0x02},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsMap());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === Map.prototype"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 1"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.get(42) === 'foo'"));
+ });
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3b, 0x3f, 0x01, 0x5e, 0x00, 0x3f, 0x01,
+ 0x5e, 0x00, 0x3a, 0x02, 0x00},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsMap());
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 1"));
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "result.get(result) === result"));
+ });
+ // Iteration order must be preserved.
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3b, 0x3f, 0x01, 0x49, 0x02, 0x3f,
+ 0x01, 0x49, 0x00, 0x3f, 0x01, 0x53, 0x01, 0x61, 0x3f, 0x01,
+ 0x49, 0x00, 0x3f, 0x01, 0x49, 0x06, 0x3f, 0x01, 0x49, 0x00,
+ 0x3f, 0x01, 0x49, 0x04, 0x3f, 0x01, 0x49, 0x00, 0x3a, 0x08},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsMap());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Array.from(result.keys()).toString() === '1,a,3,2'"));
+ });
+}
+
+TEST_F(ValueSerializerTest, RoundTripMapWithTrickyGetters) {
+ // Even if an entry is removed or reassigned, the original key/value pair is
+ // used.
+ RoundTripTest(
+ "(() => {"
+ " var m = new Map();"
+ " m.set(0, { get a() {"
+ " m.delete(1); m.set(2, 'baz'); m.set(3, 'quux');"
+ " }});"
+ " m.set(1, 'foo');"
+ " m.set(2, 'bar');"
+ " return m;"
+ "})()",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsMap());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Array.from(result.keys()).toString() === '0,1,2'"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.get(1) === 'foo'"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.get(2) === 'bar'"));
+ });
+ // However, deeper modifications of objects yet to be serialized still apply.
+ RoundTripTest(
+ "(() => {"
+ " var m = new Map();"
+ " var key = { get a() { value.foo = 'bar'; } };"
+ " var value = { get a() { key.baz = 'quux'; } };"
+ " m.set(key, value);"
+ " return m;"
+ "})()",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsMap());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "!('baz' in Array.from(result.keys())[0])"));
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Array.from(result.values())[0].foo === 'bar'"));
+ });
+}
+
+TEST_F(ValueSerializerTest, RoundTripSet) {
+ RoundTripTest(
+ "(() => { var s = new Set(); s.add(42); s.add('foo'); return s; })()",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsSet());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === Set.prototype"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 2"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.has(42)"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.has('foo')"));
+ });
+ RoundTripTest(
+ "(() => { var s = new Set(); s.add(s); return s; })()",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsSet());
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 1"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.has(result)"));
+ });
+ // Iteration order must be preserved.
+ RoundTripTest(
+ "(() => {"
+ " var s = new Set();"
+ " s.add(1); s.add('a'); s.add(3); s.add(2);"
+ " return s;"
+ "})()",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsSet());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Array.from(result.keys()).toString() === '1,a,3,2'"));
+ });
+}
+
+TEST_F(ValueSerializerTest, DecodeSet) {
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x27, 0x3f, 0x01, 0x49, 0x54, 0x3f, 0x01,
+ 0x53, 0x03, 0x66, 0x6f, 0x6f, 0x2c, 0x02},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsSet());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === Set.prototype"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 2"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.has(42)"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.has('foo')"));
+ });
+ DecodeTest(
+ {0xff, 0x09, 0x3f, 0x00, 0x27, 0x3f, 0x01, 0x5e, 0x00, 0x2c, 0x01, 0x00},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsSet());
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 1"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.has(result)"));
+ });
+ // Iteration order must be preserved.
+ DecodeTest(
+ {0xff, 0x09, 0x3f, 0x00, 0x27, 0x3f, 0x01, 0x49, 0x02, 0x3f, 0x01, 0x53,
+ 0x01, 0x61, 0x3f, 0x01, 0x49, 0x06, 0x3f, 0x01, 0x49, 0x04, 0x2c, 0x04},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsSet());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Array.from(result.keys()).toString() === '1,a,3,2'"));
+ });
+}
+
+TEST_F(ValueSerializerTest, RoundTripSetWithTrickyGetters) {
+ // Even if an element is added or removed during serialization, the original
+ // set of elements is used.
+ RoundTripTest(
+ "(() => {"
+ " var s = new Set();"
+ " s.add({ get a() { s.delete(1); s.add(2); } });"
+ " s.add(1);"
+ " return s;"
+ "})()",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsSet());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Array.from(result.keys()).toString() === '[object Object],1'"));
+ });
+ // However, deeper modifications of objects yet to be serialized still apply.
+ RoundTripTest(
+ "(() => {"
+ " var s = new Set();"
+ " var first = { get a() { second.foo = 'bar'; } };"
+ " var second = { get a() { first.baz = 'quux'; } };"
+ " s.add(first);"
+ " s.add(second);"
+ " return s;"
+ "})()",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsSet());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "!('baz' in Array.from(result.keys())[0])"));
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Array.from(result.keys())[1].foo === 'bar'"));
+ });
+}
+
+TEST_F(ValueSerializerTest, RoundTripArrayBuffer) {
+ RoundTripTest("new ArrayBuffer()", [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsArrayBuffer());
+ EXPECT_EQ(0u, ArrayBuffer::Cast(*value)->ByteLength());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === ArrayBuffer.prototype"));
+ });
+ RoundTripTest("new Uint8Array([0, 128, 255]).buffer",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsArrayBuffer());
+ EXPECT_EQ(3u, ArrayBuffer::Cast(*value)->ByteLength());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "new Uint8Array(result).toString() === '0,128,255'"));
+ });
+ RoundTripTest(
+ "({ a: new ArrayBuffer(), get b() { return this.a; }})",
+ [this](Local<Value> value) {
+ EXPECT_TRUE(
+ EvaluateScriptForResultBool("result.a instanceof ArrayBuffer"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
+ });
+}
+
+TEST_F(ValueSerializerTest, DecodeArrayBuffer) {
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x42, 0x00},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsArrayBuffer());
+ EXPECT_EQ(0u, ArrayBuffer::Cast(*value)->ByteLength());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === ArrayBuffer.prototype"));
+ });
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x42, 0x03, 0x00, 0x80, 0xff, 0x00},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsArrayBuffer());
+ EXPECT_EQ(3u, ArrayBuffer::Cast(*value)->ByteLength());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "new Uint8Array(result).toString() === '0,128,255'"));
+ });
+ DecodeTest(
+ {0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x53, 0x01,
+ 0x61, 0x3f, 0x01, 0x42, 0x00, 0x3f, 0x02, 0x53, 0x01,
+ 0x62, 0x3f, 0x02, 0x5e, 0x01, 0x7b, 0x02, 0x00},
+ [this](Local<Value> value) {
+ EXPECT_TRUE(
+ EvaluateScriptForResultBool("result.a instanceof ArrayBuffer"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
+ });
+}
+
+TEST_F(ValueSerializerTest, DecodeInvalidArrayBuffer) {
+ InvalidDecodeTest({0xff, 0x09, 0x42, 0xff, 0xff, 0x00});
+}
+
+// Includes an ArrayBuffer wrapper marked for transfer from the serialization
+// context to the deserialization context.
+class ValueSerializerTestWithArrayBufferTransfer : public ValueSerializerTest {
+ protected:
+ static const size_t kTestByteLength = 4;
+
+ ValueSerializerTestWithArrayBufferTransfer() {
+ {
+ Context::Scope scope(serialization_context());
+ input_buffer_ = ArrayBuffer::New(isolate(), nullptr, 0);
+ input_buffer_->Neuter();
+ }
+ {
+ Context::Scope scope(deserialization_context());
+ output_buffer_ = ArrayBuffer::New(isolate(), kTestByteLength);
+ const uint8_t data[kTestByteLength] = {0x00, 0x01, 0x80, 0xff};
+ memcpy(output_buffer_->GetContents().Data(), data, kTestByteLength);
+ }
+ }
+
+ const Local<ArrayBuffer>& input_buffer() { return input_buffer_; }
+ const Local<ArrayBuffer>& output_buffer() { return output_buffer_; }
+
+ void BeforeEncode(ValueSerializer* serializer) override {
+ serializer->TransferArrayBuffer(0, input_buffer_);
+ }
+
+ void BeforeDecode(ValueDeserializer* deserializer) override {
+ deserializer->TransferArrayBuffer(0, output_buffer_);
+ }
+
+ private:
+ Local<ArrayBuffer> input_buffer_;
+ Local<ArrayBuffer> output_buffer_;
+};
+
+TEST_F(ValueSerializerTestWithArrayBufferTransfer,
+ RoundTripArrayBufferTransfer) {
+ RoundTripTest([this]() { return input_buffer(); },
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsArrayBuffer());
+ EXPECT_EQ(output_buffer(), value);
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "new Uint8Array(result).toString() === '0,1,128,255'"));
+ });
+ RoundTripTest(
+ [this]() {
+ Local<Object> object = Object::New(isolate());
+ EXPECT_TRUE(object
+ ->CreateDataProperty(serialization_context(),
+ StringFromUtf8("a"),
+ input_buffer())
+ .FromMaybe(false));
+ EXPECT_TRUE(object
+ ->CreateDataProperty(serialization_context(),
+ StringFromUtf8("b"),
+ input_buffer())
+ .FromMaybe(false));
+ return object;
+ },
+ [this](Local<Value> value) {
+ EXPECT_TRUE(
+ EvaluateScriptForResultBool("result.a instanceof ArrayBuffer"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "new Uint8Array(result.a).toString() === '0,1,128,255'"));
+ });
+}
+
+TEST_F(ValueSerializerTest, RoundTripTypedArray) {
+// Check that the right type comes out the other side for every kind of typed
+// array.
+#define TYPED_ARRAY_ROUND_TRIP_TEST(Type, type, TYPE, ctype, size) \
+ RoundTripTest("new " #Type "Array(2)", [this](Local<Value> value) { \
+ ASSERT_TRUE(value->Is##Type##Array()); \
+ EXPECT_EQ(2 * size, TypedArray::Cast(*value)->ByteLength()); \
+ EXPECT_EQ(2, TypedArray::Cast(*value)->Length()); \
+ EXPECT_TRUE(EvaluateScriptForResultBool( \
+ "Object.getPrototypeOf(result) === " #Type "Array.prototype")); \
+ });
+ TYPED_ARRAYS(TYPED_ARRAY_ROUND_TRIP_TEST)
+#undef TYPED_ARRAY_CASE
+
+ // Check that values of various kinds are suitably preserved.
+ RoundTripTest("new Uint8Array([1, 128, 255])", [this](Local<Value> value) {
+ EXPECT_TRUE(
+ EvaluateScriptForResultBool("result.toString() === '1,128,255'"));
+ });
+ RoundTripTest("new Int16Array([0, 256, -32768])", [this](Local<Value> value) {
+ EXPECT_TRUE(
+ EvaluateScriptForResultBool("result.toString() === '0,256,-32768'"));
+ });
+ RoundTripTest("new Float32Array([0, -0.5, NaN, Infinity])",
+ [this](Local<Value> value) {
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "result.toString() === '0,-0.5,NaN,Infinity'"));
+ });
+
+ // Array buffer views sharing a buffer should do so on the other side.
+ // Similarly, multiple references to the same typed array should be resolved.
+ RoundTripTest(
+ "(() => {"
+ " var buffer = new ArrayBuffer(32);"
+ " return {"
+ " u8: new Uint8Array(buffer),"
+ " get u8_2() { return this.u8; },"
+ " f32: new Float32Array(buffer, 4, 5),"
+ " b: buffer,"
+ " };"
+ "})()",
+ [this](Local<Value> value) {
+ EXPECT_TRUE(
+ EvaluateScriptForResultBool("result.u8 instanceof Uint8Array"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.u8 === result.u8_2"));
+ EXPECT_TRUE(
+ EvaluateScriptForResultBool("result.f32 instanceof Float32Array"));
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "result.u8.buffer === result.f32.buffer"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.f32.byteOffset === 4"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.f32.length === 5"));
+ });
+}
+
+TEST_F(ValueSerializerTest, DecodeTypedArray) {
+ // Check that the right type comes out the other side for every kind of typed
+ // array.
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3f, 0x00, 0x42, 0x02, 0x00, 0x00, 0x56,
+ 0x42, 0x00, 0x02},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsUint8Array());
+ EXPECT_EQ(2, TypedArray::Cast(*value)->ByteLength());
+ EXPECT_EQ(2, TypedArray::Cast(*value)->Length());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === Uint8Array.prototype"));
+ });
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3f, 0x00, 0x42, 0x02, 0x00, 0x00, 0x56,
+ 0x62, 0x00, 0x02},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsInt8Array());
+ EXPECT_EQ(2, TypedArray::Cast(*value)->ByteLength());
+ EXPECT_EQ(2, TypedArray::Cast(*value)->Length());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === Int8Array.prototype"));
+ });
+#if defined(V8_TARGET_LITTLE_ENDIAN)
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3f, 0x00, 0x42, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x56, 0x57, 0x00, 0x04},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsUint16Array());
+ EXPECT_EQ(4, TypedArray::Cast(*value)->ByteLength());
+ EXPECT_EQ(2, TypedArray::Cast(*value)->Length());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === Uint16Array.prototype"));
+ });
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3f, 0x00, 0x42, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x56, 0x77, 0x00, 0x04},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsInt16Array());
+ EXPECT_EQ(4, TypedArray::Cast(*value)->ByteLength());
+ EXPECT_EQ(2, TypedArray::Cast(*value)->Length());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === Int16Array.prototype"));
+ });
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3f, 0x00, 0x42, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x44, 0x00, 0x08},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsUint32Array());
+ EXPECT_EQ(8, TypedArray::Cast(*value)->ByteLength());
+ EXPECT_EQ(2, TypedArray::Cast(*value)->Length());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === Uint32Array.prototype"));
+ });
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3f, 0x00, 0x42, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x64, 0x00, 0x08},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsInt32Array());
+ EXPECT_EQ(8, TypedArray::Cast(*value)->ByteLength());
+ EXPECT_EQ(2, TypedArray::Cast(*value)->Length());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === Int32Array.prototype"));
+ });
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3f, 0x00, 0x42, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x66, 0x00, 0x08},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsFloat32Array());
+ EXPECT_EQ(8, TypedArray::Cast(*value)->ByteLength());
+ EXPECT_EQ(2, TypedArray::Cast(*value)->Length());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === Float32Array.prototype"));
+ });
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3f, 0x00, 0x42, 0x10, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x56, 0x46, 0x00, 0x10},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsFloat64Array());
+ EXPECT_EQ(16, TypedArray::Cast(*value)->ByteLength());
+ EXPECT_EQ(2, TypedArray::Cast(*value)->Length());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === Float64Array.prototype"));
+ });
+#endif // V8_TARGET_LITTLE_ENDIAN
+
+ // Check that values of various kinds are suitably preserved.
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3f, 0x00, 0x42, 0x03, 0x01, 0x80, 0xff,
+ 0x56, 0x42, 0x00, 0x03, 0x00},
+ [this](Local<Value> value) {
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "result.toString() === '1,128,255'"));
+ });
+#if defined(V8_TARGET_LITTLE_ENDIAN)
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3f, 0x00, 0x42, 0x06, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x80, 0x56, 0x77, 0x00, 0x06},
+ [this](Local<Value> value) {
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "result.toString() === '0,256,-32768'"));
+ });
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3f, 0x00, 0x42, 0x10, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x00, 0x00, 0xc0, 0x7f,
+ 0x00, 0x00, 0x80, 0x7f, 0x56, 0x66, 0x00, 0x10},
+ [this](Local<Value> value) {
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "result.toString() === '0,-0.5,NaN,Infinity'"));
+ });
+#endif // V8_TARGET_LITTLE_ENDIAN
+
+ // Array buffer views sharing a buffer should do so on the other side.
+ // Similarly, multiple references to the same typed array should be resolved.
+ DecodeTest(
+ {0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x53, 0x02, 0x75, 0x38, 0x3f,
+ 0x01, 0x3f, 0x01, 0x42, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x56, 0x42, 0x00, 0x20, 0x3f, 0x03, 0x53, 0x04, 0x75, 0x38, 0x5f,
+ 0x32, 0x3f, 0x03, 0x5e, 0x02, 0x3f, 0x03, 0x53, 0x03, 0x66, 0x33, 0x32,
+ 0x3f, 0x03, 0x3f, 0x03, 0x5e, 0x01, 0x56, 0x66, 0x04, 0x14, 0x3f, 0x04,
+ 0x53, 0x01, 0x62, 0x3f, 0x04, 0x5e, 0x01, 0x7b, 0x04, 0x00},
+ [this](Local<Value> value) {
+ EXPECT_TRUE(
+ EvaluateScriptForResultBool("result.u8 instanceof Uint8Array"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.u8 === result.u8_2"));
+ EXPECT_TRUE(
+ EvaluateScriptForResultBool("result.f32 instanceof Float32Array"));
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "result.u8.buffer === result.f32.buffer"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.f32.byteOffset === 4"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.f32.length === 5"));
+ });
+}
+
+TEST_F(ValueSerializerTest, DecodeInvalidTypedArray) {
+ // Byte offset out of range.
+ InvalidDecodeTest(
+ {0xff, 0x09, 0x42, 0x02, 0x00, 0x00, 0x56, 0x42, 0x03, 0x01});
+ // Byte offset in range, offset + length out of range.
+ InvalidDecodeTest(
+ {0xff, 0x09, 0x42, 0x02, 0x00, 0x00, 0x56, 0x42, 0x01, 0x03});
+ // Byte offset not divisible by element size.
+ InvalidDecodeTest(
+ {0xff, 0x09, 0x42, 0x04, 0x00, 0x00, 0x00, 0x00, 0x56, 0x77, 0x01, 0x02});
+ // Byte length not divisible by element size.
+ InvalidDecodeTest(
+ {0xff, 0x09, 0x42, 0x04, 0x00, 0x00, 0x00, 0x00, 0x56, 0x77, 0x02, 0x01});
+}
+
+TEST_F(ValueSerializerTest, RoundTripDataView) {
+ RoundTripTest("new DataView(new ArrayBuffer(4), 1, 2)",
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsDataView());
+ EXPECT_EQ(1, DataView::Cast(*value)->ByteOffset());
+ EXPECT_EQ(2, DataView::Cast(*value)->ByteLength());
+ EXPECT_EQ(4, DataView::Cast(*value)->Buffer()->ByteLength());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === DataView.prototype"));
+ });
+}
+
+TEST_F(ValueSerializerTest, DecodeDataView) {
+ DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x3f, 0x00, 0x42, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x56, 0x3f, 0x01, 0x02},
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsDataView());
+ EXPECT_EQ(1, DataView::Cast(*value)->ByteOffset());
+ EXPECT_EQ(2, DataView::Cast(*value)->ByteLength());
+ EXPECT_EQ(4, DataView::Cast(*value)->Buffer()->ByteLength());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === DataView.prototype"));
+ });
+}
+
+TEST_F(ValueSerializerTest, DecodeInvalidDataView) {
+ // Byte offset out of range.
+ InvalidDecodeTest(
+ {0xff, 0x09, 0x42, 0x02, 0x00, 0x00, 0x56, 0x3f, 0x03, 0x01});
+ // Byte offset in range, offset + length out of range.
+ InvalidDecodeTest(
+ {0xff, 0x09, 0x42, 0x02, 0x00, 0x00, 0x56, 0x3f, 0x01, 0x03});
+}
+
+class ValueSerializerTestWithSharedArrayBufferTransfer
+ : public ValueSerializerTest {
+ protected:
+ static const size_t kTestByteLength = 4;
+
+ ValueSerializerTestWithSharedArrayBufferTransfer() {
+ const uint8_t data[kTestByteLength] = {0x00, 0x01, 0x80, 0xff};
+ memcpy(data_, data, kTestByteLength);
+ {
+ Context::Scope scope(serialization_context());
+ input_buffer_ =
+ SharedArrayBuffer::New(isolate(), &data_, kTestByteLength);
+ }
+ {
+ Context::Scope scope(deserialization_context());
+ output_buffer_ =
+ SharedArrayBuffer::New(isolate(), &data_, kTestByteLength);
+ }
+ }
+
+ const Local<SharedArrayBuffer>& input_buffer() { return input_buffer_; }
+ const Local<SharedArrayBuffer>& output_buffer() { return output_buffer_; }
+
+ void BeforeEncode(ValueSerializer* serializer) override {
+ serializer->TransferSharedArrayBuffer(0, input_buffer_);
+ }
+
+ void BeforeDecode(ValueDeserializer* deserializer) override {
+ deserializer->TransferSharedArrayBuffer(0, output_buffer_);
+ }
+
+ static void SetUpTestCase() {
+ flag_was_enabled_ = i::FLAG_harmony_sharedarraybuffer;
+ i::FLAG_harmony_sharedarraybuffer = true;
+ ValueSerializerTest::SetUpTestCase();
+ }
+
+ static void TearDownTestCase() {
+ ValueSerializerTest::TearDownTestCase();
+ i::FLAG_harmony_sharedarraybuffer = flag_was_enabled_;
+ flag_was_enabled_ = false;
+ }
+
+ private:
+ static bool flag_was_enabled_;
+ uint8_t data_[kTestByteLength];
+ Local<SharedArrayBuffer> input_buffer_;
+ Local<SharedArrayBuffer> output_buffer_;
+};
+
+bool ValueSerializerTestWithSharedArrayBufferTransfer::flag_was_enabled_ =
+ false;
+
+TEST_F(ValueSerializerTestWithSharedArrayBufferTransfer,
+ RoundTripSharedArrayBufferTransfer) {
+ RoundTripTest([this]() { return input_buffer(); },
+ [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsSharedArrayBuffer());
+ EXPECT_EQ(output_buffer(), value);
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "new Uint8Array(result).toString() === '0,1,128,255'"));
+ });
+ RoundTripTest(
+ [this]() {
+ Local<Object> object = Object::New(isolate());
+ EXPECT_TRUE(object
+ ->CreateDataProperty(serialization_context(),
+ StringFromUtf8("a"),
+ input_buffer())
+ .FromMaybe(false));
+ EXPECT_TRUE(object
+ ->CreateDataProperty(serialization_context(),
+ StringFromUtf8("b"),
+ input_buffer())
+ .FromMaybe(false));
+ return object;
+ },
+ [this](Local<Value> value) {
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "result.a instanceof SharedArrayBuffer"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "new Uint8Array(result.a).toString() === '0,1,128,255'"));
+ });
+}
+
+TEST_F(ValueSerializerTestWithSharedArrayBufferTransfer,
+ SharedArrayBufferMustBeTransferred) {
+ // A SharedArrayBuffer which was not marked for transfer should fail encoding.
+ InvalidEncodeTest("new SharedArrayBuffer(32)");
+}
+
+TEST_F(ValueSerializerTest, UnsupportedHostObject) {
+ InvalidEncodeTest("new ExampleHostObject()");
+ InvalidEncodeTest("({ a: new ExampleHostObject() })");
+}
+
+class ValueSerializerTestWithHostObject : public ValueSerializerTest {
+ protected:
+ ValueSerializerTestWithHostObject() : serializer_delegate_(this) {}
+
+ static const uint8_t kExampleHostObjectTag;
+
+ void WriteExampleHostObjectTag() {
+ serializer_->WriteRawBytes(&kExampleHostObjectTag, 1);
+ }
+
+ bool ReadExampleHostObjectTag() {
+ const void* tag;
+ return deserializer_->ReadRawBytes(1, &tag) &&
+ *reinterpret_cast<const uint8_t*>(tag) == kExampleHostObjectTag;
+ }
+
+// GMock doesn't use the "override" keyword.
+#if __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Winconsistent-missing-override"
+#endif
+
+ class SerializerDelegate : public ValueSerializer::Delegate {
+ public:
+ explicit SerializerDelegate(ValueSerializerTestWithHostObject* test)
+ : test_(test) {}
+ MOCK_METHOD2(WriteHostObject,
+ Maybe<bool>(Isolate* isolate, Local<Object> object));
+ void ThrowDataCloneError(Local<String> message) override {
+ test_->isolate()->ThrowException(Exception::Error(message));
+ }
+
+ private:
+ ValueSerializerTestWithHostObject* test_;
+ };
+
+ class DeserializerDelegate : public ValueDeserializer::Delegate {
+ public:
+ MOCK_METHOD1(ReadHostObject, MaybeLocal<Object>(Isolate* isolate));
+ };
+
+#if __clang__
+#pragma clang diagnostic pop
+#endif
+
+ ValueSerializer::Delegate* GetSerializerDelegate() override {
+ return &serializer_delegate_;
+ }
+ void BeforeEncode(ValueSerializer* serializer) override {
+ serializer_ = serializer;
+ }
+ ValueDeserializer::Delegate* GetDeserializerDelegate() override {
+ return &deserializer_delegate_;
+ }
+ void BeforeDecode(ValueDeserializer* deserializer) override {
+ deserializer_ = deserializer;
+ }
+
+ SerializerDelegate serializer_delegate_;
+ DeserializerDelegate deserializer_delegate_;
+ ValueSerializer* serializer_;
+ ValueDeserializer* deserializer_;
+
+ friend class SerializerDelegate;
+ friend class DeserializerDelegate;
+};
+
+// This is a tag that's not used in V8.
+const uint8_t ValueSerializerTestWithHostObject::kExampleHostObjectTag = '+';
+
+TEST_F(ValueSerializerTestWithHostObject, RoundTripUint32) {
+ // The host can serialize data as uint32_t.
+ EXPECT_CALL(serializer_delegate_, WriteHostObject(isolate(), _))
+ .WillRepeatedly(Invoke([this](Isolate*, Local<Object> object) {
+ uint32_t value = 0;
+ EXPECT_TRUE(object->GetInternalField(0)
+ ->Uint32Value(serialization_context())
+ .To(&value));
+ WriteExampleHostObjectTag();
+ serializer_->WriteUint32(value);
+ return Just(true);
+ }));
+ EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
+ .WillRepeatedly(Invoke([this](Isolate*) {
+ EXPECT_TRUE(ReadExampleHostObjectTag());
+ uint32_t value = 0;
+ EXPECT_TRUE(deserializer_->ReadUint32(&value));
+ Local<Value> argv[] = {Integer::NewFromUnsigned(isolate(), value)};
+ return NewHostObject(deserialization_context(), arraysize(argv), argv);
+ }));
+ RoundTripTest("new ExampleHostObject(42)", [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsObject());
+ ASSERT_TRUE(Object::Cast(*value)->InternalFieldCount());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === ExampleHostObject.prototype"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.value === 42"));
+ });
+ RoundTripTest(
+ "new ExampleHostObject(0xCAFECAFE)", [this](Local<Value> value) {
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.value === 0xCAFECAFE"));
+ });
+}
+
+TEST_F(ValueSerializerTestWithHostObject, RoundTripUint64) {
+ // The host can serialize data as uint64_t.
+ EXPECT_CALL(serializer_delegate_, WriteHostObject(isolate(), _))
+ .WillRepeatedly(Invoke([this](Isolate*, Local<Object> object) {
+ uint32_t value = 0, value2 = 0;
+ EXPECT_TRUE(object->GetInternalField(0)
+ ->Uint32Value(serialization_context())
+ .To(&value));
+ EXPECT_TRUE(object->GetInternalField(1)
+ ->Uint32Value(serialization_context())
+ .To(&value2));
+ WriteExampleHostObjectTag();
+ serializer_->WriteUint64((static_cast<uint64_t>(value) << 32) | value2);
+ return Just(true);
+ }));
+ EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
+ .WillRepeatedly(Invoke([this](Isolate*) {
+ EXPECT_TRUE(ReadExampleHostObjectTag());
+ uint64_t value_packed;
+ EXPECT_TRUE(deserializer_->ReadUint64(&value_packed));
+ Local<Value> argv[] = {
+ Integer::NewFromUnsigned(isolate(),
+ static_cast<uint32_t>(value_packed >> 32)),
+ Integer::NewFromUnsigned(isolate(),
+ static_cast<uint32_t>(value_packed))};
+ return NewHostObject(deserialization_context(), arraysize(argv), argv);
+ }));
+ RoundTripTest("new ExampleHostObject(42, 0)", [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsObject());
+ ASSERT_TRUE(Object::Cast(*value)->InternalFieldCount());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === ExampleHostObject.prototype"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.value === 42"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.value2 === 0"));
+ });
+ RoundTripTest(
+ "new ExampleHostObject(0xFFFFFFFF, 0x12345678)",
+ [this](Local<Value> value) {
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.value === 0xFFFFFFFF"));
+ EXPECT_TRUE(
+ EvaluateScriptForResultBool("result.value2 === 0x12345678"));
+ });
+}
+
+TEST_F(ValueSerializerTestWithHostObject, RoundTripDouble) {
+ // The host can serialize data as double.
+ EXPECT_CALL(serializer_delegate_, WriteHostObject(isolate(), _))
+ .WillRepeatedly(Invoke([this](Isolate*, Local<Object> object) {
+ double value = 0;
+ EXPECT_TRUE(object->GetInternalField(0)
+ ->NumberValue(serialization_context())
+ .To(&value));
+ WriteExampleHostObjectTag();
+ serializer_->WriteDouble(value);
+ return Just(true);
+ }));
+ EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
+ .WillRepeatedly(Invoke([this](Isolate*) {
+ EXPECT_TRUE(ReadExampleHostObjectTag());
+ double value = 0;
+ EXPECT_TRUE(deserializer_->ReadDouble(&value));
+ Local<Value> argv[] = {Number::New(isolate(), value)};
+ return NewHostObject(deserialization_context(), arraysize(argv), argv);
+ }));
+ RoundTripTest("new ExampleHostObject(-3.5)", [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsObject());
+ ASSERT_TRUE(Object::Cast(*value)->InternalFieldCount());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === ExampleHostObject.prototype"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.value === -3.5"));
+ });
+ RoundTripTest("new ExampleHostObject(NaN)", [this](Local<Value> value) {
+ EXPECT_TRUE(EvaluateScriptForResultBool("Number.isNaN(result.value)"));
+ });
+ RoundTripTest("new ExampleHostObject(Infinity)", [this](Local<Value> value) {
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.value === Infinity"));
+ });
+ RoundTripTest("new ExampleHostObject(-0)", [this](Local<Value> value) {
+ EXPECT_TRUE(EvaluateScriptForResultBool("1/result.value === -Infinity"));
+ });
+}
+
+TEST_F(ValueSerializerTestWithHostObject, RoundTripRawBytes) {
+ // The host can serialize arbitrary raw bytes.
+ const struct {
+ uint64_t u64;
+ uint32_t u32;
+ char str[12];
+ } sample_data = {0x1234567812345678, 0x87654321, "Hello world"};
+ EXPECT_CALL(serializer_delegate_, WriteHostObject(isolate(), _))
+ .WillRepeatedly(
+ Invoke([this, &sample_data](Isolate*, Local<Object> object) {
+ WriteExampleHostObjectTag();
+ serializer_->WriteRawBytes(&sample_data, sizeof(sample_data));
+ return Just(true);
+ }));
+ EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
+ .WillRepeatedly(Invoke([this, &sample_data](Isolate*) {
+ EXPECT_TRUE(ReadExampleHostObjectTag());
+ const void* copied_data = nullptr;
+ EXPECT_TRUE(
+ deserializer_->ReadRawBytes(sizeof(sample_data), &copied_data));
+ if (copied_data) {
+ EXPECT_EQ(0, memcmp(&sample_data, copied_data, sizeof(sample_data)));
+ }
+ return NewHostObject(deserialization_context(), 0, nullptr);
+ }));
+ RoundTripTest("new ExampleHostObject()", [this](Local<Value> value) {
+ ASSERT_TRUE(value->IsObject());
+ ASSERT_TRUE(Object::Cast(*value)->InternalFieldCount());
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "Object.getPrototypeOf(result) === ExampleHostObject.prototype"));
+ });
+}
+
+TEST_F(ValueSerializerTestWithHostObject, RoundTripSameObject) {
+ // If the same object exists in two places, the delegate should be invoked
+ // only once, and the objects should be the same (by reference equality) on
+ // the other side.
+ EXPECT_CALL(serializer_delegate_, WriteHostObject(isolate(), _))
+ .WillOnce(Invoke([this](Isolate*, Local<Object> object) {
+ WriteExampleHostObjectTag();
+ return Just(true);
+ }));
+ EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
+ .WillOnce(Invoke([this](Isolate*) {
+ EXPECT_TRUE(ReadExampleHostObjectTag());
+ return NewHostObject(deserialization_context(), 0, nullptr);
+ }));
+ RoundTripTest(
+ "({ a: new ExampleHostObject(), get b() { return this.a; }})",
+ [this](Local<Value> value) {
+ EXPECT_TRUE(EvaluateScriptForResultBool(
+ "result.a instanceof ExampleHostObject"));
+ EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
+ });
+}
+
} // namespace
} // namespace v8