Revert of Blink-compatible serialization of arrays, both dense and sparse. (patchset #6 id:100001 of https://codereview.chromium.org/2259633002/ )

Reason for revert:
Broke MIPS compile due to an uninitialization warning:

https://build.chromium.org/p/client.v8.ports/builders/V8%20Mips%20-%20builder/builds/3110/steps/compile/logs/stdio

Original issue's description:
> 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
>
> Committed: https://crrev.com/2e000127df2e88e31d352ef70af397741d1f2298
> Cr-Commit-Position: refs/heads/master@{#38729}

TBR=jkummerow@chromium.org
# Skipping CQ checks because original CL landed less than 1 days ago.
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=chromium:148757

Review-Url: https://codereview.chromium.org/2255313002
Cr-Commit-Position: refs/heads/master@{#38730}
This commit is contained in:
jbroman 2016-08-18 12:58:33 -07:00 committed by Commit bot
parent 2e000127df
commit 255971d3ac
3 changed files with 0 additions and 477 deletions

View File

@ -60,16 +60,6 @@ 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)
@ -263,8 +253,6 @@ 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));
@ -290,67 +278,6 @@ 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;
@ -527,10 +454,6 @@ MaybeHandle<Object> ValueDeserializer::ReadObject() {
}
case SerializationTag::kBeginJSObject:
return ReadJSObject();
case SerializationTag::kBeginSparseJSArray:
return ReadSparseJSArray();
case SerializationTag::kBeginDenseJSArray:
return ReadDenseJSArray();
default:
return MaybeHandle<Object>();
}
@ -594,71 +517,6 @@ 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++) {

View File

@ -72,7 +72,6 @@ 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
@ -141,8 +140,6 @@ 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

View File

@ -707,337 +707,5 @@ 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