| Index: test/unittests/value-serializer-unittest.cc
|
| diff --git a/test/unittests/value-serializer-unittest.cc b/test/unittests/value-serializer-unittest.cc
|
| index 9f1aa2fa9999237dcd578e88cf8deb978c141875..24be764f362ea5896171997608361ac3e26e8fb8 100644
|
| --- a/test/unittests/value-serializer-unittest.cc
|
| +++ b/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_;
|
| @@ -30,7 +62,11 @@ class ValueSerializerTest : public TestWithIsolate {
|
| }
|
|
|
| // 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>
|
| @@ -52,7 +88,7 @@ class ValueSerializerTest : public TestWithIsolate {
|
|
|
| Maybe<std::vector<uint8_t>> DoEncode(Local<Value> value) {
|
| Local<Context> context = serialization_context();
|
| - ValueSerializer serializer(isolate());
|
| + ValueSerializer serializer(isolate(), GetSerializerDelegate());
|
| BeforeEncode(&serializer);
|
| serializer.WriteHeader();
|
| if (!serializer.WriteValue(context, value).FromMaybe(false)) {
|
| @@ -93,7 +129,8 @@ class ValueSerializerTest : public TestWithIsolate {
|
| Context::Scope scope(context);
|
| TryCatch try_catch(isolate());
|
| ValueDeserializer deserializer(isolate(), &data[0],
|
| - static_cast<int>(data.size()));
|
| + static_cast<int>(data.size()),
|
| + GetDeserializerDelegate());
|
| deserializer.SetSupportsLegacyWireFormat(true);
|
| BeforeDecode(&deserializer);
|
| ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
|
| @@ -116,7 +153,8 @@ class ValueSerializerTest : public TestWithIsolate {
|
| Context::Scope scope(context);
|
| TryCatch try_catch(isolate());
|
| ValueDeserializer deserializer(isolate(), &data[0],
|
| - static_cast<int>(data.size()));
|
| + static_cast<int>(data.size()),
|
| + GetDeserializerDelegate());
|
| deserializer.SetSupportsLegacyWireFormat(true);
|
| BeforeDecode(&deserializer);
|
| ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
|
| @@ -138,7 +176,8 @@ class ValueSerializerTest : public TestWithIsolate {
|
| Context::Scope scope(context);
|
| TryCatch try_catch(isolate());
|
| ValueDeserializer deserializer(isolate(), &data[0],
|
| - static_cast<int>(data.size()));
|
| + static_cast<int>(data.size()),
|
| + GetDeserializerDelegate());
|
| deserializer.SetSupportsLegacyWireFormat(true);
|
| BeforeDecode(&deserializer);
|
| Maybe<bool> header_result = deserializer.ReadHeader(context);
|
| @@ -176,9 +215,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);
|
| };
|
| @@ -2024,5 +2072,213 @@ TEST_F(ValueSerializerTestWithSharedArrayBufferTransfer,
|
| 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, 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
|
|
|