| Index: test/unittests/value-serializer-unittest.cc
|
| diff --git a/test/unittests/value-serializer-unittest.cc b/test/unittests/value-serializer-unittest.cc
|
| index c48961fb6cddbb61ac4c2fa464af29ab925e0062..0648ff226b509a2955ce04eeac83a642a8f2f7ce 100644
|
| --- a/test/unittests/value-serializer-unittest.cc
|
| +++ b/test/unittests/value-serializer-unittest.cc
|
| @@ -38,21 +38,49 @@ class ValueSerializerTest : public TestWithIsolate {
|
| });
|
| }
|
|
|
| + // Variant for the common case where a script is used to build the original
|
| + // value.
|
| + template <typename OutputFunctor>
|
| + void RoundTripTest(const char* source, const OutputFunctor& output_functor) {
|
| + RoundTripTest([this, source]() { return EvaluateScriptForInput(source); },
|
| + output_functor);
|
| + }
|
| +
|
| + 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);
|
| + serializer.WriteHeader();
|
| + if (serializer.WriteObject(Utils::OpenHandle(*value)).FromMaybe(false)) {
|
| + return Just(serializer.ReleaseBuffer());
|
| + }
|
| + if (internal_isolate->has_pending_exception()) {
|
| + internal_isolate->OptionalRescheduleException(true);
|
| + }
|
| + return Nothing<std::vector<uint8_t>>();
|
| + }
|
| +
|
| template <typename InputFunctor, typename EncodedDataFunctor>
|
| void EncodeTest(const InputFunctor& input_functor,
|
| const EncodedDataFunctor& encoded_data_functor) {
|
| Context::Scope scope(serialization_context());
|
| TryCatch try_catch(isolate());
|
| - // TODO(jbroman): Use the public API once it exists.
|
| Local<Value> input_value = input_functor();
|
| - i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate());
|
| - i::HandleScope handle_scope(internal_isolate);
|
| - i::ValueSerializer serializer;
|
| - serializer.WriteHeader();
|
| - ASSERT_TRUE(serializer.WriteObject(Utils::OpenHandle(*input_value))
|
| - .FromMaybe(false));
|
| + std::vector<uint8_t> buffer;
|
| + ASSERT_TRUE(DoEncode(input_value).To(&buffer));
|
| ASSERT_FALSE(try_catch.HasCaught());
|
| - encoded_data_functor(serializer.ReleaseBuffer());
|
| + encoded_data_functor(buffer);
|
| + }
|
| +
|
| + template <typename MessageFunctor>
|
| + void InvalidEncodeTest(const char* source, const MessageFunctor& functor) {
|
| + Context::Scope scope(serialization_context());
|
| + TryCatch try_catch(isolate());
|
| + Local<Value> input_value = EvaluateScriptForInput(source);
|
| + ASSERT_TRUE(DoEncode(input_value).IsNothing());
|
| + functor(try_catch.Message());
|
| }
|
|
|
| template <typename OutputFunctor>
|
| @@ -388,5 +416,216 @@ TEST_F(ValueSerializerTest, EncodeTwoByteStringUsesPadding) {
|
| });
|
| }
|
|
|
| +TEST_F(ValueSerializerTest, RoundTripDictionaryObject) {
|
| + // Empty object.
|
| + RoundTripTest("({})", [this](Local<Value> value) {
|
| + ASSERT_TRUE(value->IsObject());
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getPrototypeOf(result) === Object.prototype"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getOwnPropertyNames(result).length === 0"));
|
| + });
|
| + // String key.
|
| + RoundTripTest("({ a: 42 })", [this](Local<Value> value) {
|
| + ASSERT_TRUE(value->IsObject());
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty('a')"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result.a === 42"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getOwnPropertyNames(result).length === 1"));
|
| + });
|
| + // Integer key (treated as a string, but may be encoded differently).
|
| + RoundTripTest("({ 42: 'a' })", [this](Local<Value> value) {
|
| + ASSERT_TRUE(value->IsObject());
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty('42')"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result[42] === 'a'"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getOwnPropertyNames(result).length === 1"));
|
| + });
|
| + // Key order must be preserved.
|
| + RoundTripTest("({ x: 1, y: 2, a: 3 })", [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getOwnPropertyNames(result).toString() === 'x,y,a'"));
|
| + });
|
| + // A harder case of enumeration order.
|
| + // Indexes first, in order (but not 2^32 - 1, which is not an index), then the
|
| + // remaining (string) keys, in the order they were defined.
|
| + RoundTripTest(
|
| + "({ a: 2, 0xFFFFFFFF: 1, 0xFFFFFFFE: 3, 1: 0 })",
|
| + [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getOwnPropertyNames(result).toString() === "
|
| + "'1,4294967294,a,4294967295'"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result.a === 2"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result[0xFFFFFFFF] === 1"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result[0xFFFFFFFE] === 3"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result[1] === 0"));
|
| + });
|
| + // This detects a fairly subtle case: the object itself must be in the map
|
| + // before its properties are deserialized, so that references to it can be
|
| + // resolved.
|
| + RoundTripTest(
|
| + "(() => { var y = {}; y.self = y; return y; })()",
|
| + [this](Local<Value> value) {
|
| + ASSERT_TRUE(value->IsObject());
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result === result.self"));
|
| + });
|
| +}
|
| +
|
| +TEST_F(ValueSerializerTest, DecodeDictionaryObject) {
|
| + // Empty object.
|
| + DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x6f, 0x7b, 0x00, 0x00},
|
| + [this](Local<Value> value) {
|
| + ASSERT_TRUE(value->IsObject());
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getPrototypeOf(result) === Object.prototype"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getOwnPropertyNames(result).length === 0"));
|
| + });
|
| + // String key.
|
| + DecodeTest(
|
| + {0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x53, 0x01, 0x61, 0x3f, 0x01,
|
| + 0x49, 0x54, 0x7b, 0x01},
|
| + [this](Local<Value> value) {
|
| + ASSERT_TRUE(value->IsObject());
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty('a')"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result.a === 42"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getOwnPropertyNames(result).length === 1"));
|
| + });
|
| + // Integer key (treated as a string, but may be encoded differently).
|
| + DecodeTest(
|
| + {0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x49, 0x54, 0x3f, 0x01, 0x53,
|
| + 0x01, 0x61, 0x7b, 0x01},
|
| + [this](Local<Value> value) {
|
| + ASSERT_TRUE(value->IsObject());
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty('42')"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result[42] === 'a'"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getOwnPropertyNames(result).length === 1"));
|
| + });
|
| + // Key order must be preserved.
|
| + DecodeTest(
|
| + {0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x53, 0x01, 0x78, 0x3f, 0x01,
|
| + 0x49, 0x02, 0x3f, 0x01, 0x53, 0x01, 0x79, 0x3f, 0x01, 0x49, 0x04, 0x3f,
|
| + 0x01, 0x53, 0x01, 0x61, 0x3f, 0x01, 0x49, 0x06, 0x7b, 0x03},
|
| + [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getOwnPropertyNames(result).toString() === 'x,y,a'"));
|
| + });
|
| + // A harder case of enumeration order.
|
| + DecodeTest(
|
| + {0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x49, 0x02, 0x3f, 0x01,
|
| + 0x49, 0x00, 0x3f, 0x01, 0x55, 0xfe, 0xff, 0xff, 0xff, 0x0f, 0x3f,
|
| + 0x01, 0x49, 0x06, 0x3f, 0x01, 0x53, 0x01, 0x61, 0x3f, 0x01, 0x49,
|
| + 0x04, 0x3f, 0x01, 0x53, 0x0a, 0x34, 0x32, 0x39, 0x34, 0x39, 0x36,
|
| + 0x37, 0x32, 0x39, 0x35, 0x3f, 0x01, 0x49, 0x02, 0x7b, 0x04},
|
| + [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getOwnPropertyNames(result).toString() === "
|
| + "'1,4294967294,a,4294967295'"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result.a === 2"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result[0xFFFFFFFF] === 1"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result[0xFFFFFFFE] === 3"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result[1] === 0"));
|
| + });
|
| + // This detects a fairly subtle case: the object itself must be in the map
|
| + // before its properties are deserialized, so that references to it can be
|
| + // resolved.
|
| + DecodeTest(
|
| + {0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x53, 0x04, 0x73,
|
| + 0x65, 0x6c, 0x66, 0x3f, 0x01, 0x5e, 0x00, 0x7b, 0x01, 0x00},
|
| + [this](Local<Value> value) {
|
| + ASSERT_TRUE(value->IsObject());
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result === result.self"));
|
| + });
|
| +}
|
| +
|
| +TEST_F(ValueSerializerTest, RoundTripOnlyOwnEnumerableStringKeys) {
|
| + // Only "own" properties should be serialized, not ones on the prototype.
|
| + RoundTripTest("(() => { var x = {}; x.__proto__ = {a: 4}; return x; })()",
|
| + [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("!('a' in result)"));
|
| + });
|
| + // Only enumerable properties should be serialized.
|
| + RoundTripTest(
|
| + "(() => {"
|
| + " var x = {};"
|
| + " Object.defineProperty(x, 'a', {value: 1, enumerable: false});"
|
| + " return x;"
|
| + "})()",
|
| + [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("!('a' in result)"));
|
| + });
|
| + // Symbol keys should not be serialized.
|
| + RoundTripTest("({ [Symbol()]: 4 })", [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getOwnPropertySymbols(result).length === 0"));
|
| + });
|
| +}
|
| +
|
| +TEST_F(ValueSerializerTest, RoundTripTrickyGetters) {
|
| + // Keys are enumerated before any setters are called, but if there is no own
|
| + // property when the value is to be read, then it should not be serialized.
|
| + RoundTripTest("({ get a() { delete this.b; return 1; }, b: 2 })",
|
| + [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("!('b' in result)"));
|
| + });
|
| + // Keys added after the property enumeration should not be serialized.
|
| + RoundTripTest("({ get a() { this.b = 3; }})", [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("!('b' in result)"));
|
| + });
|
| + // But if you remove a key and add it back, that's fine. But it will appear in
|
| + // the original place in enumeration order.
|
| + RoundTripTest(
|
| + "({ get a() { delete this.b; this.b = 4; }, b: 2, c: 3 })",
|
| + [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool(
|
| + "Object.getOwnPropertyNames(result).toString() === 'a,b,c'"));
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result.b === 4"));
|
| + });
|
| + // Similarly, it only matters if a property was enumerable when the
|
| + // enumeration happened.
|
| + RoundTripTest(
|
| + "({ get a() {"
|
| + " Object.defineProperty(this, 'b', {value: 2, enumerable: false});"
|
| + "}, b: 1})",
|
| + [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("result.b === 2"));
|
| + });
|
| + RoundTripTest(
|
| + "(() => {"
|
| + " var x = {"
|
| + " get a() {"
|
| + " Object.defineProperty(this, 'b', {value: 2, enumerable: true});"
|
| + " }"
|
| + " };"
|
| + " Object.defineProperty(x, 'b',"
|
| + " {value: 1, enumerable: false, configurable: true});"
|
| + " return x;"
|
| + "})()",
|
| + [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("!('b' in result)"));
|
| + });
|
| + // The property also should not be read if it can only be found on the
|
| + // prototype chain (but not as an own property) after enumeration.
|
| + RoundTripTest(
|
| + "(() => {"
|
| + " var x = { get a() { delete this.b; }, b: 1 };"
|
| + " x.__proto__ = { b: 0 };"
|
| + " return x;"
|
| + "})()",
|
| + [this](Local<Value> value) {
|
| + EXPECT_TRUE(EvaluateScriptForResultBool("!('b' in result)"));
|
| + });
|
| + // If an exception is thrown by script, encoding must fail and the exception
|
| + // must be thrown.
|
| + InvalidEncodeTest("({ get a() { throw new Error('sentinel'); } })",
|
| + [](Local<Message> message) {
|
| + ASSERT_FALSE(message.IsEmpty());
|
| + EXPECT_NE(std::string::npos,
|
| + Utf8Value(message->Get()).find("sentinel"));
|
| + });
|
| +}
|
| +
|
| } // namespace
|
| } // namespace v8
|
|
|