diff options
Diffstat (limited to 'deps/v8/test/unittests/value-serializer-unittest.cc')
-rw-r--r-- | deps/v8/test/unittests/value-serializer-unittest.cc | 1098 |
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 |