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

Follow object map transitions when deserializing object properties.

Similar to json-parser.

BUG=chromium:148757

Review-Url: https://codereview.chromium.org/2334353002
Cr-Commit-Position: refs/heads/master@{#39429}
parent b249ffc1
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "src/isolate.h" #include "src/isolate.h"
#include "src/objects-inl.h" #include "src/objects-inl.h"
#include "src/objects.h" #include "src/objects.h"
#include "src/transitions.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
...@@ -1062,6 +1063,46 @@ MaybeHandle<String> ValueDeserializer::ReadTwoByteString() { ...@@ -1062,6 +1063,46 @@ MaybeHandle<String> ValueDeserializer::ReadTwoByteString() {
return string; return string;
} }
bool ValueDeserializer::ReadExpectedString(Handle<String> expected) {
// In the case of failure, the position in the stream is reset.
const uint8_t* original_position = position_;
SerializationTag tag;
uint32_t byte_length;
Vector<const uint8_t> bytes;
if (!ReadTag().To(&tag) || !ReadVarint<uint32_t>().To(&byte_length) ||
byte_length >
static_cast<uint32_t>(std::numeric_limits<int32_t>::max()) ||
!ReadRawBytes(byte_length).To(&bytes)) {
position_ = original_position;
return false;
}
expected = String::Flatten(expected);
DisallowHeapAllocation no_gc;
String::FlatContent flat = expected->GetFlatContent();
// If the bytes are verbatim what is in the flattened string, then the string
// is successfully consumed.
if (tag == SerializationTag::kUtf8String && flat.IsOneByte()) {
Vector<const uint8_t> chars = flat.ToOneByteVector();
if (byte_length == chars.length() &&
String::IsAscii(chars.begin(), chars.length()) &&
memcmp(bytes.begin(), chars.begin(), byte_length) == 0) {
return true;
}
} else if (tag == SerializationTag::kTwoByteString && flat.IsTwoByte()) {
Vector<const uc16> chars = flat.ToUC16Vector();
if (byte_length == static_cast<unsigned>(chars.length()) * sizeof(uc16) &&
memcmp(bytes.begin(), chars.begin(), byte_length) == 0) {
return true;
}
}
position_ = original_position;
return false;
}
MaybeHandle<JSObject> ValueDeserializer::ReadJSObject() { MaybeHandle<JSObject> ValueDeserializer::ReadJSObject() {
// If we are at the end of the stack, abort. This function may recurse. // If we are at the end of the stack, abort. This function may recurse.
STACK_CHECK(isolate_, MaybeHandle<JSObject>()); STACK_CHECK(isolate_, MaybeHandle<JSObject>());
...@@ -1074,7 +1115,7 @@ MaybeHandle<JSObject> ValueDeserializer::ReadJSObject() { ...@@ -1074,7 +1115,7 @@ MaybeHandle<JSObject> ValueDeserializer::ReadJSObject() {
uint32_t num_properties; uint32_t num_properties;
uint32_t expected_num_properties; uint32_t expected_num_properties;
if (!ReadJSObjectProperties(object, SerializationTag::kEndJSObject) if (!ReadJSObjectProperties(object, SerializationTag::kEndJSObject, true)
.To(&num_properties) || .To(&num_properties) ||
!ReadVarint<uint32_t>().To(&expected_num_properties) || !ReadVarint<uint32_t>().To(&expected_num_properties) ||
num_properties != expected_num_properties) { num_properties != expected_num_properties) {
...@@ -1102,7 +1143,7 @@ MaybeHandle<JSArray> ValueDeserializer::ReadSparseJSArray() { ...@@ -1102,7 +1143,7 @@ MaybeHandle<JSArray> ValueDeserializer::ReadSparseJSArray() {
uint32_t num_properties; uint32_t num_properties;
uint32_t expected_num_properties; uint32_t expected_num_properties;
uint32_t expected_length; uint32_t expected_length;
if (!ReadJSObjectProperties(array, SerializationTag::kEndSparseJSArray) if (!ReadJSObjectProperties(array, SerializationTag::kEndSparseJSArray, false)
.To(&num_properties) || .To(&num_properties) ||
!ReadVarint<uint32_t>().To(&expected_num_properties) || !ReadVarint<uint32_t>().To(&expected_num_properties) ||
!ReadVarint<uint32_t>().To(&expected_length) || !ReadVarint<uint32_t>().To(&expected_length) ||
...@@ -1140,7 +1181,7 @@ MaybeHandle<JSArray> ValueDeserializer::ReadDenseJSArray() { ...@@ -1140,7 +1181,7 @@ MaybeHandle<JSArray> ValueDeserializer::ReadDenseJSArray() {
uint32_t num_properties; uint32_t num_properties;
uint32_t expected_num_properties; uint32_t expected_num_properties;
uint32_t expected_length; uint32_t expected_length;
if (!ReadJSObjectProperties(array, SerializationTag::kEndDenseJSArray) if (!ReadJSObjectProperties(array, SerializationTag::kEndDenseJSArray, false)
.To(&num_properties) || .To(&num_properties) ||
!ReadVarint<uint32_t>().To(&expected_num_properties) || !ReadVarint<uint32_t>().To(&expected_num_properties) ||
!ReadVarint<uint32_t>().To(&expected_length) || !ReadVarint<uint32_t>().To(&expected_length) ||
...@@ -1389,9 +1430,127 @@ MaybeHandle<JSObject> ValueDeserializer::ReadHostObject() { ...@@ -1389,9 +1430,127 @@ MaybeHandle<JSObject> ValueDeserializer::ReadHostObject() {
return js_object; return js_object;
} }
// Copies a vector of property values into an object, given the map that should
// be used.
static void CommitProperties(Handle<JSObject> object, Handle<Map> map,
const std::vector<Handle<Object>>& properties) {
JSObject::AllocateStorageForMap(object, map);
DCHECK(!object->map()->is_dictionary_map());
DisallowHeapAllocation no_gc;
DescriptorArray* descriptors = object->map()->instance_descriptors();
for (unsigned i = 0; i < properties.size(); i++) {
object->WriteToField(i, descriptors->GetDetails(i), *properties[i]);
}
}
Maybe<uint32_t> ValueDeserializer::ReadJSObjectProperties( Maybe<uint32_t> ValueDeserializer::ReadJSObjectProperties(
Handle<JSObject> object, SerializationTag end_tag) { Handle<JSObject> object, SerializationTag end_tag,
for (uint32_t num_properties = 0;; num_properties++) { bool can_use_transitions) {
uint32_t num_properties = 0;
// Fast path (following map transitions).
if (can_use_transitions) {
bool transitioning = true;
Handle<Map> map(object->map(), isolate_);
DCHECK(!map->is_dictionary_map());
DCHECK(map->instance_descriptors()->IsEmpty());
std::vector<Handle<Object>> properties;
properties.reserve(8);
while (transitioning) {
// If there are no more properties, finish.
SerializationTag tag;
if (!PeekTag().To(&tag)) return Nothing<uint32_t>();
if (tag == end_tag) {
ConsumeTag(end_tag);
CommitProperties(object, map, properties);
CHECK_LT(properties.size(), std::numeric_limits<uint32_t>::max());
return Just(static_cast<uint32_t>(properties.size()));
}
// Determine the key to be used and the target map to transition to, if
// possible. Transitioning may abort if the key is not a string, or if no
// transition was found.
Handle<Object> key;
Handle<Map> target;
Handle<String> expected_key = TransitionArray::ExpectedTransitionKey(map);
if (!expected_key.is_null() && ReadExpectedString(expected_key)) {
key = expected_key;
target = TransitionArray::ExpectedTransitionTarget(map);
} else {
if (!ReadObject().ToHandle(&key)) return Nothing<uint32_t>();
if (key->IsString()) {
key =
isolate_->factory()->InternalizeString(Handle<String>::cast(key));
target = TransitionArray::FindTransitionToField(
map, Handle<String>::cast(key));
transitioning = !target.is_null();
} else {
transitioning = false;
}
}
// Read the value that corresponds to it.
Handle<Object> value;
if (!ReadObject().ToHandle(&value)) return Nothing<uint32_t>();
// If still transitioning and the value fits the field representation
// (though generalization may be required), store the property value so
// that we can copy them all at once. Otherwise, stop transitioning.
if (transitioning) {
int descriptor = static_cast<int>(properties.size());
PropertyDetails details =
target->instance_descriptors()->GetDetails(descriptor);
Representation expected_representation = details.representation();
if (value->FitsRepresentation(expected_representation)) {
if (expected_representation.IsHeapObject() &&
!target->instance_descriptors()
->GetFieldType(descriptor)
->NowContains(value)) {
Handle<FieldType> value_type =
value->OptimalType(isolate_, expected_representation);
Map::GeneralizeFieldType(target, descriptor,
expected_representation, value_type);
}
DCHECK(target->instance_descriptors()
->GetFieldType(descriptor)
->NowContains(value));
properties.push_back(value);
map = target;
continue;
} else {
transitioning = false;
}
}
// Fell out of transitioning fast path. Commit the properties gathered so
// far, and then start setting properties slowly instead.
DCHECK(!transitioning);
CHECK_LT(properties.size(), std::numeric_limits<uint32_t>::max());
CommitProperties(object, map, properties);
num_properties = static_cast<uint32_t>(properties.size());
bool success;
LookupIterator it = LookupIterator::PropertyOrElement(
isolate_, object, key, &success, LookupIterator::OWN);
if (!success ||
JSObject::DefineOwnPropertyIgnoreAttributes(&it, value, NONE)
.is_null()) {
return Nothing<uint32_t>();
}
num_properties++;
}
// At this point, transitioning should be done, but at least one property
// should have been written (in the zero-property case, there is an early
// return).
DCHECK(!transitioning);
DCHECK_GE(num_properties, 1u);
}
// Slow path.
for (;; num_properties++) {
SerializationTag tag; SerializationTag tag;
if (!PeekTag().To(&tag)) return Nothing<uint32_t>(); if (!PeekTag().To(&tag)) return Nothing<uint32_t>();
if (tag == end_tag) { if (tag == end_tag) {
......
...@@ -204,6 +204,10 @@ class ValueDeserializer { ...@@ -204,6 +204,10 @@ class ValueDeserializer {
Maybe<double> ReadDouble() WARN_UNUSED_RESULT; Maybe<double> ReadDouble() WARN_UNUSED_RESULT;
Maybe<Vector<const uint8_t>> ReadRawBytes(int size) WARN_UNUSED_RESULT; Maybe<Vector<const uint8_t>> ReadRawBytes(int size) WARN_UNUSED_RESULT;
// Reads a string if it matches the one provided.
// Returns true if this was the case. Otherwise, nothing is consumed.
bool ReadExpectedString(Handle<String> expected) WARN_UNUSED_RESULT;
// Like ReadObject, but skips logic for special cases in simulating the // Like ReadObject, but skips logic for special cases in simulating the
// "stack machine". // "stack machine".
MaybeHandle<Object> ReadObjectInternal() WARN_UNUSED_RESULT; MaybeHandle<Object> ReadObjectInternal() WARN_UNUSED_RESULT;
...@@ -232,7 +236,8 @@ class ValueDeserializer { ...@@ -232,7 +236,8 @@ class ValueDeserializer {
* encountered. If successful, returns the number of properties read. * encountered. If successful, returns the number of properties read.
*/ */
Maybe<uint32_t> ReadJSObjectProperties(Handle<JSObject> object, Maybe<uint32_t> ReadJSObjectProperties(Handle<JSObject> object,
SerializationTag end_tag); SerializationTag end_tag,
bool can_use_transitions);
// Manipulating the map from IDs to reified objects. // Manipulating the map from IDs to reified objects.
bool HasObjectWithID(uint32_t id); bool HasObjectWithID(uint32_t id);
......
...@@ -86,6 +86,21 @@ class ValueSerializerTest : public TestWithIsolate { ...@@ -86,6 +86,21 @@ class ValueSerializerTest : public TestWithIsolate {
output_functor); 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) { Maybe<std::vector<uint8_t>> DoEncode(Local<Value> value) {
Local<Context> context = serialization_context(); Local<Context> context = serialization_context();
ValueSerializer serializer(isolate(), GetSerializerDelegate()); ValueSerializer serializer(isolate(), GetSerializerDelegate());
...@@ -704,6 +719,31 @@ TEST_F(ValueSerializerTest, RoundTripTrickyGetters) { ...@@ -704,6 +719,31 @@ TEST_F(ValueSerializerTest, RoundTripTrickyGetters) {
}); });
} }
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) { TEST_F(ValueSerializerTest, DecodeDictionaryObjectVersion0) {
// Empty object. // Empty object.
DecodeTestForVersion0( DecodeTestForVersion0(
......
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