OLD | NEW |
---|---|
1 // Copyright 2016 the V8 project authors. All rights reserved. | 1 // Copyright 2016 the V8 project authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "src/value-serializer.h" | 5 #include "src/value-serializer.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <string> | 8 #include <string> |
9 | 9 |
10 #include "include/v8.h" | 10 #include "include/v8.h" |
(...skipping 20 matching lines...) Expand all Loading... | |
31 | 31 |
32 template <typename InputFunctor, typename OutputFunctor> | 32 template <typename InputFunctor, typename OutputFunctor> |
33 void RoundTripTest(const InputFunctor& input_functor, | 33 void RoundTripTest(const InputFunctor& input_functor, |
34 const OutputFunctor& output_functor) { | 34 const OutputFunctor& output_functor) { |
35 EncodeTest(input_functor, | 35 EncodeTest(input_functor, |
36 [this, &output_functor](const std::vector<uint8_t>& data) { | 36 [this, &output_functor](const std::vector<uint8_t>& data) { |
37 DecodeTest(data, output_functor); | 37 DecodeTest(data, output_functor); |
38 }); | 38 }); |
39 } | 39 } |
40 | 40 |
41 // Variant for the common case where a script is used to build the original | |
42 // value. | |
43 template <typename OutputFunctor> | |
44 void RoundTripTest(const char* source, const OutputFunctor& output_functor) { | |
45 RoundTripTest([this, source]() { return EvaluateScriptForInput(source); }, | |
46 output_functor); | |
47 } | |
48 | |
49 Maybe<std::vector<uint8_t>> DoEncode(Local<Value> value) { | |
50 // This approximates what the API implementation would do. | |
51 // TODO(jbroman): Use the public API once it exists. | |
52 i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate()); | |
53 i::HandleScope handle_scope(internal_isolate); | |
54 i::ValueSerializer serializer(internal_isolate); | |
55 serializer.WriteHeader(); | |
56 if (serializer.WriteObject(Utils::OpenHandle(*value)).FromMaybe(false)) | |
57 return Just(serializer.ReleaseBuffer()); | |
Jakob Kummerow
2016/08/17 13:00:15
nit: braces please
jbroman
2016/08/17 14:05:09
Done.
| |
58 if (internal_isolate->has_pending_exception()) | |
59 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.)
| |
60 return Nothing<std::vector<uint8_t>>(); | |
61 } | |
62 | |
41 template <typename InputFunctor, typename EncodedDataFunctor> | 63 template <typename InputFunctor, typename EncodedDataFunctor> |
42 void EncodeTest(const InputFunctor& input_functor, | 64 void EncodeTest(const InputFunctor& input_functor, |
43 const EncodedDataFunctor& encoded_data_functor) { | 65 const EncodedDataFunctor& encoded_data_functor) { |
44 Context::Scope scope(serialization_context()); | 66 Context::Scope scope(serialization_context()); |
45 TryCatch try_catch(isolate()); | 67 TryCatch try_catch(isolate()); |
46 // TODO(jbroman): Use the public API once it exists. | |
47 Local<Value> input_value = input_functor(); | 68 Local<Value> input_value = input_functor(); |
48 i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate()); | 69 std::vector<uint8_t> buffer; |
49 i::HandleScope handle_scope(internal_isolate); | 70 ASSERT_TRUE(DoEncode(input_value).To(&buffer)); |
50 i::ValueSerializer serializer; | |
51 serializer.WriteHeader(); | |
52 ASSERT_TRUE(serializer.WriteObject(Utils::OpenHandle(*input_value)) | |
53 .FromMaybe(false)); | |
54 ASSERT_FALSE(try_catch.HasCaught()); | 71 ASSERT_FALSE(try_catch.HasCaught()); |
55 encoded_data_functor(serializer.ReleaseBuffer()); | 72 encoded_data_functor(buffer); |
73 } | |
74 | |
75 template <typename MessageFunctor> | |
76 void InvalidEncodeTest(const char* source, const MessageFunctor& functor) { | |
77 Context::Scope scope(serialization_context()); | |
78 TryCatch try_catch(isolate()); | |
79 Local<Value> input_value = EvaluateScriptForInput(source); | |
80 ASSERT_TRUE(DoEncode(input_value).IsNothing()); | |
81 functor(try_catch.Message()); | |
56 } | 82 } |
57 | 83 |
58 template <typename OutputFunctor> | 84 template <typename OutputFunctor> |
59 void DecodeTest(const std::vector<uint8_t>& data, | 85 void DecodeTest(const std::vector<uint8_t>& data, |
60 const OutputFunctor& output_functor) { | 86 const OutputFunctor& output_functor) { |
61 Context::Scope scope(deserialization_context()); | 87 Context::Scope scope(deserialization_context()); |
62 TryCatch try_catch(isolate()); | 88 TryCatch try_catch(isolate()); |
63 // TODO(jbroman): Use the public API once it exists. | 89 // TODO(jbroman): Use the public API once it exists. |
64 i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate()); | 90 i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate()); |
65 i::HandleScope handle_scope(internal_isolate); | 91 i::HandleScope handle_scope(internal_isolate); |
(...skipping 315 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
381 [](const std::vector<uint8_t>& data) { | 407 [](const std::vector<uint8_t>& data) { |
382 // This is a sufficient but not necessary condition to be aligned. | 408 // This is a sufficient but not necessary condition to be aligned. |
383 // Note that the third byte (0x00) is padding. | 409 // Note that the third byte (0x00) is padding. |
384 const uint8_t expected_prefix[] = {0xff, 0x09, 0x00, 0x63, 0x94, 0x03}; | 410 const uint8_t expected_prefix[] = {0xff, 0x09, 0x00, 0x63, 0x94, 0x03}; |
385 ASSERT_GT(data.size(), sizeof(expected_prefix) / sizeof(uint8_t)); | 411 ASSERT_GT(data.size(), sizeof(expected_prefix) / sizeof(uint8_t)); |
386 EXPECT_TRUE(std::equal(std::begin(expected_prefix), | 412 EXPECT_TRUE(std::equal(std::begin(expected_prefix), |
387 std::end(expected_prefix), data.begin())); | 413 std::end(expected_prefix), data.begin())); |
388 }); | 414 }); |
389 } | 415 } |
390 | 416 |
417 TEST_F(ValueSerializerTest, RoundTripDictionaryObject) { | |
418 // Empty object. | |
419 RoundTripTest("({})", [this](Local<Value> value) { | |
420 ASSERT_TRUE(value->IsObject()); | |
421 EXPECT_TRUE(EvaluateScriptForResultBool( | |
422 "Object.getPrototypeOf(result) === Object.prototype")); | |
423 EXPECT_TRUE(EvaluateScriptForResultBool( | |
424 "Object.getOwnPropertyNames(result).length === 0")); | |
425 }); | |
426 // String key. | |
427 RoundTripTest("({ a: 42 })", [this](Local<Value> value) { | |
428 ASSERT_TRUE(value->IsObject()); | |
429 EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty('a')")); | |
430 EXPECT_TRUE(EvaluateScriptForResultBool("result.a === 42")); | |
431 EXPECT_TRUE(EvaluateScriptForResultBool( | |
432 "Object.getOwnPropertyNames(result).length === 1")); | |
433 }); | |
434 // Integer key (treated as a string, but may be encoded differently). | |
435 RoundTripTest("({ 42: 'a' })", [this](Local<Value> value) { | |
436 ASSERT_TRUE(value->IsObject()); | |
437 EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty('42')")); | |
438 EXPECT_TRUE(EvaluateScriptForResultBool("result[42] === 'a'")); | |
439 EXPECT_TRUE(EvaluateScriptForResultBool( | |
440 "Object.getOwnPropertyNames(result).length === 1")); | |
441 }); | |
442 // Key order must be preserved. | |
443 RoundTripTest("({ x: 1, y: 2, a: 3 })", [this](Local<Value> value) { | |
444 EXPECT_TRUE(EvaluateScriptForResultBool( | |
445 "Object.getOwnPropertyNames(result).toString() === 'x,y,a'")); | |
446 }); | |
447 // A harder case of enumeration order. | |
448 // 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.
| |
449 // remaining (string) keys, in the order they were defined. | |
450 RoundTripTest( | |
451 "({ a: 2, 0xFFFFFFFF: 1, 0xFFFFFFFE: 3, 1: 0 })", | |
452 [this](Local<Value> value) { | |
453 EXPECT_TRUE(EvaluateScriptForResultBool( | |
454 "Object.getOwnPropertyNames(result).toString() === " | |
455 "'1,4294967294,a,4294967295'")); | |
456 EXPECT_TRUE(EvaluateScriptForResultBool("result.a === 2")); | |
457 EXPECT_TRUE(EvaluateScriptForResultBool("result[0xFFFFFFFF] === 1")); | |
458 EXPECT_TRUE(EvaluateScriptForResultBool("result[0xFFFFFFFE] === 3")); | |
459 EXPECT_TRUE(EvaluateScriptForResultBool("result[1] === 0")); | |
460 }); | |
461 // This detects a fairly subtle case: the object itself must be in the map | |
462 // before its properties are deserialized, so that references to it can be | |
463 // resolved. | |
464 RoundTripTest( | |
465 "(() => { var y = {}; y.self = y; return y; })()", | |
466 [this](Local<Value> value) { | |
467 ASSERT_TRUE(value->IsObject()); | |
468 EXPECT_TRUE(EvaluateScriptForResultBool("result === result.self")); | |
469 }); | |
470 } | |
471 | |
472 TEST_F(ValueSerializerTest, DecodeDictionaryObject) { | |
473 // Empty object. | |
474 DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x6f, 0x7b, 0x00, 0x00}, | |
475 [this](Local<Value> value) { | |
476 ASSERT_TRUE(value->IsObject()); | |
477 EXPECT_TRUE(EvaluateScriptForResultBool( | |
478 "Object.getPrototypeOf(result) === Object.prototype")); | |
479 EXPECT_TRUE(EvaluateScriptForResultBool( | |
480 "Object.getOwnPropertyNames(result).length === 0")); | |
481 }); | |
482 // String key. | |
483 DecodeTest( | |
484 {0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x53, 0x01, 0x61, 0x3f, 0x01, | |
485 0x49, 0x54, 0x7b, 0x01}, | |
486 [this](Local<Value> value) { | |
487 ASSERT_TRUE(value->IsObject()); | |
488 EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty('a')")); | |
489 EXPECT_TRUE(EvaluateScriptForResultBool("result.a === 42")); | |
490 EXPECT_TRUE(EvaluateScriptForResultBool( | |
491 "Object.getOwnPropertyNames(result).length === 1")); | |
492 }); | |
493 // Integer key (treated as a string, but may be encoded differently). | |
494 DecodeTest( | |
495 {0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x49, 0x54, 0x3f, 0x01, 0x53, | |
496 0x01, 0x61, 0x7b, 0x01}, | |
497 [this](Local<Value> value) { | |
498 ASSERT_TRUE(value->IsObject()); | |
499 EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty('42')")); | |
500 EXPECT_TRUE(EvaluateScriptForResultBool("result[42] === 'a'")); | |
501 EXPECT_TRUE(EvaluateScriptForResultBool( | |
502 "Object.getOwnPropertyNames(result).length === 1")); | |
503 }); | |
504 // Key order must be preserved. | |
505 DecodeTest( | |
506 {0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x53, 0x01, 0x78, 0x3f, 0x01, | |
507 0x49, 0x02, 0x3f, 0x01, 0x53, 0x01, 0x79, 0x3f, 0x01, 0x49, 0x04, 0x3f, | |
508 0x01, 0x53, 0x01, 0x61, 0x3f, 0x01, 0x49, 0x06, 0x7b, 0x03}, | |
509 [this](Local<Value> value) { | |
510 EXPECT_TRUE(EvaluateScriptForResultBool( | |
511 "Object.getOwnPropertyNames(result).toString() === 'x,y,a'")); | |
512 }); | |
513 // A harder case of enumeration order. | |
514 DecodeTest( | |
515 {0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x49, 0x02, 0x3f, 0x01, | |
516 0x49, 0x00, 0x3f, 0x01, 0x55, 0xfe, 0xff, 0xff, 0xff, 0x0f, 0x3f, | |
517 0x01, 0x49, 0x06, 0x3f, 0x01, 0x53, 0x01, 0x61, 0x3f, 0x01, 0x49, | |
518 0x04, 0x3f, 0x01, 0x53, 0x0a, 0x34, 0x32, 0x39, 0x34, 0x39, 0x36, | |
519 0x37, 0x32, 0x39, 0x35, 0x3f, 0x01, 0x49, 0x02, 0x7b, 0x04}, | |
520 [this](Local<Value> value) { | |
521 EXPECT_TRUE(EvaluateScriptForResultBool( | |
522 "Object.getOwnPropertyNames(result).toString() === " | |
523 "'1,4294967294,a,4294967295'")); | |
524 EXPECT_TRUE(EvaluateScriptForResultBool("result.a === 2")); | |
525 EXPECT_TRUE(EvaluateScriptForResultBool("result[0xFFFFFFFF] === 1")); | |
526 EXPECT_TRUE(EvaluateScriptForResultBool("result[0xFFFFFFFE] === 3")); | |
527 EXPECT_TRUE(EvaluateScriptForResultBool("result[1] === 0")); | |
528 }); | |
529 // This detects a fairly subtle case: the object itself must be in the map | |
530 // before its properties are deserialized, so that references to it can be | |
531 // resolved. | |
532 DecodeTest( | |
533 {0xff, 0x09, 0x3f, 0x00, 0x6f, 0x3f, 0x01, 0x53, 0x04, 0x73, | |
534 0x65, 0x6c, 0x66, 0x3f, 0x01, 0x5e, 0x00, 0x7b, 0x01, 0x00}, | |
535 [this](Local<Value> value) { | |
536 ASSERT_TRUE(value->IsObject()); | |
537 EXPECT_TRUE(EvaluateScriptForResultBool("result === result.self")); | |
538 }); | |
539 } | |
540 | |
541 TEST_F(ValueSerializerTest, RoundTripOnlyOwnEnumerableStringKeys) { | |
542 // Only "own" properties should be serialized, not ones on the prototype. | |
543 RoundTripTest("(() => { var x = {}; x.__proto__ = {a: 4}; return x; })()", | |
544 [this](Local<Value> value) { | |
545 EXPECT_TRUE(EvaluateScriptForResultBool("!('a' in result)")); | |
546 }); | |
547 // Only enumerable properties should be serialized. | |
548 RoundTripTest( | |
549 "(() => {" | |
550 " var x = {};" | |
551 " Object.defineProperty(x, 'a', {value: 1, enumerable: false});" | |
552 " return x;" | |
553 "})()", | |
554 [this](Local<Value> value) { | |
555 EXPECT_TRUE(EvaluateScriptForResultBool("!('a' in result)")); | |
556 }); | |
557 // Symbol keys should not be serialized. | |
558 RoundTripTest("({ [Symbol()]: 4 })", [this](Local<Value> value) { | |
559 EXPECT_TRUE(EvaluateScriptForResultBool( | |
560 "Object.getOwnPropertySymbols(result).length === 0")); | |
561 }); | |
562 } | |
563 | |
564 TEST_F(ValueSerializerTest, RoundTripTrickyGetters) { | |
565 // Keys are enumerated before any setters are called, but if there is no own | |
566 // property when the value is to be read, then it should not be serialized. | |
567 RoundTripTest("({ get a() { delete this.b; return 1; }, b: 2 })", | |
568 [this](Local<Value> value) { | |
569 EXPECT_TRUE(EvaluateScriptForResultBool("!('b' in result)")); | |
570 }); | |
571 // Keys added after the property enumeration should not be serialized. | |
572 RoundTripTest("({ get a() { this.b = 3; }})", [this](Local<Value> value) { | |
573 EXPECT_TRUE(EvaluateScriptForResultBool("!('b' in result)")); | |
574 }); | |
575 // But if you remove a key and add it back, that's fine. But it will appear in | |
576 // the original place in enumeration order. | |
577 RoundTripTest( | |
578 "({ get a() { delete this.b; this.b = 4; }, b: 2, c: 3 })", | |
579 [this](Local<Value> value) { | |
580 EXPECT_TRUE(EvaluateScriptForResultBool( | |
581 "Object.getOwnPropertyNames(result).toString() === 'a,b,c'")); | |
582 EXPECT_TRUE(EvaluateScriptForResultBool("result.b === 4")); | |
583 }); | |
584 // Similarly, it only matters if a property was enumerable when the | |
585 // enumeration happened. | |
586 RoundTripTest( | |
587 "({ get a() {" | |
588 " Object.defineProperty(this, 'b', {value: 2, enumerable: false});" | |
589 "}, b: 1})", | |
590 [this](Local<Value> value) { | |
591 EXPECT_TRUE(EvaluateScriptForResultBool("result.b === 2")); | |
592 }); | |
593 RoundTripTest( | |
594 "(() => {" | |
595 " var x = {" | |
596 " get a() {" | |
597 " Object.defineProperty(this, 'b', {value: 2, enumerable: true});" | |
598 " }" | |
599 " };" | |
600 " Object.defineProperty(x, 'b'," | |
601 " {value: 1, enumerable: false, configurable: true});" | |
602 " return x;" | |
603 "})()", | |
604 [this](Local<Value> value) { | |
605 EXPECT_TRUE(EvaluateScriptForResultBool("!('b' in result)")); | |
606 }); | |
607 // The property also should not be read if it can only be found on the | |
608 // prototype chain (but not as an own property) after enumeration. | |
609 RoundTripTest( | |
610 "(() => {" | |
611 " var x = { get a() { delete this.b; }, b: 1 };" | |
612 " x.__proto__ = { b: 0 };" | |
613 " return x;" | |
614 "})()", | |
615 [this](Local<Value> value) { | |
616 EXPECT_TRUE(EvaluateScriptForResultBool("!('b' in result)")); | |
617 }); | |
618 // If an exception is thrown by script, encoding must fail and the exception | |
619 // must be thrown. | |
620 InvalidEncodeTest("({ get a() { throw new Error('sentinel'); } })", | |
621 [](Local<Message> message) { | |
622 ASSERT_FALSE(message.IsEmpty()); | |
623 EXPECT_NE(std::string::npos, | |
624 Utf8Value(message->Get()).find("sentinel")); | |
625 }); | |
626 } | |
627 | |
391 } // namespace | 628 } // namespace |
392 } // namespace v8 | 629 } // namespace v8 |
OLD | NEW |