Chromium Code Reviews| 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 |