058a7ee01e
The "version 0" format did not deal with references, and used a stack model to deserialize objects (conceptually, a postorder tree traversal). This requires an explicit stack, so special logic is added to decode this format. All subsequent versions also put an object marker at the beginning, which is equivalent to how the current version serializes. BUG=chromium:148757 Review-Url: https://codereview.chromium.org/2248893003 Cr-Commit-Position: refs/heads/master@{#38686}
712 lines
30 KiB
C++
712 lines
30 KiB
C++
// Copyright 2016 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "src/value-serializer.h"
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
|
|
#include "include/v8.h"
|
|
#include "src/api.h"
|
|
#include "src/base/build_config.h"
|
|
#include "test/unittests/test-utils.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace v8 {
|
|
namespace {
|
|
|
|
class ValueSerializerTest : public TestWithIsolate {
|
|
protected:
|
|
ValueSerializerTest()
|
|
: serialization_context_(Context::New(isolate())),
|
|
deserialization_context_(Context::New(isolate())) {}
|
|
|
|
const Local<Context>& serialization_context() {
|
|
return serialization_context_;
|
|
}
|
|
const Local<Context>& deserialization_context() {
|
|
return deserialization_context_;
|
|
}
|
|
|
|
template <typename InputFunctor, typename OutputFunctor>
|
|
void RoundTripTest(const InputFunctor& input_functor,
|
|
const OutputFunctor& output_functor) {
|
|
EncodeTest(input_functor,
|
|
[this, &output_functor](const std::vector<uint8_t>& data) {
|
|
DecodeTest(data, output_functor);
|
|
});
|
|
}
|
|
|
|
// 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());
|
|
Local<Value> input_value = input_functor();
|
|
std::vector<uint8_t> buffer;
|
|
ASSERT_TRUE(DoEncode(input_value).To(&buffer));
|
|
ASSERT_FALSE(try_catch.HasCaught());
|
|
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>
|
|
void DecodeTest(const std::vector<uint8_t>& data,
|
|
const OutputFunctor& output_functor) {
|
|
Context::Scope scope(deserialization_context());
|
|
TryCatch try_catch(isolate());
|
|
// 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::ValueDeserializer deserializer(
|
|
internal_isolate,
|
|
i::Vector<const uint8_t>(&data[0], static_cast<int>(data.size())));
|
|
ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false));
|
|
Local<Value> result;
|
|
ASSERT_TRUE(ToLocal<Value>(deserializer.ReadObject(), &result));
|
|
ASSERT_FALSE(result.IsEmpty());
|
|
ASSERT_FALSE(try_catch.HasCaught());
|
|
ASSERT_TRUE(deserialization_context()
|
|
->Global()
|
|
->CreateDataProperty(deserialization_context_,
|
|
StringFromUtf8("result"), result)
|
|
.FromMaybe(false));
|
|
output_functor(result);
|
|
ASSERT_FALSE(try_catch.HasCaught());
|
|
}
|
|
|
|
template <typename OutputFunctor>
|
|
void DecodeTestForVersion0(const std::vector<uint8_t>& data,
|
|
const OutputFunctor& output_functor) {
|
|
Context::Scope scope(deserialization_context());
|
|
TryCatch try_catch(isolate());
|
|
// 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::ValueDeserializer deserializer(
|
|
internal_isolate,
|
|
i::Vector<const uint8_t>(&data[0], static_cast<int>(data.size())));
|
|
// TODO(jbroman): Enable legacy support.
|
|
ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false));
|
|
// TODO(jbroman): Check version 0.
|
|
Local<Value> result;
|
|
ASSERT_TRUE(ToLocal<Value>(
|
|
deserializer.ReadObjectUsingEntireBufferForLegacyFormat(), &result));
|
|
ASSERT_FALSE(result.IsEmpty());
|
|
ASSERT_FALSE(try_catch.HasCaught());
|
|
ASSERT_TRUE(deserialization_context()
|
|
->Global()
|
|
->CreateDataProperty(deserialization_context_,
|
|
StringFromUtf8("result"), result)
|
|
.FromMaybe(false));
|
|
output_functor(result);
|
|
ASSERT_FALSE(try_catch.HasCaught());
|
|
}
|
|
|
|
void InvalidDecodeTest(const std::vector<uint8_t>& data) {
|
|
Context::Scope scope(deserialization_context());
|
|
TryCatch try_catch(isolate());
|
|
i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate());
|
|
i::HandleScope handle_scope(internal_isolate);
|
|
i::ValueDeserializer deserializer(
|
|
internal_isolate,
|
|
i::Vector<const uint8_t>(&data[0], static_cast<int>(data.size())));
|
|
Maybe<bool> header_result = deserializer.ReadHeader();
|
|
if (header_result.IsNothing()) return;
|
|
ASSERT_TRUE(header_result.ToChecked());
|
|
ASSERT_TRUE(deserializer.ReadObject().is_null());
|
|
}
|
|
|
|
Local<Value> EvaluateScriptForInput(const char* utf8_source) {
|
|
Local<String> source = StringFromUtf8(utf8_source);
|
|
Local<Script> script =
|
|
Script::Compile(serialization_context_, source).ToLocalChecked();
|
|
return script->Run(serialization_context_).ToLocalChecked();
|
|
}
|
|
|
|
bool EvaluateScriptForResultBool(const char* utf8_source) {
|
|
Local<String> source = StringFromUtf8(utf8_source);
|
|
Local<Script> script =
|
|
Script::Compile(deserialization_context_, source).ToLocalChecked();
|
|
Local<Value> value = script->Run(deserialization_context_).ToLocalChecked();
|
|
return value->BooleanValue(deserialization_context_).FromJust();
|
|
}
|
|
|
|
Local<String> StringFromUtf8(const char* source) {
|
|
return String::NewFromUtf8(isolate(), source, NewStringType::kNormal)
|
|
.ToLocalChecked();
|
|
}
|
|
|
|
static std::string Utf8Value(Local<Value> value) {
|
|
String::Utf8Value utf8(value);
|
|
return std::string(*utf8, utf8.length());
|
|
}
|
|
|
|
private:
|
|
Local<Context> serialization_context_;
|
|
Local<Context> deserialization_context_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ValueSerializerTest);
|
|
};
|
|
|
|
TEST_F(ValueSerializerTest, DecodeInvalid) {
|
|
// Version tag but no content.
|
|
InvalidDecodeTest({0xff});
|
|
// Version too large.
|
|
InvalidDecodeTest({0xff, 0x7f, 0x5f});
|
|
// Nonsense tag.
|
|
InvalidDecodeTest({0xff, 0x09, 0xdd});
|
|
}
|
|
|
|
TEST_F(ValueSerializerTest, RoundTripOddball) {
|
|
RoundTripTest([this]() { return Undefined(isolate()); },
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsUndefined()); });
|
|
RoundTripTest([this]() { return True(isolate()); },
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsTrue()); });
|
|
RoundTripTest([this]() { return False(isolate()); },
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsFalse()); });
|
|
RoundTripTest([this]() { return Null(isolate()); },
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
|
|
}
|
|
|
|
TEST_F(ValueSerializerTest, DecodeOddball) {
|
|
// What this code is expected to generate.
|
|
DecodeTest({0xff, 0x09, 0x5f},
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsUndefined()); });
|
|
DecodeTest({0xff, 0x09, 0x54},
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsTrue()); });
|
|
DecodeTest({0xff, 0x09, 0x46},
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsFalse()); });
|
|
DecodeTest({0xff, 0x09, 0x30},
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
|
|
|
|
// What v9 of the Blink code generates.
|
|
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x5f, 0x00},
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsUndefined()); });
|
|
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x54, 0x00},
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsTrue()); });
|
|
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x46, 0x00},
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsFalse()); });
|
|
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x30, 0x00},
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
|
|
|
|
// v0 (with no explicit version).
|
|
DecodeTest({0x5f, 0x00},
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsUndefined()); });
|
|
DecodeTest({0x54, 0x00},
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsTrue()); });
|
|
DecodeTest({0x46, 0x00},
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsFalse()); });
|
|
DecodeTest({0x30, 0x00},
|
|
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
|
|
}
|
|
|
|
TEST_F(ValueSerializerTest, RoundTripNumber) {
|
|
RoundTripTest([this]() { return Integer::New(isolate(), 42); },
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsInt32());
|
|
EXPECT_EQ(42, Int32::Cast(*value)->Value());
|
|
});
|
|
RoundTripTest([this]() { return Integer::New(isolate(), -31337); },
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsInt32());
|
|
EXPECT_EQ(-31337, Int32::Cast(*value)->Value());
|
|
});
|
|
RoundTripTest(
|
|
[this]() {
|
|
return Integer::New(isolate(), std::numeric_limits<int32_t>::min());
|
|
},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsInt32());
|
|
EXPECT_EQ(std::numeric_limits<int32_t>::min(),
|
|
Int32::Cast(*value)->Value());
|
|
});
|
|
RoundTripTest([this]() { return Number::New(isolate(), -0.25); },
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsNumber());
|
|
EXPECT_EQ(-0.25, Number::Cast(*value)->Value());
|
|
});
|
|
RoundTripTest(
|
|
[this]() {
|
|
return Number::New(isolate(), std::numeric_limits<double>::quiet_NaN());
|
|
},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsNumber());
|
|
EXPECT_TRUE(std::isnan(Number::Cast(*value)->Value()));
|
|
});
|
|
}
|
|
|
|
TEST_F(ValueSerializerTest, DecodeNumber) {
|
|
// 42 zig-zag encoded (signed)
|
|
DecodeTest({0xff, 0x09, 0x49, 0x54},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsInt32());
|
|
EXPECT_EQ(42, Int32::Cast(*value)->Value());
|
|
});
|
|
// 42 varint encoded (unsigned)
|
|
DecodeTest({0xff, 0x09, 0x55, 0x2a},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsInt32());
|
|
EXPECT_EQ(42, Int32::Cast(*value)->Value());
|
|
});
|
|
// 160 zig-zag encoded (signed)
|
|
DecodeTest({0xff, 0x09, 0x49, 0xc0, 0x02},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsInt32());
|
|
ASSERT_EQ(160, Int32::Cast(*value)->Value());
|
|
});
|
|
// 160 varint encoded (unsigned)
|
|
DecodeTest({0xff, 0x09, 0x55, 0xa0, 0x01},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsInt32());
|
|
ASSERT_EQ(160, Int32::Cast(*value)->Value());
|
|
});
|
|
#if defined(V8_TARGET_LITTLE_ENDIAN)
|
|
// IEEE 754 doubles, little-endian byte order
|
|
DecodeTest({0xff, 0x09, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xbf},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsNumber());
|
|
EXPECT_EQ(-0.25, Number::Cast(*value)->Value());
|
|
});
|
|
// quiet NaN
|
|
DecodeTest({0xff, 0x09, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x7f},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsNumber());
|
|
EXPECT_TRUE(std::isnan(Number::Cast(*value)->Value()));
|
|
});
|
|
// signaling NaN
|
|
DecodeTest({0xff, 0x09, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x7f},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsNumber());
|
|
EXPECT_TRUE(std::isnan(Number::Cast(*value)->Value()));
|
|
});
|
|
#endif
|
|
// TODO(jbroman): Equivalent test for big-endian machines.
|
|
}
|
|
|
|
// String constants (in UTF-8) used for string encoding tests.
|
|
static const char kHelloString[] = "Hello";
|
|
static const char kQuebecString[] = "\x51\x75\xC3\xA9\x62\x65\x63";
|
|
static const char kEmojiString[] = "\xF0\x9F\x91\x8A";
|
|
|
|
TEST_F(ValueSerializerTest, RoundTripString) {
|
|
RoundTripTest([this]() { return String::Empty(isolate()); },
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsString());
|
|
EXPECT_EQ(0, String::Cast(*value)->Length());
|
|
});
|
|
// Inside ASCII.
|
|
RoundTripTest([this]() { return StringFromUtf8(kHelloString); },
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsString());
|
|
EXPECT_EQ(5, String::Cast(*value)->Length());
|
|
EXPECT_EQ(kHelloString, Utf8Value(value));
|
|
});
|
|
// Inside Latin-1 (i.e. one-byte string), but not ASCII.
|
|
RoundTripTest([this]() { return StringFromUtf8(kQuebecString); },
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsString());
|
|
EXPECT_EQ(6, String::Cast(*value)->Length());
|
|
EXPECT_EQ(kQuebecString, Utf8Value(value));
|
|
});
|
|
// An emoji (decodes to two 16-bit chars).
|
|
RoundTripTest([this]() { return StringFromUtf8(kEmojiString); },
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsString());
|
|
EXPECT_EQ(2, String::Cast(*value)->Length());
|
|
EXPECT_EQ(kEmojiString, Utf8Value(value));
|
|
});
|
|
}
|
|
|
|
TEST_F(ValueSerializerTest, DecodeString) {
|
|
// Decoding the strings above from UTF-8.
|
|
DecodeTest({0xff, 0x09, 0x53, 0x00},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsString());
|
|
EXPECT_EQ(0, String::Cast(*value)->Length());
|
|
});
|
|
DecodeTest({0xff, 0x09, 0x53, 0x05, 'H', 'e', 'l', 'l', 'o'},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsString());
|
|
EXPECT_EQ(5, String::Cast(*value)->Length());
|
|
EXPECT_EQ(kHelloString, Utf8Value(value));
|
|
});
|
|
DecodeTest({0xff, 0x09, 0x53, 0x07, 'Q', 'u', 0xc3, 0xa9, 'b', 'e', 'c'},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsString());
|
|
EXPECT_EQ(6, String::Cast(*value)->Length());
|
|
EXPECT_EQ(kQuebecString, Utf8Value(value));
|
|
});
|
|
DecodeTest({0xff, 0x09, 0x53, 0x04, 0xf0, 0x9f, 0x91, 0x8a},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsString());
|
|
EXPECT_EQ(2, String::Cast(*value)->Length());
|
|
EXPECT_EQ(kEmojiString, Utf8Value(value));
|
|
});
|
|
|
|
// And from two-byte strings (endianness dependent).
|
|
#if defined(V8_TARGET_LITTLE_ENDIAN)
|
|
DecodeTest({0xff, 0x09, 0x63, 0x00},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsString());
|
|
EXPECT_EQ(0, String::Cast(*value)->Length());
|
|
});
|
|
DecodeTest({0xff, 0x09, 0x63, 0x0a, 'H', '\0', 'e', '\0', 'l', '\0', 'l',
|
|
'\0', 'o', '\0'},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsString());
|
|
EXPECT_EQ(5, String::Cast(*value)->Length());
|
|
EXPECT_EQ(kHelloString, Utf8Value(value));
|
|
});
|
|
DecodeTest({0xff, 0x09, 0x63, 0x0c, 'Q', '\0', 'u', '\0', 0xe9, '\0', 'b',
|
|
'\0', 'e', '\0', 'c', '\0'},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsString());
|
|
EXPECT_EQ(6, String::Cast(*value)->Length());
|
|
EXPECT_EQ(kQuebecString, Utf8Value(value));
|
|
});
|
|
DecodeTest({0xff, 0x09, 0x63, 0x04, 0x3d, 0xd8, 0x4a, 0xdc},
|
|
[](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsString());
|
|
EXPECT_EQ(2, String::Cast(*value)->Length());
|
|
EXPECT_EQ(kEmojiString, Utf8Value(value));
|
|
});
|
|
#endif
|
|
// TODO(jbroman): The same for big-endian systems.
|
|
}
|
|
|
|
TEST_F(ValueSerializerTest, DecodeInvalidString) {
|
|
// UTF-8 string with too few bytes available.
|
|
InvalidDecodeTest({0xff, 0x09, 0x53, 0x10, 'v', '8'});
|
|
#if defined(V8_TARGET_LITTLE_ENDIAN)
|
|
// Two-byte string with too few bytes available.
|
|
InvalidDecodeTest({0xff, 0x09, 0x63, 0x10, 'v', '\0', '8', '\0'});
|
|
// Two-byte string with an odd byte length.
|
|
InvalidDecodeTest({0xff, 0x09, 0x63, 0x03, 'v', '\0', '8'});
|
|
#endif
|
|
// TODO(jbroman): The same for big-endian systems.
|
|
}
|
|
|
|
TEST_F(ValueSerializerTest, EncodeTwoByteStringUsesPadding) {
|
|
// As long as the output has a version that Blink expects to be able to read,
|
|
// we must respect its alignment requirements. It requires that two-byte
|
|
// characters be aligned.
|
|
EncodeTest(
|
|
[this]() {
|
|
// We need a string whose length will take two bytes to encode, so that
|
|
// a padding byte is needed to keep the characters aligned. The string
|
|
// must also have a two-byte character, so that it gets the two-byte
|
|
// encoding.
|
|
std::string string(200, ' ');
|
|
string += kEmojiString;
|
|
return StringFromUtf8(string.c_str());
|
|
},
|
|
[](const std::vector<uint8_t>& data) {
|
|
// This is a sufficient but not necessary condition to be aligned.
|
|
// Note that the third byte (0x00) is padding.
|
|
const uint8_t expected_prefix[] = {0xff, 0x09, 0x00, 0x63, 0x94, 0x03};
|
|
ASSERT_GT(data.size(), sizeof(expected_prefix) / sizeof(uint8_t));
|
|
EXPECT_TRUE(std::equal(std::begin(expected_prefix),
|
|
std::end(expected_prefix), data.begin()));
|
|
});
|
|
}
|
|
|
|
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"));
|
|
});
|
|
}
|
|
|
|
TEST_F(ValueSerializerTest, DecodeDictionaryObjectVersion0) {
|
|
// Empty object.
|
|
DecodeTestForVersion0(
|
|
{0x7b, 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.
|
|
DecodeTestForVersion0(
|
|
{0x53, 0x01, 0x61, 0x49, 0x54, 0x7b, 0x01, 0x00},
|
|
[this](Local<Value> value) {
|
|
ASSERT_TRUE(value->IsObject());
|
|
EXPECT_TRUE(EvaluateScriptForResultBool(
|
|
"Object.getPrototypeOf(result) === Object.prototype"));
|
|
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).
|
|
DecodeTestForVersion0(
|
|
{0x49, 0x54, 0x53, 0x01, 0x61, 0x7b, 0x01, 0x00},
|
|
[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.
|
|
DecodeTestForVersion0(
|
|
{0x53, 0x01, 0x78, 0x49, 0x02, 0x53, 0x01, 0x79, 0x49, 0x04, 0x53, 0x01,
|
|
0x61, 0x49, 0x06, 0x7b, 0x03, 0x00},
|
|
[this](Local<Value> value) {
|
|
EXPECT_TRUE(EvaluateScriptForResultBool(
|
|
"Object.getOwnPropertyNames(result).toString() === 'x,y,a'"));
|
|
});
|
|
// A property and an element.
|
|
DecodeTestForVersion0(
|
|
{0x49, 0x54, 0x53, 0x01, 0x61, 0x53, 0x01, 0x61, 0x49, 0x54, 0x7b, 0x02},
|
|
[this](Local<Value> value) {
|
|
EXPECT_TRUE(EvaluateScriptForResultBool(
|
|
"Object.getOwnPropertyNames(result).toString() === '42,a'"));
|
|
EXPECT_TRUE(EvaluateScriptForResultBool("result[42] === 'a'"));
|
|
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === 42"));
|
|
});
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace v8
|