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