Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(695)

Unified Diff: test/unittests/value-serializer-unittest.cc

Issue 2327653002: Support delegating serialization of host objects. (Closed)
Patch Set: Isolate* argument to delegate Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« include/v8.h ('K') | « src/value-serializer.cc ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« include/v8.h ('K') | « src/value-serializer.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698