Chromium Code Reviews| 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..9877d382287da110d612ba67388cd319cc59fdd3 100644 |
| --- a/test/unittests/value-serializer-unittest.cc |
| +++ b/test/unittests/value-serializer-unittest.cc |
| @@ -38,21 +38,47 @@ 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()); |
|
Jakob Kummerow
2016/08/17 13:00:15
nit: braces please
jbroman
2016/08/17 14:05:09
Done.
|
| + if (internal_isolate->has_pending_exception()) |
| + internal_isolate->OptionalRescheduleException(true); |
|
Jakob Kummerow
2016/08/17 13:00:15
nit: braces please
jbroman
2016/08/17 14:05:09
Done. (I wish clang-format fixed this.)
|
| + 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 +414,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^31 - 1, which is not an index), then the |
|
Jakob Kummerow
2016/08/17 13:00:15
nit: you mean "2^32 - 1".
jbroman
2016/08/17 14:05:09
Indeed. Done.
|
| + // 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 |