Blink-compatible serialization of arrays, both dense and sparse.
The current "dense" format is not expressive enough to distinguish between an element that is not defined and one that has the value "undefined", but in this CL the existing behaviour of Blink is used for such cases. Format changes to fix these issues could be made later on. Not included in this CL is compatibility with version 0 arrays. Those will be implemented in a separate CL. BUG=chromium:148757 Review-Url: https://codereview.chromium.org/2259633002 Cr-Commit-Position: refs/heads/master@{#38729}
This commit is contained in:
parent
06520ec220
commit
2e000127df
@ -60,6 +60,16 @@ enum class SerializationTag : uint8_t {
|
||||
kBeginJSObject = 'o',
|
||||
// End of a JS object. numProperties:uint32_t
|
||||
kEndJSObject = '{',
|
||||
// Beginning of a sparse JS array. length:uint32_t
|
||||
// Elements and properties are written as key/value pairs, like objects.
|
||||
kBeginSparseJSArray = 'a',
|
||||
// End of a sparse JS array. numProperties:uint32_t length:uint32_t
|
||||
kEndSparseJSArray = '@',
|
||||
// Beginning of a dense JS array. length:uint32_t
|
||||
// |length| elements, followed by properties as key/value pairs
|
||||
kBeginDenseJSArray = 'A',
|
||||
// End of a dense JS array. numProperties:uint32_t length:uint32_t
|
||||
kEndDenseJSArray = '$',
|
||||
};
|
||||
|
||||
ValueSerializer::ValueSerializer(Isolate* isolate)
|
||||
@ -253,6 +263,8 @@ Maybe<bool> ValueSerializer::WriteJSReceiver(Handle<JSReceiver> receiver) {
|
||||
|
||||
HandleScope scope(isolate_);
|
||||
switch (instance_type) {
|
||||
case JS_ARRAY_TYPE:
|
||||
return WriteJSArray(Handle<JSArray>::cast(receiver));
|
||||
case JS_OBJECT_TYPE:
|
||||
case JS_API_OBJECT_TYPE:
|
||||
return WriteJSObject(Handle<JSObject>::cast(receiver));
|
||||
@ -278,6 +290,67 @@ Maybe<bool> ValueSerializer::WriteJSObject(Handle<JSObject> object) {
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteJSArray(Handle<JSArray> array) {
|
||||
uint32_t length;
|
||||
array->length()->ToArrayLength(&length);
|
||||
|
||||
// To keep things simple, for now we decide between dense and sparse
|
||||
// serialization based on elements kind. A more principled heuristic could
|
||||
// count the elements, but would need to take care to note which indices
|
||||
// existed (as only indices which were enumerable own properties at this point
|
||||
// should be serialized).
|
||||
const bool should_serialize_densely =
|
||||
array->HasFastElements() && !array->HasFastHoleyElements();
|
||||
|
||||
if (should_serialize_densely) {
|
||||
// TODO(jbroman): Distinguish between undefined and a hole (this can happen
|
||||
// if serializing one of the elements deletes another). This requires wire
|
||||
// format changes.
|
||||
WriteTag(SerializationTag::kBeginDenseJSArray);
|
||||
WriteVarint<uint32_t>(length);
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
// Serializing the array's elements can have arbitrary side effects, so we
|
||||
// cannot rely on still having fast elements, even if it did to begin
|
||||
// with.
|
||||
Handle<Object> element;
|
||||
LookupIterator it(isolate_, array, i, array, LookupIterator::OWN);
|
||||
if (!Object::GetProperty(&it).ToHandle(&element) ||
|
||||
!WriteObject(element).FromMaybe(false)) {
|
||||
return Nothing<bool>();
|
||||
}
|
||||
}
|
||||
KeyAccumulator accumulator(isolate_, KeyCollectionMode::kOwnOnly,
|
||||
ENUMERABLE_STRINGS);
|
||||
if (!accumulator.CollectOwnPropertyNames(array, array).FromMaybe(false)) {
|
||||
return Nothing<bool>();
|
||||
}
|
||||
Handle<FixedArray> keys =
|
||||
accumulator.GetKeys(GetKeysConversion::kConvertToString);
|
||||
uint32_t properties_written;
|
||||
if (!WriteJSObjectProperties(array, keys).To(&properties_written)) {
|
||||
return Nothing<bool>();
|
||||
}
|
||||
WriteTag(SerializationTag::kEndDenseJSArray);
|
||||
WriteVarint<uint32_t>(properties_written);
|
||||
WriteVarint<uint32_t>(length);
|
||||
} else {
|
||||
WriteTag(SerializationTag::kBeginSparseJSArray);
|
||||
WriteVarint<uint32_t>(length);
|
||||
Handle<FixedArray> keys;
|
||||
uint32_t properties_written;
|
||||
if (!KeyAccumulator::GetKeys(array, KeyCollectionMode::kOwnOnly,
|
||||
ENUMERABLE_STRINGS)
|
||||
.ToHandle(&keys) ||
|
||||
!WriteJSObjectProperties(array, keys).To(&properties_written)) {
|
||||
return Nothing<bool>();
|
||||
}
|
||||
WriteTag(SerializationTag::kEndSparseJSArray);
|
||||
WriteVarint<uint32_t>(properties_written);
|
||||
WriteVarint<uint32_t>(length);
|
||||
}
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
Maybe<uint32_t> ValueSerializer::WriteJSObjectProperties(
|
||||
Handle<JSObject> object, Handle<FixedArray> keys) {
|
||||
uint32_t properties_written = 0;
|
||||
@ -454,6 +527,10 @@ MaybeHandle<Object> ValueDeserializer::ReadObject() {
|
||||
}
|
||||
case SerializationTag::kBeginJSObject:
|
||||
return ReadJSObject();
|
||||
case SerializationTag::kBeginSparseJSArray:
|
||||
return ReadSparseJSArray();
|
||||
case SerializationTag::kBeginDenseJSArray:
|
||||
return ReadDenseJSArray();
|
||||
default:
|
||||
return MaybeHandle<Object>();
|
||||
}
|
||||
@ -517,6 +594,71 @@ MaybeHandle<JSObject> ValueDeserializer::ReadJSObject() {
|
||||
return scope.CloseAndEscape(object);
|
||||
}
|
||||
|
||||
MaybeHandle<JSArray> ValueDeserializer::ReadSparseJSArray() {
|
||||
// If we are at the end of the stack, abort. This function may recurse.
|
||||
if (StackLimitCheck(isolate_).HasOverflowed()) return MaybeHandle<JSArray>();
|
||||
|
||||
uint32_t length;
|
||||
if (!ReadVarint<uint32_t>().To(&length)) return MaybeHandle<JSArray>();
|
||||
|
||||
uint32_t id = next_id_++;
|
||||
HandleScope scope(isolate_);
|
||||
Handle<JSArray> array = isolate_->factory()->NewJSArray(0);
|
||||
JSArray::SetLength(array, length);
|
||||
AddObjectWithID(id, array);
|
||||
|
||||
uint32_t num_properties;
|
||||
uint32_t expected_num_properties;
|
||||
uint32_t expected_length;
|
||||
if (!ReadJSObjectProperties(array, SerializationTag::kEndSparseJSArray)
|
||||
.To(&num_properties) ||
|
||||
!ReadVarint<uint32_t>().To(&expected_num_properties) ||
|
||||
!ReadVarint<uint32_t>().To(&expected_length) ||
|
||||
num_properties != expected_num_properties || length != expected_length) {
|
||||
return MaybeHandle<JSArray>();
|
||||
}
|
||||
|
||||
DCHECK(HasObjectWithID(id));
|
||||
return scope.CloseAndEscape(array);
|
||||
}
|
||||
|
||||
MaybeHandle<JSArray> ValueDeserializer::ReadDenseJSArray() {
|
||||
// If we are at the end of the stack, abort. This function may recurse.
|
||||
if (StackLimitCheck(isolate_).HasOverflowed()) return MaybeHandle<JSArray>();
|
||||
|
||||
uint32_t length;
|
||||
if (!ReadVarint<uint32_t>().To(&length)) return MaybeHandle<JSArray>();
|
||||
|
||||
uint32_t id = next_id_++;
|
||||
HandleScope scope(isolate_);
|
||||
Handle<JSArray> array = isolate_->factory()->NewJSArray(
|
||||
FAST_HOLEY_ELEMENTS, length, length, INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE);
|
||||
AddObjectWithID(id, array);
|
||||
|
||||
Handle<FixedArray> elements(FixedArray::cast(array->elements()), isolate_);
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
Handle<Object> element;
|
||||
if (!ReadObject().ToHandle(&element)) return MaybeHandle<JSArray>();
|
||||
// TODO(jbroman): Distinguish between undefined and a hole.
|
||||
if (element->IsUndefined(isolate_)) continue;
|
||||
elements->set(i, *element);
|
||||
}
|
||||
|
||||
uint32_t num_properties;
|
||||
uint32_t expected_num_properties;
|
||||
uint32_t expected_length;
|
||||
if (!ReadJSObjectProperties(array, SerializationTag::kEndDenseJSArray)
|
||||
.To(&num_properties) ||
|
||||
!ReadVarint<uint32_t>().To(&expected_num_properties) ||
|
||||
!ReadVarint<uint32_t>().To(&expected_length) ||
|
||||
num_properties != expected_num_properties || length != expected_length) {
|
||||
return MaybeHandle<JSArray>();
|
||||
}
|
||||
|
||||
DCHECK(HasObjectWithID(id));
|
||||
return scope.CloseAndEscape(array);
|
||||
}
|
||||
|
||||
Maybe<uint32_t> ValueDeserializer::ReadJSObjectProperties(
|
||||
Handle<JSObject> object, SerializationTag end_tag) {
|
||||
for (uint32_t num_properties = 0;; num_properties++) {
|
||||
|
@ -72,6 +72,7 @@ class ValueSerializer {
|
||||
void WriteString(Handle<String> string);
|
||||
Maybe<bool> WriteJSReceiver(Handle<JSReceiver> receiver) WARN_UNUSED_RESULT;
|
||||
Maybe<bool> WriteJSObject(Handle<JSObject> object) WARN_UNUSED_RESULT;
|
||||
Maybe<bool> WriteJSArray(Handle<JSArray> array) WARN_UNUSED_RESULT;
|
||||
|
||||
/*
|
||||
* Reads the specified keys from the object and writes key-value pairs to the
|
||||
@ -140,6 +141,8 @@ class ValueDeserializer {
|
||||
MaybeHandle<String> ReadUtf8String() WARN_UNUSED_RESULT;
|
||||
MaybeHandle<String> ReadTwoByteString() WARN_UNUSED_RESULT;
|
||||
MaybeHandle<JSObject> ReadJSObject() WARN_UNUSED_RESULT;
|
||||
MaybeHandle<JSArray> ReadSparseJSArray() WARN_UNUSED_RESULT;
|
||||
MaybeHandle<JSArray> ReadDenseJSArray() WARN_UNUSED_RESULT;
|
||||
|
||||
/*
|
||||
* Reads key-value pairs into the object until the specified end tag is
|
||||
|
@ -707,5 +707,337 @@ TEST_F(ValueSerializerTest, DecodeDictionaryObjectVersion0) {
|
||||
});
|
||||
}
|
||||
|
||||
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(5, 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(1000, 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(2, 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(1000, 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(1, 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(1000, 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(2, 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(1000, 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(2, 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(5, 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(1000, 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(2, 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(1000, 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(1, 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(1000, 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(2, 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(1000, 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(2, 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, 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(3, 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(2, 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(1000, 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(4, Array::Cast(*value)->Length());
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === 1"));
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(2)"));
|
||||
});
|
||||
// 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(1000, Array::Cast(*value)->Length());
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("result[0] === 1"));
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("!result.hasOwnProperty(2)"));
|
||||
});
|
||||
// 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(2, 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(1000, 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(3, 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(1000, 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(2, 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(1000, Array::Cast(*value)->Length());
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("!(1 in result)"));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace v8
|
||||
|
Loading…
Reference in New Issue
Block a user