Commit 058a7ee0 authored by jbroman's avatar jbroman Committed by Commit bot

Blink-compatible deserialization of old object format.

The "version 0" format did not deal with references, and used a stack model to
deserialize objects (conceptually, a postorder tree traversal). This requires
an explicit stack, so special logic is added to decode this format.

All subsequent versions also put an object marker at the beginning, which is
equivalent to how the current version serializes.

BUG=chromium:148757

Review-Url: https://codereview.chromium.org/2248893003
Cr-Commit-Position: refs/heads/master@{#38686}
parent 1c6cb5a4
......@@ -340,6 +340,12 @@ Maybe<SerializationTag> ValueDeserializer::PeekTag() const {
return Just(tag);
}
void ValueDeserializer::ConsumeTag(SerializationTag peeked_tag) {
SerializationTag actual_tag = ReadTag().ToChecked();
DCHECK(actual_tag == peeked_tag);
USE(actual_tag);
}
Maybe<SerializationTag> ValueDeserializer::ReadTag() {
SerializationTag tag;
do {
......@@ -517,9 +523,7 @@ Maybe<uint32_t> ValueDeserializer::ReadJSObjectProperties(
SerializationTag tag;
if (!PeekTag().To(&tag)) return Nothing<uint32_t>();
if (tag == end_tag) {
SerializationTag consumed_tag = ReadTag().ToChecked();
USE(consumed_tag);
DCHECK(tag == consumed_tag);
ConsumeTag(end_tag);
return Just(num_properties);
}
......@@ -569,5 +573,77 @@ void ValueDeserializer::AddObjectWithID(uint32_t id,
}
}
static MaybeHandle<JSObject> CreateJSObjectFromKeyValuePairs(
Isolate* isolate, Handle<Object>* data, uint32_t num_properties) {
Handle<JSObject> object =
isolate->factory()->NewJSObject(isolate->object_function());
for (unsigned i = 0; i < 2 * num_properties; i += 2) {
Handle<Object> key = data[i];
Handle<Object> value = data[i + 1];
bool success;
LookupIterator it = LookupIterator::PropertyOrElement(
isolate, object, key, &success, LookupIterator::OWN);
if (!success ||
JSObject::DefineOwnPropertyIgnoreAttributes(&it, value, NONE)
.is_null()) {
return MaybeHandle<JSObject>();
}
}
return object;
}
MaybeHandle<Object>
ValueDeserializer::ReadObjectUsingEntireBufferForLegacyFormat() {
if (version_ > 0) return MaybeHandle<Object>();
HandleScope scope(isolate_);
std::vector<Handle<Object>> stack;
while (position_ < end_) {
SerializationTag tag;
if (!PeekTag().To(&tag)) break;
Handle<Object> new_object;
switch (tag) {
case SerializationTag::kEndJSObject: {
ConsumeTag(SerializationTag::kEndJSObject);
// JS Object: Read the last 2*n values from the stack and use them as
// key-value pairs.
uint32_t num_properties;
if (!ReadVarint<uint32_t>().To(&num_properties) ||
stack.size() / 2 < num_properties) {
return MaybeHandle<Object>();
}
size_t begin_properties = stack.size() - 2 * num_properties;
Handle<Object>* data =
num_properties ? &stack[begin_properties] : nullptr;
if (!CreateJSObjectFromKeyValuePairs(isolate_, data, num_properties)
.ToHandle(&new_object)) {
return MaybeHandle<Object>();
}
stack.resize(begin_properties);
break;
}
default:
if (!ReadObject().ToHandle(&new_object)) return MaybeHandle<Object>();
break;
}
stack.push_back(new_object);
}
// Nothing remains but padding.
#ifdef DEBUG
while (position_ < end_) {
DCHECK(*position_++ == static_cast<uint8_t>(SerializationTag::kPadding));
}
#endif
position_ = end_;
if (stack.size() != 1) return MaybeHandle<Object>();
return scope.CloseAndEscape(stack[0]);
}
} // namespace internal
} // namespace v8
......@@ -113,9 +113,20 @@ class ValueDeserializer {
*/
MaybeHandle<Object> ReadObject() WARN_UNUSED_RESULT;
/*
* Reads an object, consuming the entire buffer.
*
* This is required for the legacy "version 0" format, which did not allow
* reference deduplication, and instead relied on a "stack" model for
* deserializing, with the contents of objects and arrays provided first.
*/
MaybeHandle<Object> ReadObjectUsingEntireBufferForLegacyFormat()
WARN_UNUSED_RESULT;
private:
// Reading the wire format.
Maybe<SerializationTag> PeekTag() const WARN_UNUSED_RESULT;
void ConsumeTag(SerializationTag peeked_tag);
Maybe<SerializationTag> ReadTag() WARN_UNUSED_RESULT;
template <typename T>
Maybe<T> ReadVarint() WARN_UNUSED_RESULT;
......
......@@ -108,6 +108,34 @@ class ValueSerializerTest : public TestWithIsolate {
ASSERT_FALSE(try_catch.HasCaught());
}
template <typename OutputFunctor>
void DecodeTestForVersion0(const std::vector<uint8_t>& data,
const OutputFunctor& output_functor) {
Context::Scope scope(deserialization_context());
TryCatch try_catch(isolate());
// TODO(jbroman): Use the public API once it exists.
i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate());
i::HandleScope handle_scope(internal_isolate);
i::ValueDeserializer deserializer(
internal_isolate,
i::Vector<const uint8_t>(&data[0], static_cast<int>(data.size())));
// TODO(jbroman): Enable legacy support.
ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false));
// TODO(jbroman): Check version 0.
Local<Value> result;
ASSERT_TRUE(ToLocal<Value>(
deserializer.ReadObjectUsingEntireBufferForLegacyFormat(), &result));
ASSERT_FALSE(result.IsEmpty());
ASSERT_FALSE(try_catch.HasCaught());
ASSERT_TRUE(deserialization_context()
->Global()
->CreateDataProperty(deserialization_context_,
StringFromUtf8("result"), result)
.FromMaybe(false));
output_functor(result);
ASSERT_FALSE(try_catch.HasCaught());
}
void InvalidDecodeTest(const std::vector<uint8_t>& data) {
Context::Scope scope(deserialization_context());
TryCatch try_catch(isolate());
......@@ -627,5 +655,57 @@ TEST_F(ValueSerializerTest, RoundTripTrickyGetters) {
});
}
TEST_F(ValueSerializerTest, DecodeDictionaryObjectVersion0) {
// Empty object.
DecodeTestForVersion0(
{0x7b, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsObject());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Object.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getOwnPropertyNames(result).length === 0"));
});
// String key.
DecodeTestForVersion0(
{0x53, 0x01, 0x61, 0x49, 0x54, 0x7b, 0x01, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsObject());
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getPrototypeOf(result) === Object.prototype"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty('a')"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === 42"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getOwnPropertyNames(result).length === 1"));
});
// Integer key (treated as a string, but may be encoded differently).
DecodeTestForVersion0(
{0x49, 0x54, 0x53, 0x01, 0x61, 0x7b, 0x01, 0x00},
[this](Local<Value> value) {
ASSERT_TRUE(value->IsObject());
EXPECT_TRUE(EvaluateScriptForResultBool("result.hasOwnProperty('42')"));
EXPECT_TRUE(EvaluateScriptForResultBool("result[42] === 'a'"));
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getOwnPropertyNames(result).length === 1"));
});
// Key order must be preserved.
DecodeTestForVersion0(
{0x53, 0x01, 0x78, 0x49, 0x02, 0x53, 0x01, 0x79, 0x49, 0x04, 0x53, 0x01,
0x61, 0x49, 0x06, 0x7b, 0x03, 0x00},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getOwnPropertyNames(result).toString() === 'x,y,a'"));
});
// A property and an element.
DecodeTestForVersion0(
{0x49, 0x54, 0x53, 0x01, 0x61, 0x53, 0x01, 0x61, 0x49, 0x54, 0x7b, 0x02},
[this](Local<Value> value) {
EXPECT_TRUE(EvaluateScriptForResultBool(
"Object.getOwnPropertyNames(result).toString() === '42,a'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result[42] === 'a'"));
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === 42"));
});
}
} // namespace
} // namespace v8
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