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:
parent
2e000127df
commit
255971d3ac
@ -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++) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user