Blink-compatible serialization of oddball values.
BUG=chromium:148757 Review-Url: https://codereview.chromium.org/2232243003 Cr-Commit-Position: refs/heads/master@{#38625}
This commit is contained in:
parent
bb9707c8d2
commit
e6d1a80e79
2
BUILD.gn
2
BUILD.gn
@ -1625,6 +1625,8 @@ v8_source_set("v8_base") {
|
||||
"src/v8memory.h",
|
||||
"src/v8threads.cc",
|
||||
"src/v8threads.h",
|
||||
"src/value-serializer.cc",
|
||||
"src/value-serializer.h",
|
||||
"src/version.cc",
|
||||
"src/version.h",
|
||||
"src/vm-state-inl.h",
|
||||
|
@ -1231,6 +1231,8 @@
|
||||
'v8memory.h',
|
||||
'v8threads.cc',
|
||||
'v8threads.h',
|
||||
'value-serializer.cc',
|
||||
'value-serializer.h',
|
||||
'vector.h',
|
||||
'version.cc',
|
||||
'version.h',
|
||||
|
174
src/value-serializer.cc
Normal file
174
src/value-serializer.cc
Normal file
@ -0,0 +1,174 @@
|
||||
// Copyright 2016 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/value-serializer.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "src/base/logging.h"
|
||||
#include "src/factory.h"
|
||||
#include "src/handles-inl.h"
|
||||
#include "src/isolate.h"
|
||||
#include "src/objects-inl.h"
|
||||
#include "src/objects.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
static const uint32_t kLatestVersion = 9;
|
||||
|
||||
enum class SerializationTag : uint8_t {
|
||||
kVersion = 0xFF,
|
||||
kPadding = '\0',
|
||||
kVerifyObjectCount = '?',
|
||||
kUndefined = '_',
|
||||
kNull = '0',
|
||||
kTrue = 'T',
|
||||
kFalse = 'F',
|
||||
};
|
||||
|
||||
ValueSerializer::ValueSerializer() {}
|
||||
|
||||
ValueSerializer::~ValueSerializer() {}
|
||||
|
||||
void ValueSerializer::WriteHeader() {
|
||||
WriteTag(SerializationTag::kVersion);
|
||||
WriteVarint(kLatestVersion);
|
||||
}
|
||||
|
||||
void ValueSerializer::WriteTag(SerializationTag tag) {
|
||||
buffer_.push_back(static_cast<uint8_t>(tag));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ValueSerializer::WriteVarint(T value) {
|
||||
// Writes an unsigned integer as a base-128 varint.
|
||||
// The number is written, 7 bits at a time, from the least significant to the
|
||||
// most significant 7 bits. Each byte, except the last, has the MSB set.
|
||||
// See also https://developers.google.com/protocol-buffers/docs/encoding
|
||||
static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value,
|
||||
"Only unsigned integer types can be written as varints.");
|
||||
uint8_t stack_buffer[sizeof(T) * 8 / 7 + 1];
|
||||
uint8_t* next_byte = &stack_buffer[0];
|
||||
do {
|
||||
*next_byte = (value & 0x7f) | 0x80;
|
||||
next_byte++;
|
||||
value >>= 7;
|
||||
} while (value);
|
||||
*(next_byte - 1) &= 0x7f;
|
||||
buffer_.insert(buffer_.end(), stack_buffer, next_byte);
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) {
|
||||
if (object->IsSmi()) UNIMPLEMENTED();
|
||||
|
||||
DCHECK(object->IsHeapObject());
|
||||
switch (HeapObject::cast(*object)->map()->instance_type()) {
|
||||
case ODDBALL_TYPE:
|
||||
WriteOddball(Oddball::cast(*object));
|
||||
return Just(true);
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
return Nothing<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
void ValueSerializer::WriteOddball(Oddball* oddball) {
|
||||
SerializationTag tag = SerializationTag::kUndefined;
|
||||
switch (oddball->kind()) {
|
||||
case Oddball::kUndefined:
|
||||
tag = SerializationTag::kUndefined;
|
||||
break;
|
||||
case Oddball::kFalse:
|
||||
tag = SerializationTag::kFalse;
|
||||
break;
|
||||
case Oddball::kTrue:
|
||||
tag = SerializationTag::kTrue;
|
||||
break;
|
||||
case Oddball::kNull:
|
||||
tag = SerializationTag::kNull;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
WriteTag(tag);
|
||||
}
|
||||
|
||||
ValueDeserializer::ValueDeserializer(Isolate* isolate,
|
||||
Vector<const uint8_t> data)
|
||||
: isolate_(isolate),
|
||||
position_(data.start()),
|
||||
end_(data.start() + data.length()) {}
|
||||
|
||||
ValueDeserializer::~ValueDeserializer() {}
|
||||
|
||||
Maybe<bool> ValueDeserializer::ReadHeader() {
|
||||
if (position_ < end_ &&
|
||||
*position_ == static_cast<uint8_t>(SerializationTag::kVersion)) {
|
||||
ReadTag().ToChecked();
|
||||
if (!ReadVarint<uint32_t>().To(&version_)) return Nothing<bool>();
|
||||
if (version_ > kLatestVersion) return Nothing<bool>();
|
||||
}
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
Maybe<SerializationTag> ValueDeserializer::ReadTag() {
|
||||
SerializationTag tag;
|
||||
do {
|
||||
if (position_ >= end_) return Nothing<SerializationTag>();
|
||||
tag = static_cast<SerializationTag>(*position_);
|
||||
position_++;
|
||||
} while (tag == SerializationTag::kPadding);
|
||||
return Just(tag);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Maybe<T> ValueDeserializer::ReadVarint() {
|
||||
// Reads an unsigned integer as a base-128 varint.
|
||||
// The number is written, 7 bits at a time, from the least significant to the
|
||||
// most significant 7 bits. Each byte, except the last, has the MSB set.
|
||||
// If the varint is larger than T, any more significant bits are discarded.
|
||||
// See also https://developers.google.com/protocol-buffers/docs/encoding
|
||||
static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value,
|
||||
"Only unsigned integer types can be read as varints.");
|
||||
T value = 0;
|
||||
unsigned shift = 0;
|
||||
bool has_another_byte;
|
||||
do {
|
||||
if (position_ >= end_) return Nothing<T>();
|
||||
uint8_t byte = *position_;
|
||||
if (V8_LIKELY(shift < sizeof(T) * 8)) {
|
||||
value |= (byte & 0x7f) << shift;
|
||||
shift += 7;
|
||||
}
|
||||
has_another_byte = byte & 0x80;
|
||||
position_++;
|
||||
} while (has_another_byte);
|
||||
return Just(value);
|
||||
}
|
||||
|
||||
MaybeHandle<Object> ValueDeserializer::ReadObject() {
|
||||
SerializationTag tag;
|
||||
if (!ReadTag().To(&tag)) return MaybeHandle<Object>();
|
||||
switch (tag) {
|
||||
case SerializationTag::kVerifyObjectCount:
|
||||
// Read the count and ignore it.
|
||||
if (ReadVarint<uint32_t>().IsNothing()) return MaybeHandle<Object>();
|
||||
return ReadObject();
|
||||
case SerializationTag::kUndefined:
|
||||
return isolate_->factory()->undefined_value();
|
||||
case SerializationTag::kNull:
|
||||
return isolate_->factory()->null_value();
|
||||
case SerializationTag::kTrue:
|
||||
return isolate_->factory()->true_value();
|
||||
case SerializationTag::kFalse:
|
||||
return isolate_->factory()->false_value();
|
||||
default:
|
||||
return MaybeHandle<Object>();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
101
src/value-serializer.h
Normal file
101
src/value-serializer.h
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright 2016 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef V8_VALUE_SERIALIZER_H_
|
||||
#define V8_VALUE_SERIALIZER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "include/v8.h"
|
||||
#include "src/base/compiler-specific.h"
|
||||
#include "src/base/macros.h"
|
||||
#include "src/vector.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
class Isolate;
|
||||
class Object;
|
||||
class Oddball;
|
||||
|
||||
enum class SerializationTag : uint8_t;
|
||||
|
||||
/**
|
||||
* Writes V8 objects in a binary format that allows the objects to be cloned
|
||||
* according to the HTML structured clone algorithm.
|
||||
*
|
||||
* Format is based on Blink's previous serialization logic.
|
||||
*/
|
||||
class ValueSerializer {
|
||||
public:
|
||||
ValueSerializer();
|
||||
~ValueSerializer();
|
||||
|
||||
/*
|
||||
* Writes out a header, which includes the format version.
|
||||
*/
|
||||
void WriteHeader();
|
||||
|
||||
/*
|
||||
* Serializes a V8 object into the buffer.
|
||||
*/
|
||||
Maybe<bool> WriteObject(Handle<Object> object) WARN_UNUSED_RESULT;
|
||||
|
||||
/*
|
||||
* Returns the stored data. This serializer should not be used once the buffer
|
||||
* is released. The contents are undefined if a previous write has failed.
|
||||
*/
|
||||
std::vector<uint8_t> ReleaseBuffer() { return std::move(buffer_); }
|
||||
|
||||
private:
|
||||
// Writing the wire format.
|
||||
void WriteTag(SerializationTag tag);
|
||||
template <typename T>
|
||||
void WriteVarint(T value);
|
||||
|
||||
// Writing V8 objects of various kinds.
|
||||
void WriteOddball(Oddball* oddball);
|
||||
|
||||
std::vector<uint8_t> buffer_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ValueSerializer);
|
||||
};
|
||||
|
||||
/*
|
||||
* Deserializes values from data written with ValueSerializer, or a compatible
|
||||
* implementation.
|
||||
*/
|
||||
class ValueDeserializer {
|
||||
public:
|
||||
ValueDeserializer(Isolate* isolate, Vector<const uint8_t> data);
|
||||
~ValueDeserializer();
|
||||
|
||||
/*
|
||||
* Runs version detection logic, which may fail if the format is invalid.
|
||||
*/
|
||||
Maybe<bool> ReadHeader() WARN_UNUSED_RESULT;
|
||||
|
||||
/*
|
||||
* Deserializes a V8 object from the buffer.
|
||||
*/
|
||||
MaybeHandle<Object> ReadObject() WARN_UNUSED_RESULT;
|
||||
|
||||
private:
|
||||
Maybe<SerializationTag> ReadTag() WARN_UNUSED_RESULT;
|
||||
template <typename T>
|
||||
Maybe<T> ReadVarint() WARN_UNUSED_RESULT;
|
||||
|
||||
Isolate* const isolate_;
|
||||
const uint8_t* position_;
|
||||
const uint8_t* const end_;
|
||||
uint32_t version_ = 0;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ValueDeserializer);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_VALUE_SERIALIZER_H_
|
@ -112,6 +112,7 @@
|
||||
'source-position-table-unittest.cc',
|
||||
'test-utils.h',
|
||||
'test-utils.cc',
|
||||
'value-serializer-unittest.cc',
|
||||
'wasm/asm-types-unittest.cc',
|
||||
'wasm/ast-decoder-unittest.cc',
|
||||
'wasm/control-transfer-unittest.cc',
|
||||
|
167
test/unittests/value-serializer-unittest.cc
Normal file
167
test/unittests/value-serializer-unittest.cc
Normal file
@ -0,0 +1,167 @@
|
||||
// Copyright 2016 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/value-serializer.h"
|
||||
#include "include/v8.h"
|
||||
#include "src/api.h"
|
||||
#include "test/unittests/test-utils.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace {
|
||||
|
||||
class ValueSerializerTest : public TestWithIsolate {
|
||||
protected:
|
||||
ValueSerializerTest()
|
||||
: serialization_context_(Context::New(isolate())),
|
||||
deserialization_context_(Context::New(isolate())) {}
|
||||
|
||||
const Local<Context>& serialization_context() {
|
||||
return serialization_context_;
|
||||
}
|
||||
const Local<Context>& deserialization_context() {
|
||||
return deserialization_context_;
|
||||
}
|
||||
|
||||
template <typename InputFunctor, typename OutputFunctor>
|
||||
void RoundTripTest(const InputFunctor& input_functor,
|
||||
const OutputFunctor& output_functor) {
|
||||
std::vector<uint8_t> data;
|
||||
{
|
||||
Context::Scope scope(serialization_context());
|
||||
TryCatch try_catch(isolate());
|
||||
// TODO(jbroman): Use the public API once it exists.
|
||||
Local<Value> input_value = input_functor();
|
||||
i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate());
|
||||
i::HandleScope handle_scope(internal_isolate);
|
||||
i::ValueSerializer serializer;
|
||||
serializer.WriteHeader();
|
||||
ASSERT_TRUE(serializer.WriteObject(Utils::OpenHandle(*input_value))
|
||||
.FromMaybe(false));
|
||||
ASSERT_FALSE(try_catch.HasCaught());
|
||||
data = serializer.ReleaseBuffer();
|
||||
}
|
||||
DecodeTest(data, output_functor);
|
||||
}
|
||||
|
||||
template <typename OutputFunctor>
|
||||
void DecodeTest(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())));
|
||||
ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false));
|
||||
Local<Value> result;
|
||||
ASSERT_TRUE(ToLocal<Value>(deserializer.ReadObject(), &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());
|
||||
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())));
|
||||
Maybe<bool> header_result = deserializer.ReadHeader();
|
||||
if (header_result.IsNothing()) return;
|
||||
ASSERT_TRUE(header_result.ToChecked());
|
||||
ASSERT_TRUE(deserializer.ReadObject().is_null());
|
||||
}
|
||||
|
||||
Local<Value> EvaluateScriptForInput(const char* utf8_source) {
|
||||
Local<String> source = StringFromUtf8(utf8_source);
|
||||
Local<Script> script =
|
||||
Script::Compile(serialization_context_, source).ToLocalChecked();
|
||||
return script->Run(serialization_context_).ToLocalChecked();
|
||||
}
|
||||
|
||||
bool EvaluateScriptForResultBool(const char* utf8_source) {
|
||||
Local<String> source = StringFromUtf8(utf8_source);
|
||||
Local<Script> script =
|
||||
Script::Compile(deserialization_context_, source).ToLocalChecked();
|
||||
Local<Value> value = script->Run(deserialization_context_).ToLocalChecked();
|
||||
return value->BooleanValue(deserialization_context_).FromJust();
|
||||
}
|
||||
|
||||
Local<String> StringFromUtf8(const char* source) {
|
||||
return String::NewFromUtf8(isolate(), source, NewStringType::kNormal)
|
||||
.ToLocalChecked();
|
||||
}
|
||||
|
||||
private:
|
||||
Local<Context> serialization_context_;
|
||||
Local<Context> deserialization_context_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ValueSerializerTest);
|
||||
};
|
||||
|
||||
TEST_F(ValueSerializerTest, DecodeInvalid) {
|
||||
// Version tag but no content.
|
||||
InvalidDecodeTest({0xff});
|
||||
// Version too large.
|
||||
InvalidDecodeTest({0xff, 0x7f, 0x5f});
|
||||
// Nonsense tag.
|
||||
InvalidDecodeTest({0xff, 0x09, 0xdd});
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTest, RoundTripOddball) {
|
||||
RoundTripTest([this]() { return Undefined(isolate()); },
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsUndefined()); });
|
||||
RoundTripTest([this]() { return True(isolate()); },
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsTrue()); });
|
||||
RoundTripTest([this]() { return False(isolate()); },
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsFalse()); });
|
||||
RoundTripTest([this]() { return Null(isolate()); },
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTest, DecodeOddball) {
|
||||
// What this code is expected to generate.
|
||||
DecodeTest({0xff, 0x09, 0x5f},
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsUndefined()); });
|
||||
DecodeTest({0xff, 0x09, 0x54},
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsTrue()); });
|
||||
DecodeTest({0xff, 0x09, 0x46},
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsFalse()); });
|
||||
DecodeTest({0xff, 0x09, 0x30},
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
|
||||
|
||||
// What v9 of the Blink code generates.
|
||||
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x5f, 0x00},
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsUndefined()); });
|
||||
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x54, 0x00},
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsTrue()); });
|
||||
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x46, 0x00},
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsFalse()); });
|
||||
DecodeTest({0xff, 0x09, 0x3f, 0x00, 0x30, 0x00},
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
|
||||
|
||||
// v0 (with no explicit version).
|
||||
DecodeTest({0x5f, 0x00},
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsUndefined()); });
|
||||
DecodeTest({0x54, 0x00},
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsTrue()); });
|
||||
DecodeTest({0x46, 0x00},
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsFalse()); });
|
||||
DecodeTest({0x30, 0x00},
|
||||
[](Local<Value> value) { EXPECT_TRUE(value->IsNull()); });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace v8
|
Loading…
Reference in New Issue
Block a user