blob: c143b5809027de80bb697e0de5fc9613ccfa3717 [file] [log] [blame]
// 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 "src/objects-inl.h"
#include "src/wasm/wasm-objects.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace {
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
class ValueSerializerTest : public TestWithIsolate {
protected:
ValueSerializerTest()
: serialization_context_(Context::New(isolate())),
deserialization_context_(Context::New(isolate())) {
// Create a host object type that can be tested through
// serialization/deserialization delegates below.
Local<FunctionTemplate> function_template = v8::FunctionTemplate::New(
isolate(), [](const FunctionCallbackInfo<Value>& args) {
args.Holder()->SetInternalField(0, args[0]);
args.Holder()->SetInternalField(1, args[1]);
});
function_template->InstanceTemplate()->SetInternalFieldCount(2);
function_template->InstanceTemplate()->SetAccessor(
StringFromUtf8("value"),
[](Local<String> property, const PropertyCallbackInfo<Value>& args) {
args.GetReturnValue().Set(args.Holder()->GetInternalField(0));
});
function_template->InstanceTemplate()->SetAccessor(
StringFromUtf8("value2"),
[](Local<String> property, const PropertyCallbackInfo<Value>& args) {
args.GetReturnValue().Set(args.Holder()->GetInternalField(1));
});
for (Local<Context> context :
{serialization_context_, deserialization_context_}) {
context->Global()
->CreateDataProperty(
context, StringFromUtf8("ExampleHostObject"),
function_template->GetFunction(context).ToLocalChecked())
.ToChecked();
}
host_object_constructor_template_ = function_template;
isolate_ = reinterpret_cast<i::Isolate*>(isolate());
}
~ValueSerializerTest() {
// In some cases unhandled scheduled exceptions from current test produce
// that Context::New(isolate()) from next test's constructor returns NULL.
// In order to prevent that, we added destructor which will clear scheduled
// exceptions just for the current test from test case.
if (isolate_->has_scheduled_exception()) {
isolate_->clear_scheduled_exception();
}
}
const Local<Context>& serialization_context() {
return serialization_context_;
}
const Local<Context>& deserialization_context() {
return deserialization_context_;
}
bool ExpectInlineWasm() const { return expect_inline_wasm_; }
void SetExpectInlineWasm(bool value) { expect_inline_wasm_ = value; }
// Overridden in more specific fixtures.
virtual ValueSerializer::Delegate* GetSerializerDelegate() { return nullptr; }
virtual void BeforeEncode(ValueSerializer*) {}
virtual void AfterEncode() {}
virtual ValueDeserializer::Delegate* GetDeserializerDelegate() {
return nullptr;
}
virtual void BeforeDecode(ValueDeserializer*) {}
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);
}
// Variant which uses JSON.parse/stringify to check the result.
void RoundTripJSON(const char* source) {
RoundTripTest(
[this, source]() {
return JSON::Parse(serialization_context_, StringFromUtf8(source))
.ToLocalChecked();
},
[this, source](Local<Value> value) {
ASSERT_TRUE(value->IsObject());
EXPECT_EQ(source, Utf8Value(JSON::Stringify(deserialization_context_,
value.As<Object>())
.ToLocalChecked()));
});
}
Maybe<std::vector<uint8_t>> DoEncode(Local<Value> value) {
Local<Context> context = serialization_context();
ValueSerializer serializer(isolate(), GetSerializerDelegate());
BeforeEncode(&serializer);
serializer.WriteHeader();
if (!serializer.WriteValue(context, value).FromMaybe(false)) {
return Nothing<std::vector<uint8_t>>();
}
AfterEncode();
std::pair<uint8_t*, size_t> buffer = serializer.Release();
std::vector<uint8_t> result(buffer.first, buffer.first + buffer.second);
free(buffer.first);
return Just(std::move(result));
}
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 InputFunctor, typename MessageFunctor>
void InvalidEncodeTest(const InputFunctor& input_functor,
const MessageFunctor& functor) {
Context::Scope scope(serialization_context());
TryCatch try_catch(isolate());
Local<Value> input_value = input_functor();
ASSERT_TRUE(DoEncode(input_value).IsNothing());
functor(try_catch.Message());
}
template <typename MessageFunctor>
void InvalidEncodeTest(const char* source, const MessageFunctor& functor) {
InvalidEncodeTest(
[this, source]() { return EvaluateScriptForInput(source); }, functor);
}
void InvalidEncodeTest(const char* source) {
InvalidEncodeTest(source, [](Local<Message>) {});
}
template <typename OutputFunctor>
void DecodeTest(const std::vector<uint8_t>& data,
const OutputFunctor& output_functor) {
Local<Context> context = deserialization_context();
Context::Scope scope(context);
TryCatch try_catch(isolate());
ValueDeserializer deserializer(isolate(), &data[0],
static_cast<int>(data.size()),
GetDeserializerDelegate());
deserializer.SetSupportsLegacyWireFormat(true);
deserializer.SetExpectInlineWasm(ExpectInlineWasm());
BeforeDecode(&deserializer);
ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
Local<Value> result;
ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result));
ASSERT_FALSE(result.IsEmpty());
ASSERT_FALSE(try_catch.HasCaught());
ASSERT_TRUE(
context->Global()
->CreateDataProperty(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) {
Local<Context> context = deserialization_context();
Context::Scope scope(context);
TryCatch try_catch(isolate());
ValueDeserializer deserializer(isolate(), &data[0],
static_cast<int>(data.size()),
GetDeserializerDelegate());
deserializer.SetSupportsLegacyWireFormat(true);
deserializer.SetExpectInlineWasm(ExpectInlineWasm());
BeforeDecode(&deserializer);
ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
ASSERT_EQ(0u, deserializer.GetWireFormatVersion());
Local<Value> result;
ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result));
ASSERT_FALSE(result.IsEmpty());
ASSERT_FALSE(try_catch.HasCaught());
ASSERT_TRUE(
context->Global()
->CreateDataProperty(context, StringFromUtf8("result"), result)
.FromMaybe(false));
output_functor(result);
ASSERT_FALSE(try_catch.HasCaught());
}
void InvalidDecodeTest(const std::vector<uint8_t>& data) {
Local<Context> context = deserialization_context();
Context::Scope scope(context);
TryCatch try_catch(isolate());
ValueDeserializer deserializer(isolate(), &data[0],
static_cast<int>(data.size()),
GetDeserializerDelegate());
deserializer.SetSupportsLegacyWireFormat(true);
deserializer.SetExpectInlineWasm(ExpectInlineWasm());
BeforeDecode(&deserializer);
Maybe<bool> header_result = deserializer.ReadHeader(context);
if (header_result.IsNothing()) {
EXPECT_TRUE(try_catch.HasCaught());
return;
}
ASSERT_TRUE(header_result.ToChecked());
ASSERT_TRUE(deserializer.ReadValue(context).IsEmpty());
EXPECT_TRUE(try_catch.HasCaught());
}
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();
}
std::string Utf8Value(Local<Value> value) {
String::Utf8Value utf8(isolate(), value);
return std::string(*utf8, utf8.length());
}
Local<Object> NewHostObject(Local<Context> context, int argc,
Local<Value> argv[]) {
return host_object_constructor_template_->GetFunction(context)
.ToLocalChecked()
->NewInstance(context, argc, argv)
.ToLocalChecked();
}
Local<Object> NewDummyUint8Array() {
static uint8_t data[] = {4, 5, 6};
Local<ArrayBuffer> ab =
ArrayBuffer::New(isolate(), static_cast<void*>(data), sizeof(data));
return Uint8Array::New(ab, 0, sizeof(data));
}
private:
Local<Context> serialization_context_;
Local<Context> deserialization_context_;
Local<FunctionTemplate> host_object_constructor_template_;
i::Isolate* isolate_;
bool expect_inline_wasm_ = false;
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); },
[this](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); },
[this](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); },
[this](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'},
[this](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'},
[this](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},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(2, String::Cast(*value)->Length());
EXPECT_EQ(kEmojiString, Utf8Value(value));
});
// And from Latin-1 (for the ones that fit).
DecodeTest({0xFF, 0x0A, 0x22, 0x00}, [](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(0, String::Cast(*value)->Length());
});
DecodeTest({0xFF, 0x0A, 0x22, 0x05, 'H', 'e', 'l', 'l', 'o'},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(5, String::Cast(*value)->Length());
EXPECT_EQ(kHelloString, Utf8Value(value));
});
DecodeTest({0xFF, 0x0A, 0x22, 0x06, 'Q', 'u', 0xE9, 'b', 'e', 'c'},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsString());
EXPECT_EQ(6, String::Cast(*value)->Length());
EXPECT_EQ(kQuebecString, 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'},
[this](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'},
[this](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},
[this](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'});
// One-byte string with too few bytes available.
InvalidDecodeTest({0xFF, 0x0A, 0x22, 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. This test assumes
// that the wire format version is one byte long, but is flexible to
// what that value may be.
const uint8_t expected_prefix[] = {0x00, 0x63, 0x94, 0x03};
ASSERT_GT(data.size(), sizeof(expected_prefix) + 2);
EXPECT_EQ(0xFF, data[0]);
EXPECT_GE(data[1], 0x09);
EXPECT_LE(data[1], 0x7F);
EXPECT_TRUE(std::equal(std::begin(expected_prefix),
std::end(expected_prefix), data.begin() + 2));
});
}
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, InvalidDecodeObjectWithInvalidKeyType) {
// Objects which would need conversion to string shouldn't be present as
// object keys. The serializer would have obtained them from the own property
// keys list, which should only contain names and indices.
InvalidDecodeTest(
{0xFF, 0x09, 0x6F, 0x61, 0x00, 0x40, 0x00, 0x00, 0x7B, 0x01});
}
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'); } })",
[this](Local<Message> message) {
ASSERT_FALSE(message.IsEmpty());
EXPECT_NE(std::string::npos,
Utf8Value(message->Get()).find("sentinel"));
});
}
TEST_F(ValueSerializerTest, RoundTripDictionaryObjectForTransitions) {
// A case which should run on the fast path, and should reach all of the
// different cases:
// 1. no known transition (first time creating this kind of object)
// 2. expected transitions match to end
// 3. transition partially matches, but falls back due to new property 'w'
// 4. transition to 'z' is now a full transition (needs to be looked up)
// 5. same for 'w'
// 6. new property after complex transition succeeded
// 7. new property after complex transition failed (due to new property)
RoundTripJSON(
"[{\"x\":1,\"y\":2,\"z\":3}"
",{\"x\":4,\"y\":5,\"z\":6}"
",{\"x\":5,\"y\":6,\"w\":7}"
",{\"x\":6,\"y\":7,\"z\":8}"
",{\"x\":0,\"y\":0,\"w\":0}"
",{\"x\":3,\"y\":1,\"w\":4,\"z\":1}"
",{\"x\":5,\"y\":9,\"k\":2,\"z\":6}]");
// A simpler case that uses two-byte strings.
RoundTripJSON(
"[{\"\xF0\x9F\x91\x8A\":1,\"\xF0\x9F\x91\x8B\":2}"
",{\"\xF0\x9F\x91\x8A\":3,\"\xF0\x9F\x91\x8C\":4}"
",{\"\xF0\x9F\x91\x8A\":5,\"\xF0\x9F\x91\x9B\":6}]");
}
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"));
});
}
TEST_F(ValueSerializerTest, RoundTripArray) {
// A simple array of integers.
RoundTripTest("[1, 2, 3, 4, 5]", [this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
EXPECT_EQ(5u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Array.prototype"));
EXPECT_TRUE(
EvaluateScriptForResultBool("result.toString() === '1,2,3,4,5'"));
});
// A long (sparse) array.
RoundTripTest(
"(() => { var x = new Array(1000); x[500] = 42; return x; })()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
EXPECT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[500] === 42"));
});
// Duplicate reference.
RoundTripTest(
"(() => { var y = {}; return [y, y]; })()", [this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(2u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === result[1]"));
});
// Duplicate reference in a sparse array.
RoundTripTest(
"(() => { var x = new Array(1000); x[1] = x[500] = {}; return x; })()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(
EvaluateScriptForResultBool("typeof result[1] === 'object'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result[1] === result[500]"));
});
// Self reference.
RoundTripTest(
"(() => { var y = []; y[0] = y; return y; })()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === result"));
});
// Self reference in a sparse array.
RoundTripTest(
"(() => { var y = new Array(1000); y[519] = y; return y; })()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[519] === result"));
});
// Array with additional properties.
RoundTripTest(
"(() => { var y = [1, 2]; y.foo = 'bar'; return y; })()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(2u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result.toString() === '1,2'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.foo === 'bar'"));
});
// Sparse array with additional properties.
RoundTripTest(
"(() => { var y = new Array(1000); y.foo = 'bar'; return y; })()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === ','.repeat(999)"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.foo === 'bar'"));
});
// The distinction between holes and undefined elements must be maintained.
RoundTripTest("[,undefined]", [this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(2u, Array::Cast(*value)->Length());
EXPECT_TRUE(
EvaluateScriptForResultBool("typeof result[0] === 'undefined'"));
EXPECT_TRUE(
EvaluateScriptForResultBool("typeof result[1] === 'undefined'"));
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(0)"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty(1)"));
});
}
TEST_F(ValueSerializerTest, DecodeArray) {
// A simple array of integers.
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x41, 0x05, 0x3F, 0x01, 0x49, 0x02,
0x3F, 0x01, 0x49, 0x04, 0x3F, 0x01, 0x49, 0x06, 0x3F, 0x01,
0x49, 0x08, 0x3F, 0x01, 0x49, 0x0A, 0x24, 0x00, 0x05, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
EXPECT_EQ(5u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Array.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === '1,2,3,4,5'"));
});
// A long (sparse) array.
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x61, 0xE8, 0x07, 0x3F, 0x01, 0x49,
0xE8, 0x07, 0x3F, 0x01, 0x49, 0x54, 0x40, 0x01, 0xE8, 0x07},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
EXPECT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[500] === 42"));
});
// Duplicate reference.
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x41, 0x02, 0x3F, 0x01, 0x6F, 0x7B, 0x00, 0x3F,
0x02, 0x5E, 0x01, 0x24, 0x00, 0x02},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(2u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === result[1]"));
});
// Duplicate reference in a sparse array.
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x61, 0xE8, 0x07, 0x3F, 0x01, 0x49,
0x02, 0x3F, 0x01, 0x6F, 0x7B, 0x00, 0x3F, 0x02, 0x49, 0xE8,
0x07, 0x3F, 0x02, 0x5E, 0x01, 0x40, 0x02, 0xE8, 0x07, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(
EvaluateScriptForResultBool("typeof result[1] === 'object'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result[1] === result[500]"));
});
// Self reference.
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x41, 0x01, 0x3F, 0x01, 0x5E, 0x00, 0x24,
0x00, 0x01, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === result"));
});
// Self reference in a sparse array.
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x61, 0xE8, 0x07, 0x3F, 0x01, 0x49,
0x8E, 0x08, 0x3F, 0x01, 0x5E, 0x00, 0x40, 0x01, 0xE8, 0x07},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[519] === result"));
});
// Array with additional properties.
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x41, 0x02, 0x3F, 0x01, 0x49, 0x02, 0x3F,
0x01, 0x49, 0x04, 0x3F, 0x01, 0x53, 0x03, 0x66, 0x6F, 0x6F, 0x3F,
0x01, 0x53, 0x03, 0x62, 0x61, 0x72, 0x24, 0x01, 0x02, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(2u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result.toString() === '1,2'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.foo === 'bar'"));
});
// Sparse array with additional properties.
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x61, 0xE8, 0x07, 0x3F, 0x01,
0x53, 0x03, 0x66, 0x6F, 0x6F, 0x3F, 0x01, 0x53, 0x03,
0x62, 0x61, 0x72, 0x40, 0x01, 0xE8, 0x07, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === ','.repeat(999)"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.foo === 'bar'"));
});
// The distinction between holes and undefined elements must be maintained.
// Note that since the previous output from Chrome fails this test, an
// encoding using the sparse format was constructed instead.
DecodeTest(
{0xFF, 0x09, 0x61, 0x02, 0x49, 0x02, 0x5F, 0x40, 0x01, 0x02},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(2u, Array::Cast(*value)->Length());
EXPECT_TRUE(
EvaluateScriptForResultBool("typeof result[0] === 'undefined'"));
EXPECT_TRUE(
EvaluateScriptForResultBool("typeof result[1] === 'undefined'"));
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(0)"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty(1)"));
});
}
TEST_F(ValueSerializerTest, DecodeInvalidOverLargeArray) {
// So large it couldn't exist in the V8 heap, and its size couldn't fit in a
// SMI on 32-bit systems (2^30).
InvalidDecodeTest({0xFF, 0x09, 0x41, 0x80, 0x80, 0x80, 0x80, 0x04});
// Not so large, but there isn't enough data left in the buffer.
InvalidDecodeTest({0xFF, 0x09, 0x41, 0x01});
}
TEST_F(ValueSerializerTest, RoundTripArrayWithNonEnumerableElement) {
// Even though this array looks like [1,5,3], the 5 should be missing from the
// perspective of structured clone, which only clones properties that were
// enumerable.
RoundTripTest(
"(() => {"
" var x = [1,2,3];"
" Object.defineProperty(x, '1', {enumerable:false, value:5});"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(3u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty('1')"));
});
}
TEST_F(ValueSerializerTest, RoundTripArrayWithTrickyGetters) {
// If an element is deleted before it is serialized, then it's deleted.
RoundTripTest(
"(() => {"
" var x = [{ get a() { delete x[1]; }}, 42];"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(2u, Array::Cast(*value)->Length());
EXPECT_TRUE(
EvaluateScriptForResultBool("typeof result[1] === 'undefined'"));
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(1)"));
});
// Same for sparse arrays.
RoundTripTest(
"(() => {"
" var x = [{ get a() { delete x[1]; }}, 42];"
" x.length = 1000;"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(
EvaluateScriptForResultBool("typeof result[1] === 'undefined'"));
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(1)"));
});
// If the length is changed, then the resulting array still has the original
// length, but elements that were not yet serialized are gone.
RoundTripTest(
"(() => {"
" var x = [1, { get a() { x.length = 0; }}, 3, 4];"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(4u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === 1"));
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(2)"));
});
// The same is true if the length is shortened, but there are still items
// remaining.
RoundTripTest(
"(() => {"
" var x = [1, { get a() { x.length = 3; }}, 3, 4];"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(4u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[2] === 3"));
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(3)"));
});
// Same for sparse arrays.
RoundTripTest(
"(() => {"
" var x = [1, { get a() { x.length = 0; }}, 3, 4];"
" x.length = 1000;"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === 1"));
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(2)"));
});
RoundTripTest(
"(() => {"
" var x = [1, { get a() { x.length = 3; }}, 3, 4];"
" x.length = 1000;"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[2] === 3"));
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(3)"));
});
// If a getter makes a property non-enumerable, it should still be enumerated
// as enumeration happens once before getters are invoked.
RoundTripTest(
"(() => {"
" var x = [{ get a() {"
" Object.defineProperty(x, '1', { value: 3, enumerable: false });"
" }}, 2];"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(2u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[1] === 3"));
});
// Same for sparse arrays.
RoundTripTest(
"(() => {"
" var x = [{ get a() {"
" Object.defineProperty(x, '1', { value: 3, enumerable: false });"
" }}, 2];"
" x.length = 1000;"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[1] === 3"));
});
// Getters on the array itself must also run.
RoundTripTest(
"(() => {"
" var x = [1, 2, 3];"
" Object.defineProperty(x, '1', { enumerable: true, get: () => 4 });"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(3u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[1] === 4"));
});
// Same for sparse arrays.
RoundTripTest(
"(() => {"
" var x = [1, 2, 3];"
" Object.defineProperty(x, '1', { enumerable: true, get: () => 4 });"
" x.length = 1000;"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result[1] === 4"));
});
// Even with a getter that deletes things, we don't read from the prototype.
RoundTripTest(
"(() => {"
" var x = [{ get a() { delete x[1]; } }, 2];"
" x.__proto__ = Object.create(Array.prototype, { 1: { value: 6 } });"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(2u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("!(1 in result)"));
});
// Same for sparse arrays.
RoundTripTest(
"(() => {"
" var x = [{ get a() { delete x[1]; } }, 2];"
" x.__proto__ = Object.create(Array.prototype, { 1: { value: 6 } });"
" x.length = 1000;"
" return x;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(1000u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("!(1 in result)"));
});
}
TEST_F(ValueSerializerTest, DecodeSparseArrayVersion0) {
// Empty (sparse) array.
DecodeTestForVersion0({0x40, 0x00, 0x00, 0x00},
[](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
ASSERT_EQ(0u, Array::Cast(*value)->Length());
});
// Sparse array with a mixture of elements and properties.
DecodeTestForVersion0(
{0x55, 0x00, 0x53, 0x01, 'a', 0x55, 0x02, 0x55, 0x05, 0x53,
0x03, 'f', 'o', 'o', 0x53, 0x03, 'b', 'a', 'r', 0x53,
0x03, 'b', 'a', 'z', 0x49, 0x0B, 0x40, 0x04, 0x03, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
EXPECT_EQ(3u, Array::Cast(*value)->Length());
EXPECT_TRUE(
EvaluateScriptForResultBool("result.toString() === 'a,,5'"));
EXPECT_TRUE(EvaluateScriptForResultBool("!(1 in result)"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.foo === 'bar'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.baz === -6"));
});
// Sparse array in a sparse array (sanity check of nesting).
DecodeTestForVersion0(
{0x55, 0x01, 0x55, 0x01, 0x54, 0x40, 0x01, 0x02, 0x40, 0x01, 0x02, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
EXPECT_EQ(2u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("!(0 in result)"));
EXPECT_TRUE(EvaluateScriptForResultBool("result[1] instanceof Array"));
EXPECT_TRUE(EvaluateScriptForResultBool("!(0 in result[1])"));
EXPECT_TRUE(EvaluateScriptForResultBool("result[1][1] === true"));
});
}
TEST_F(ValueSerializerTest, RoundTripDenseArrayContainingUndefined) {
// In previous serialization versions, this would be interpreted as an absent
// property.
RoundTripTest("[undefined]", [this](Local<Value> value) {
ASSERT_TRUE(value->IsArray());
EXPECT_EQ(1u, Array::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty(0)"));
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === undefined"));
});
}
TEST_F(ValueSerializerTest, DecodeDenseArrayContainingUndefined) {
// In previous versions, "undefined" in a dense array signified absence of the
// element (for compatibility). In new versions, it has a separate encoding.
DecodeTest({0xFF, 0x09, 0x41, 0x01, 0x5F, 0x24, 0x00, 0x01},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("!(0 in result)"));
});
DecodeTest(
{0xFF, 0x0B, 0x41, 0x01, 0x5F, 0x24, 0x00, 0x01},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("0 in result"));
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === undefined"));
});
DecodeTest({0xFF, 0x0B, 0x41, 0x01, 0x2D, 0x24, 0x00, 0x01},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("!(0 in result)"));
});
}
TEST_F(ValueSerializerTest, RoundTripDate) {
RoundTripTest("new Date(1e6)", [this](Local<Value> value) {
ASSERT_TRUE(value->IsDate());
EXPECT_EQ(1e6, Date::Cast(*value)->ValueOf());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Date.prototype"));
});
RoundTripTest("new Date(Date.UTC(1867, 6, 1))", [this](Local<Value> value) {
ASSERT_TRUE(value->IsDate());
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toISOString() === '1867-07-01T00:00:00.000Z'"));
});
RoundTripTest("new Date(NaN)", [](Local<Value> value) {
ASSERT_TRUE(value->IsDate());
EXPECT_TRUE(std::isnan(Date::Cast(*value)->ValueOf()));
});
RoundTripTest(
"({ a: new Date(), get b() { return this.a; } })",
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof Date"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
}
TEST_F(ValueSerializerTest, DecodeDate) {
#if defined(V8_TARGET_LITTLE_ENDIAN)
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x80, 0x84,
0x2E, 0x41, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsDate());
EXPECT_EQ(1e6, Date::Cast(*value)->ValueOf());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Date.prototype"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x44, 0x00, 0x00, 0x20, 0x45, 0x27, 0x89,
0x87, 0xC2, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsDate());
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toISOString() === '1867-07-01T00:00:00.000Z'"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xF8, 0x7F, 0x00},
[](Local<Value> value) {
ASSERT_TRUE(value->IsDate());
EXPECT_TRUE(std::isnan(Date::Cast(*value)->ValueOf()));
});
#else
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x44, 0x41, 0x2E, 0x84, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsDate());
EXPECT_EQ(1e6, Date::Cast(*value)->ValueOf());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Date.prototype"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x44, 0xC2, 0x87, 0x89, 0x27, 0x45, 0x20,
0x00, 0x00, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsDate());
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toISOString() === '1867-07-01T00:00:00.000Z'"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x44, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00},
[](Local<Value> value) {
ASSERT_TRUE(value->IsDate());
EXPECT_TRUE(std::isnan(Date::Cast(*value)->ValueOf()));
});
#endif
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x6F, 0x3F, 0x01, 0x53, 0x01, 0x61, 0x3F,
0x01, 0x44, 0x00, 0x20, 0x39, 0x50, 0x37, 0x6A, 0x75, 0x42, 0x3F,
0x02, 0x53, 0x01, 0x62, 0x3F, 0x02, 0x5E, 0x01, 0x7B, 0x02},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof Date"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
}
TEST_F(ValueSerializerTest, RoundTripValueObjects) {
RoundTripTest("new Boolean(true)", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Boolean.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === true"));
});
RoundTripTest("new Boolean(false)", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Boolean.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === false"));
});
RoundTripTest(
"({ a: new Boolean(true), get b() { return this.a; }})",
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof Boolean"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
RoundTripTest("new Number(-42)", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Number.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === -42"));
});
RoundTripTest("new Number(NaN)", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Number.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("Number.isNaN(result.valueOf())"));
});
RoundTripTest(
"({ a: new Number(6), get b() { return this.a; }})",
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof Number"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
RoundTripTest("new String('Qu\\xe9bec')", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === String.prototype"));
EXPECT_TRUE(
EvaluateScriptForResultBool("result.valueOf() === 'Qu\\xe9bec'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.length === 6"));
});
RoundTripTest("new String('\\ud83d\\udc4a')", [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === String.prototype"));
EXPECT_TRUE(
EvaluateScriptForResultBool("result.valueOf() === '\\ud83d\\udc4a'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.length === 2"));
});
RoundTripTest(
"({ a: new String(), get b() { return this.a; }})",
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof String"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
}
TEST_F(ValueSerializerTest, RejectsOtherValueObjects) {
// This is a roundabout way of getting an instance of Symbol.
InvalidEncodeTest("Object.valueOf.apply(Symbol())");
}
TEST_F(ValueSerializerTest, DecodeValueObjects) {
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x79, 0x00}, [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Boolean.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === true"));
});
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x78, 0x00}, [this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Boolean.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === false"));
});
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x6F, 0x3F, 0x01, 0x53, 0x01, 0x61, 0x3F, 0x01,
0x79, 0x3F, 0x02, 0x53, 0x01, 0x62, 0x3F, 0x02, 0x5E, 0x01, 0x7B, 0x02},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof Boolean"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
#if defined(V8_TARGET_LITTLE_ENDIAN)
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45,
0xC0, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Number.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === -42"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xF8, 0x7F, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Number.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"Number.isNaN(result.valueOf())"));
});
#else
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x6E, 0xC0, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Number.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.valueOf() === -42"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x6E, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Number.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"Number.isNaN(result.valueOf())"));
});
#endif
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x6F, 0x3F, 0x01, 0x53, 0x01, 0x61, 0x3F,
0x01, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40, 0x3F,
0x02, 0x53, 0x01, 0x62, 0x3F, 0x02, 0x5E, 0x01, 0x7B, 0x02},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof Number"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x73, 0x07, 0x51, 0x75, 0xC3, 0xA9, 0x62,
0x65, 0x63, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === String.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.valueOf() === 'Qu\\xe9bec'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.length === 6"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x73, 0x04, 0xF0, 0x9F, 0x91, 0x8A},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === String.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.valueOf() === '\\ud83d\\udc4a'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.length === 2"));
});
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x6F, 0x3F, 0x01, 0x53, 0x01,
0x61, 0x3F, 0x01, 0x73, 0x00, 0x3F, 0x02, 0x53, 0x01,
0x62, 0x3F, 0x02, 0x5E, 0x01, 0x7B, 0x02, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof String"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
// String object containing a Latin-1 string.
DecodeTest({0xFF, 0x0C, 0x73, 0x22, 0x06, 'Q', 'u', 0xE9, 'b', 'e', 'c'},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === String.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.valueOf() === 'Qu\\xe9bec'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.length === 6"));
});
}
TEST_F(ValueSerializerTest, RoundTripRegExp) {
RoundTripTest("/foo/g", [this](Local<Value> value) {
ASSERT_TRUE(value->IsRegExp());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === RegExp.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.toString() === '/foo/g'"));
});
RoundTripTest("new RegExp('Qu\\xe9bec', 'i')", [this](Local<Value> value) {
ASSERT_TRUE(value->IsRegExp());
EXPECT_TRUE(
EvaluateScriptForResultBool("result.toString() === '/Qu\\xe9bec/i'"));
});
RoundTripTest("new RegExp('\\ud83d\\udc4a', 'ug')",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsRegExp());
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === '/\\ud83d\\udc4a/gu'"));
});
RoundTripTest(
"({ a: /foo/gi, get b() { return this.a; }})",
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof RegExp"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
}
TEST_F(ValueSerializerTest, DecodeRegExp) {
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x52, 0x03, 0x66, 0x6F, 0x6F, 0x01},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsRegExp());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === RegExp.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === '/foo/g'"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x52, 0x07, 0x51, 0x75, 0xC3, 0xA9, 0x62,
0x65, 0x63, 0x02},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsRegExp());
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === '/Qu\\xe9bec/i'"));
});
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x52, 0x04, 0xF0, 0x9F, 0x91, 0x8A, 0x11, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsRegExp());
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === '/\\ud83d\\udc4a/gu'"));
});
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x6F, 0x3F, 0x01, 0x53, 0x01, 0x61,
0x3F, 0x01, 0x52, 0x03, 0x66, 0x6F, 0x6F, 0x03, 0x3F, 0x02,
0x53, 0x01, 0x62, 0x3F, 0x02, 0x5E, 0x01, 0x7B, 0x02, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool("result.a instanceof RegExp"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
// RegExp containing a Latin-1 string.
DecodeTest(
{0xFF, 0x0C, 0x52, 0x22, 0x06, 'Q', 'u', 0xE9, 'b', 'e', 'c', 0x02},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsRegExp());
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === '/Qu\\xe9bec/i'"));
});
}
// Tests that invalid flags are not accepted by the deserializer.
TEST_F(ValueSerializerTest, DecodeRegExpDotAll) {
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x52, 0x03, 0x66, 0x6F, 0x6F, 0x1F},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsRegExp());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === RegExp.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === '/foo/gimuy'"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x52, 0x03, 0x66, 0x6F, 0x6F, 0x3F},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsRegExp());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === RegExp.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === '/foo/gimsuy'"));
});
InvalidDecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x52, 0x03, 0x66, 0x6F, 0x6F, 0x7F});
}
TEST_F(ValueSerializerTest, RoundTripMap) {
RoundTripTest(
"(() => { var m = new Map(); m.set(42, 'foo'); return m; })()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsMap());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Map.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 1"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.get(42) === 'foo'"));
});
RoundTripTest("(() => { var m = new Map(); m.set(m, m); return m; })()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsMap());
EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 1"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.get(result) === result"));
});
// Iteration order must be preserved.
RoundTripTest(
"(() => {"
" var m = new Map();"
" m.set(1, 0); m.set('a', 0); m.set(3, 0); m.set(2, 0);"
" return m;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsMap());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Array.from(result.keys()).toString() === '1,a,3,2'"));
});
}
TEST_F(ValueSerializerTest, DecodeMap) {
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x3B, 0x3F, 0x01, 0x49, 0x54, 0x3F, 0x01, 0x53,
0x03, 0x66, 0x6F, 0x6F, 0x3A, 0x02},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsMap());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Map.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 1"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.get(42) === 'foo'"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3B, 0x3F, 0x01, 0x5E, 0x00, 0x3F, 0x01,
0x5E, 0x00, 0x3A, 0x02, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsMap());
EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 1"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.get(result) === result"));
});
// Iteration order must be preserved.
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3B, 0x3F, 0x01, 0x49, 0x02, 0x3F,
0x01, 0x49, 0x00, 0x3F, 0x01, 0x53, 0x01, 0x61, 0x3F, 0x01,
0x49, 0x00, 0x3F, 0x01, 0x49, 0x06, 0x3F, 0x01, 0x49, 0x00,
0x3F, 0x01, 0x49, 0x04, 0x3F, 0x01, 0x49, 0x00, 0x3A, 0x08},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsMap());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Array.from(result.keys()).toString() === '1,a,3,2'"));
});
}
TEST_F(ValueSerializerTest, RoundTripMapWithTrickyGetters) {
// Even if an entry is removed or reassigned, the original key/value pair is
// used.
RoundTripTest(
"(() => {"
" var m = new Map();"
" m.set(0, { get a() {"
" m.delete(1); m.set(2, 'baz'); m.set(3, 'quux');"
" }});"
" m.set(1, 'foo');"
" m.set(2, 'bar');"
" return m;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsMap());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Array.from(result.keys()).toString() === '0,1,2'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.get(1) === 'foo'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.get(2) === 'bar'"));
});
// However, deeper modifications of objects yet to be serialized still apply.
RoundTripTest(
"(() => {"
" var m = new Map();"
" var key = { get a() { value.foo = 'bar'; } };"
" var value = { get a() { key.baz = 'quux'; } };"
" m.set(key, value);"
" return m;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsMap());
EXPECT_TRUE(EvaluateScriptForResultBool(
"!('baz' in Array.from(result.keys())[0])"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"Array.from(result.values())[0].foo === 'bar'"));
});
}
TEST_F(ValueSerializerTest, RoundTripSet) {
RoundTripTest(
"(() => { var s = new Set(); s.add(42); s.add('foo'); return s; })()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsSet());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Set.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 2"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.has(42)"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.has('foo')"));
});
RoundTripTest(
"(() => { var s = new Set(); s.add(s); return s; })()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsSet());
EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 1"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.has(result)"));
});
// Iteration order must be preserved.
RoundTripTest(
"(() => {"
" var s = new Set();"
" s.add(1); s.add('a'); s.add(3); s.add(2);"
" return s;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsSet());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Array.from(result.keys()).toString() === '1,a,3,2'"));
});
}
TEST_F(ValueSerializerTest, DecodeSet) {
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x27, 0x3F, 0x01, 0x49, 0x54, 0x3F, 0x01,
0x53, 0x03, 0x66, 0x6F, 0x6F, 0x2C, 0x02},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsSet());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Set.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 2"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.has(42)"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.has('foo')"));
});
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x27, 0x3F, 0x01, 0x5E, 0x00, 0x2C, 0x01, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsSet());
EXPECT_TRUE(EvaluateScriptForResultBool("result.size === 1"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.has(result)"));
});
// Iteration order must be preserved.
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x27, 0x3F, 0x01, 0x49, 0x02, 0x3F, 0x01, 0x53,
0x01, 0x61, 0x3F, 0x01, 0x49, 0x06, 0x3F, 0x01, 0x49, 0x04, 0x2C, 0x04},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsSet());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Array.from(result.keys()).toString() === '1,a,3,2'"));
});
}
TEST_F(ValueSerializerTest, RoundTripSetWithTrickyGetters) {
// Even if an element is added or removed during serialization, the original
// set of elements is used.
RoundTripTest(
"(() => {"
" var s = new Set();"
" s.add({ get a() { s.delete(1); s.add(2); } });"
" s.add(1);"
" return s;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsSet());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Array.from(result.keys()).toString() === '[object Object],1'"));
});
// However, deeper modifications of objects yet to be serialized still apply.
RoundTripTest(
"(() => {"
" var s = new Set();"
" var first = { get a() { second.foo = 'bar'; } };"
" var second = { get a() { first.baz = 'quux'; } };"
" s.add(first);"
" s.add(second);"
" return s;"
"})()",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsSet());
EXPECT_TRUE(EvaluateScriptForResultBool(
"!('baz' in Array.from(result.keys())[0])"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"Array.from(result.keys())[1].foo === 'bar'"));
});
}
TEST_F(ValueSerializerTest, RoundTripArrayBuffer) {
RoundTripTest("new ArrayBuffer()", [this](Local<Value> value) {
ASSERT_TRUE(value->IsArrayBuffer());
EXPECT_EQ(0u, ArrayBuffer::Cast(*value)->ByteLength());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === ArrayBuffer.prototype"));
});
RoundTripTest("new Uint8Array([0, 128, 255]).buffer",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArrayBuffer());
EXPECT_EQ(3u, ArrayBuffer::Cast(*value)->ByteLength());
EXPECT_TRUE(EvaluateScriptForResultBool(
"new Uint8Array(result).toString() === '0,128,255'"));
});
RoundTripTest(
"({ a: new ArrayBuffer(), get b() { return this.a; }})",
[this](Local<Value> value) {
EXPECT_TRUE(
EvaluateScriptForResultBool("result.a instanceof ArrayBuffer"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
}
TEST_F(ValueSerializerTest, DecodeArrayBuffer) {
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x42, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArrayBuffer());
EXPECT_EQ(0u, ArrayBuffer::Cast(*value)->ByteLength());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === ArrayBuffer.prototype"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x42, 0x03, 0x00, 0x80, 0xFF, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArrayBuffer());
EXPECT_EQ(3u, ArrayBuffer::Cast(*value)->ByteLength());
EXPECT_TRUE(EvaluateScriptForResultBool(
"new Uint8Array(result).toString() === '0,128,255'"));
});
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x6F, 0x3F, 0x01, 0x53, 0x01,
0x61, 0x3F, 0x01, 0x42, 0x00, 0x3F, 0x02, 0x53, 0x01,
0x62, 0x3F, 0x02, 0x5E, 0x01, 0x7B, 0x02, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(
EvaluateScriptForResultBool("result.a instanceof ArrayBuffer"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
});
}
TEST_F(ValueSerializerTest, DecodeInvalidArrayBuffer) {
InvalidDecodeTest({0xFF, 0x09, 0x42, 0xFF, 0xFF, 0x00});
}
// An array buffer allocator that never has available memory.
class OOMArrayBufferAllocator : public ArrayBuffer::Allocator {
public:
void* Allocate(size_t) override { return nullptr; }
void* AllocateUninitialized(size_t) override { return nullptr; }
void* Reserve(size_t length) override { return nullptr; }
void Free(void* data, size_t length, AllocationMode mode) override {}
void Free(void*, size_t) override {}
void SetProtection(void* data, size_t length,
Protection protection) override {}
};
TEST_F(ValueSerializerTest, DecodeArrayBufferOOM) {
// This test uses less of the harness, because it has to customize the
// isolate.
OOMArrayBufferAllocator allocator;
Isolate::CreateParams params;
params.array_buffer_allocator = &allocator;
Isolate* isolate = Isolate::New(params);
{
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
Local<Context> context = Context::New(isolate);
Context::Scope context_scope(context);
TryCatch try_catch(isolate);
const std::vector<uint8_t> data = {0xFF, 0x09, 0x3F, 0x00, 0x42,
0x03, 0x00, 0x80, 0xFF, 0x00};
ValueDeserializer deserializer(isolate, &data[0],
static_cast<int>(data.size()), nullptr);
deserializer.SetSupportsLegacyWireFormat(true);
ASSERT_TRUE(deserializer.ReadHeader(context).FromMaybe(false));
ASSERT_FALSE(try_catch.HasCaught());
EXPECT_TRUE(deserializer.ReadValue(context).IsEmpty());
EXPECT_TRUE(try_catch.HasCaught());
}
isolate->Dispose();
}
// Includes an ArrayBuffer wrapper marked for transfer from the serialization
// context to the deserialization context.
class ValueSerializerTestWithArrayBufferTransfer : public ValueSerializerTest {
protected:
static const size_t kTestByteLength = 4;
ValueSerializerTestWithArrayBufferTransfer() {
{
Context::Scope scope(serialization_context());
input_buffer_ = ArrayBuffer::New(isolate(), nullptr, 0);
}
{
Context::Scope scope(deserialization_context());
output_buffer_ = ArrayBuffer::New(isolate(), kTestByteLength);
const uint8_t data[kTestByteLength] = {0x00, 0x01, 0x80, 0xFF};
memcpy(output_buffer_->GetContents().Data(), data, kTestByteLength);
}
}
const Local<ArrayBuffer>& input_buffer() { return input_buffer_; }
const Local<ArrayBuffer>& output_buffer() { return output_buffer_; }
void BeforeEncode(ValueSerializer* serializer) override {
serializer->TransferArrayBuffer(0, input_buffer_);
}
void AfterEncode() override { input_buffer_->Neuter(); }
void BeforeDecode(ValueDeserializer* deserializer) override {
deserializer->TransferArrayBuffer(0, output_buffer_);
}
private:
Local<ArrayBuffer> input_buffer_;
Local<ArrayBuffer> output_buffer_;
};
TEST_F(ValueSerializerTestWithArrayBufferTransfer,
RoundTripArrayBufferTransfer) {
RoundTripTest([this]() { return input_buffer(); },
[this](Local<Value> value) {
ASSERT_TRUE(value->IsArrayBuffer());
EXPECT_EQ(output_buffer(), value);
EXPECT_TRUE(EvaluateScriptForResultBool(
"new Uint8Array(result).toString() === '0,1,128,255'"));
});
RoundTripTest(
[this]() {
Local<Object> object = Object::New(isolate());
EXPECT_TRUE(object
->CreateDataProperty(serialization_context(),
StringFromUtf8("a"),
input_buffer())
.FromMaybe(false));
EXPECT_TRUE(object
->CreateDataProperty(serialization_context(),
StringFromUtf8("b"),
input_buffer())
.FromMaybe(false));
return object;
},
[this](Local<Value> value) {
EXPECT_TRUE(
EvaluateScriptForResultBool("result.a instanceof ArrayBuffer"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"new Uint8Array(result.a).toString() === '0,1,128,255'"));
});
}
TEST_F(ValueSerializerTest, RoundTripTypedArray) {
// Check that the right type comes out the other side for every kind of typed
// array.
#define TYPED_ARRAY_ROUND_TRIP_TEST(Type, type, TYPE, ctype, size) \
RoundTripTest("new " #Type "Array(2)", [this](Local<Value> value) { \
ASSERT_TRUE(value->Is##Type##Array()); \
EXPECT_EQ(2u * size, TypedArray::Cast(*value)->ByteLength()); \
EXPECT_EQ(2u, TypedArray::Cast(*value)->Length()); \
EXPECT_TRUE(EvaluateScriptForResultBool( \
"Object.getPrototypeOf(result) === " #Type "Array.prototype")); \
});
TYPED_ARRAYS(TYPED_ARRAY_ROUND_TRIP_TEST)
#undef TYPED_ARRAY_CASE
// Check that values of various kinds are suitably preserved.
RoundTripTest("new Uint8Array([1, 128, 255])", [this](Local<Value> value) {
EXPECT_TRUE(
EvaluateScriptForResultBool("result.toString() === '1,128,255'"));
});
RoundTripTest("new Int16Array([0, 256, -32768])", [this](Local<Value> value) {
EXPECT_TRUE(
EvaluateScriptForResultBool("result.toString() === '0,256,-32768'"));
});
RoundTripTest("new Float32Array([0, -0.5, NaN, Infinity])",
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === '0,-0.5,NaN,Infinity'"));
});
// Array buffer views sharing a buffer should do so on the other side.
// Similarly, multiple references to the same typed array should be resolved.
RoundTripTest(
"(() => {"
" var buffer = new ArrayBuffer(32);"
" return {"
" u8: new Uint8Array(buffer),"
" get u8_2() { return this.u8; },"
" f32: new Float32Array(buffer, 4, 5),"
" b: buffer,"
" };"
"})()",
[this](Local<Value> value) {
EXPECT_TRUE(
EvaluateScriptForResultBool("result.u8 instanceof Uint8Array"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.u8 === result.u8_2"));
EXPECT_TRUE(
EvaluateScriptForResultBool("result.f32 instanceof Float32Array"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.u8.buffer === result.f32.buffer"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.f32.byteOffset === 4"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.f32.length === 5"));
});
}
TEST_F(ValueSerializerTest, DecodeTypedArray) {
// Check that the right type comes out the other side for every kind of typed
// array.
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3F, 0x00, 0x42, 0x02, 0x00, 0x00, 0x56,
0x42, 0x00, 0x02},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsUint8Array());
EXPECT_EQ(2u, TypedArray::Cast(*value)->ByteLength());
EXPECT_EQ(2u, TypedArray::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Uint8Array.prototype"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3F, 0x00, 0x42, 0x02, 0x00, 0x00, 0x56,
0x62, 0x00, 0x02},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsInt8Array());
EXPECT_EQ(2u, TypedArray::Cast(*value)->ByteLength());
EXPECT_EQ(2u, TypedArray::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Int8Array.prototype"));
});
#if defined(V8_TARGET_LITTLE_ENDIAN)
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3F, 0x00, 0x42, 0x04, 0x00, 0x00, 0x00,
0x00, 0x56, 0x57, 0x00, 0x04},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsUint16Array());
EXPECT_EQ(4u, TypedArray::Cast(*value)->ByteLength());
EXPECT_EQ(2u, TypedArray::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Uint16Array.prototype"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3F, 0x00, 0x42, 0x04, 0x00, 0x00, 0x00,
0x00, 0x56, 0x77, 0x00, 0x04},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsInt16Array());
EXPECT_EQ(4u, TypedArray::Cast(*value)->ByteLength());
EXPECT_EQ(2u, TypedArray::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Int16Array.prototype"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3F, 0x00, 0x42, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x44, 0x00, 0x08},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsUint32Array());
EXPECT_EQ(8u, TypedArray::Cast(*value)->ByteLength());
EXPECT_EQ(2u, TypedArray::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Uint32Array.prototype"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3F, 0x00, 0x42, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x64, 0x00, 0x08},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsInt32Array());
EXPECT_EQ(8u, TypedArray::Cast(*value)->ByteLength());
EXPECT_EQ(2u, TypedArray::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Int32Array.prototype"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3F, 0x00, 0x42, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x66, 0x00, 0x08},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsFloat32Array());
EXPECT_EQ(8u, TypedArray::Cast(*value)->ByteLength());
EXPECT_EQ(2u, TypedArray::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Float32Array.prototype"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3F, 0x00, 0x42, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x56, 0x46, 0x00, 0x10},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsFloat64Array());
EXPECT_EQ(16u, TypedArray::Cast(*value)->ByteLength());
EXPECT_EQ(2u, TypedArray::Cast(*value)->Length());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Float64Array.prototype"));
});
#endif // V8_TARGET_LITTLE_ENDIAN
// Check that values of various kinds are suitably preserved.
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3F, 0x00, 0x42, 0x03, 0x01, 0x80, 0xFF,
0x56, 0x42, 0x00, 0x03, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === '1,128,255'"));
});
#if defined(V8_TARGET_LITTLE_ENDIAN)
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3F, 0x00, 0x42, 0x06, 0x00, 0x00, 0x00,
0x01, 0x00, 0x80, 0x56, 0x77, 0x00, 0x06},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === '0,256,-32768'"));
});
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3F, 0x00, 0x42, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0x00, 0x00, 0xC0, 0x7F,
0x00, 0x00, 0x80, 0x7F, 0x56, 0x66, 0x00, 0x10},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.toString() === '0,-0.5,NaN,Infinity'"));
});
#endif // V8_TARGET_LITTLE_ENDIAN
// Array buffer views sharing a buffer should do so on the other side.
// Similarly, multiple references to the same typed array should be resolved.
DecodeTest(
{0xFF, 0x09, 0x3F, 0x00, 0x6F, 0x3F, 0x01, 0x53, 0x02, 0x75, 0x38, 0x3F,
0x01, 0x3F, 0x01, 0x42, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x56, 0x42, 0x00, 0x20, 0x3F, 0x03, 0x53, 0x04, 0x75, 0x38, 0x5F,
0x32, 0x3F, 0x03, 0x5E, 0x02, 0x3F, 0x03, 0x53, 0x03, 0x66, 0x33, 0x32,
0x3F, 0x03, 0x3F, 0x03, 0x5E, 0x01, 0x56, 0x66, 0x04, 0x14, 0x3F, 0x04,
0x53, 0x01, 0x62, 0x3F, 0x04, 0x5E, 0x01, 0x7B, 0x04, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(
EvaluateScriptForResultBool("result.u8 instanceof Uint8Array"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.u8 === result.u8_2"));
EXPECT_TRUE(
EvaluateScriptForResultBool("result.f32 instanceof Float32Array"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"result.u8.buffer === result.f32.buffer"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.f32.byteOffset === 4"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.f32.length === 5"));
});
}
TEST_F(ValueSerializerTest, DecodeInvalidTypedArray) {
// Byte offset out of range.
InvalidDecodeTest(
{0xFF, 0x09, 0x42, 0x02, 0x00, 0x00, 0x56, 0x42, 0x03, 0x01});
// Byte offset in range, offset + length out of range.
InvalidDecodeTest(
{0xFF, 0x09, 0x42, 0x02, 0x00, 0x00, 0x56, 0x42, 0x01, 0x03});
// Byte offset not divisible by element size.
InvalidDecodeTest(
{0xFF, 0x09, 0x42, 0x04, 0x00, 0x00, 0x00, 0x00, 0x56, 0x77, 0x01, 0x02});
// Byte length not divisible by element size.
InvalidDecodeTest(
{0xFF, 0x09, 0x42, 0x04, 0x00, 0x00, 0x00, 0x00, 0x56, 0x77, 0x02, 0x01});
// Invalid view type (0xFF).
InvalidDecodeTest(
{0xFF, 0x09, 0x42, 0x02, 0x00, 0x00, 0x56, 0xFF, 0x01, 0x01});
}
TEST_F(ValueSerializerTest, RoundTripDataView) {
RoundTripTest("new DataView(new ArrayBuffer(4), 1, 2)",
[this](Local<Value> value) {
ASSERT_TRUE(value->IsDataView());
EXPECT_EQ(1u, DataView::Cast(*value)->ByteOffset());
EXPECT_EQ(2u, DataView::Cast(*value)->ByteLength());
EXPECT_EQ(4u, DataView::Cast(*value)->Buffer()->ByteLength());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === DataView.prototype"));
});
}
TEST_F(ValueSerializerTest, DecodeDataView) {
DecodeTest({0xFF, 0x09, 0x3F, 0x00, 0x3F, 0x00, 0x42, 0x04, 0x00, 0x00, 0x00,
0x00, 0x56, 0x3F, 0x01, 0x02},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsDataView());
EXPECT_EQ(1u, DataView::Cast(*value)->ByteOffset());
EXPECT_EQ(2u, DataView::Cast(*value)->ByteLength());
EXPECT_EQ(4u, DataView::Cast(*value)->Buffer()->ByteLength());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === DataView.prototype"));
});
}
TEST_F(ValueSerializerTest, DecodeInvalidDataView) {
// Byte offset out of range.
InvalidDecodeTest(
{0xFF, 0x09, 0x42, 0x02, 0x00, 0x00, 0x56, 0x3F, 0x03, 0x01});
// Byte offset in range, offset + length out of range.
InvalidDecodeTest(
{0xFF, 0x09, 0x42, 0x02, 0x00, 0x00, 0x56, 0x3F, 0x01, 0x03});
}
class ValueSerializerTestWithSharedArrayBufferTransfer
: public ValueSerializerTest {
protected:
ValueSerializerTestWithSharedArrayBufferTransfer()
: serializer_delegate_(this) {}
void InitializeData(const std::vector<uint8_t>& data) {
data_ = data;
{
Context::Scope scope(serialization_context());
input_buffer_ =
SharedArrayBuffer::New(isolate(), data_.data(), data_.size());
}
{
Context::Scope scope(deserialization_context());
output_buffer_ =
SharedArrayBuffer::New(isolate(), data_.data(), data_.size());
}
}
const Local<SharedArrayBuffer>& input_buffer() { return input_buffer_; }
const Local<SharedArrayBuffer>& output_buffer() { return output_buffer_; }
void BeforeDecode(ValueDeserializer* deserializer) override {
deserializer->TransferSharedArrayBuffer(0, output_buffer_);
}
static void SetUpTestCase() {
flag_was_enabled_ = i::FLAG_harmony_sharedarraybuffer;
i::FLAG_harmony_sharedarraybuffer = true;
ValueSerializerTest::SetUpTestCase();
}
static void TearDownTestCase() {
ValueSerializerTest::TearDownTestCase();
i::FLAG_harmony_sharedarraybuffer = flag_was_enabled_;
flag_was_enabled_ = false;
}
protected:
// GMock doesn't use the "override" keyword.
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winconsistent-missing-override"
#endif
class SerializerDelegate : public ValueSerializer::Delegate {
public:
explicit SerializerDelegate(
ValueSerializerTestWithSharedArrayBufferTransfer* test)
: test_(test) {}
MOCK_METHOD2(GetSharedArrayBufferId,
Maybe<uint32_t>(Isolate* isolate,
Local<SharedArrayBuffer> shared_array_buffer));
void ThrowDataCloneError(Local<String> message) override {
test_->isolate()->ThrowException(Exception::Error(message));
}
private:
ValueSerializerTestWithSharedArrayBufferTransfer* test_;
};
#if __clang__
#pragma clang diagnostic pop
#endif
ValueSerializer::Delegate* GetSerializerDelegate() override {
return &serializer_delegate_;
}
SerializerDelegate serializer_delegate_;
private:
static bool flag_was_enabled_;
std::vector<uint8_t> data_;