Commit 2e000127 authored by jbroman's avatar jbroman Committed by Commit bot

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}
parent 06520ec2
......@@ -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
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment