Commit e6d1a80e authored by jbroman's avatar jbroman Committed by Commit bot

Blink-compatible serialization of oddball values.

BUG=chromium:148757

Review-Url: https://codereview.chromium.org/2232243003
Cr-Commit-Position: refs/heads/master@{#38625}
parent bb9707c8
......@@ -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',
......
// 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
// 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',
......
// 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
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