diff --git a/BUILD.gn b/BUILD.gn index dc367a13e9..c61514e5dd 100644 --- a/BUILD.gn +++ b/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", diff --git a/src/v8.gyp b/src/v8.gyp index bfc91f2a93..f9c5e29b4f 100644 --- a/src/v8.gyp +++ b/src/v8.gyp @@ -1231,6 +1231,8 @@ 'v8memory.h', 'v8threads.cc', 'v8threads.h', + 'value-serializer.cc', + 'value-serializer.h', 'vector.h', 'version.cc', 'version.h', diff --git a/src/value-serializer.cc b/src/value-serializer.cc new file mode 100644 index 0000000000..77ee124bb4 --- /dev/null +++ b/src/value-serializer.cc @@ -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 + +#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(tag)); +} + +template +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::value && std::is_unsigned::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 ValueSerializer::WriteObject(Handle 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(); + } +} + +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 data) + : isolate_(isolate), + position_(data.start()), + end_(data.start() + data.length()) {} + +ValueDeserializer::~ValueDeserializer() {} + +Maybe ValueDeserializer::ReadHeader() { + if (position_ < end_ && + *position_ == static_cast(SerializationTag::kVersion)) { + ReadTag().ToChecked(); + if (!ReadVarint().To(&version_)) return Nothing(); + if (version_ > kLatestVersion) return Nothing(); + } + return Just(true); +} + +Maybe ValueDeserializer::ReadTag() { + SerializationTag tag; + do { + if (position_ >= end_) return Nothing(); + tag = static_cast(*position_); + position_++; + } while (tag == SerializationTag::kPadding); + return Just(tag); +} + +template +Maybe 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::value && std::is_unsigned::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(); + 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 ValueDeserializer::ReadObject() { + SerializationTag tag; + if (!ReadTag().To(&tag)) return MaybeHandle(); + switch (tag) { + case SerializationTag::kVerifyObjectCount: + // Read the count and ignore it. + if (ReadVarint().IsNothing()) return MaybeHandle(); + 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(); + } +} + +} // namespace internal +} // namespace v8 diff --git a/src/value-serializer.h b/src/value-serializer.h new file mode 100644 index 0000000000..d3da52981b --- /dev/null +++ b/src/value-serializer.h @@ -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 +#include + +#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 WriteObject(Handle 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 ReleaseBuffer() { return std::move(buffer_); } + + private: + // Writing the wire format. + void WriteTag(SerializationTag tag); + template + void WriteVarint(T value); + + // Writing V8 objects of various kinds. + void WriteOddball(Oddball* oddball); + + std::vector buffer_; + + DISALLOW_COPY_AND_ASSIGN(ValueSerializer); +}; + +/* + * Deserializes values from data written with ValueSerializer, or a compatible + * implementation. + */ +class ValueDeserializer { + public: + ValueDeserializer(Isolate* isolate, Vector data); + ~ValueDeserializer(); + + /* + * Runs version detection logic, which may fail if the format is invalid. + */ + Maybe ReadHeader() WARN_UNUSED_RESULT; + + /* + * Deserializes a V8 object from the buffer. + */ + MaybeHandle ReadObject() WARN_UNUSED_RESULT; + + private: + Maybe ReadTag() WARN_UNUSED_RESULT; + template + Maybe 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_ diff --git a/test/unittests/unittests.gyp b/test/unittests/unittests.gyp index ccd3c41c96..0ea8b9a43d 100644 --- a/test/unittests/unittests.gyp +++ b/test/unittests/unittests.gyp @@ -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', diff --git a/test/unittests/value-serializer-unittest.cc b/test/unittests/value-serializer-unittest.cc new file mode 100644 index 0000000000..1bacdececd --- /dev/null +++ b/test/unittests/value-serializer-unittest.cc @@ -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& serialization_context() { + return serialization_context_; + } + const Local& deserialization_context() { + return deserialization_context_; + } + + template + void RoundTripTest(const InputFunctor& input_functor, + const OutputFunctor& output_functor) { + std::vector data; + { + Context::Scope scope(serialization_context()); + TryCatch try_catch(isolate()); + // TODO(jbroman): Use the public API once it exists. + Local input_value = input_functor(); + i::Isolate* internal_isolate = reinterpret_cast(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 + void DecodeTest(const std::vector& 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(isolate()); + i::HandleScope handle_scope(internal_isolate); + i::ValueDeserializer deserializer( + internal_isolate, + i::Vector(&data[0], static_cast(data.size()))); + ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false)); + Local result; + ASSERT_TRUE(ToLocal(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& data) { + Context::Scope scope(deserialization_context()); + TryCatch try_catch(isolate()); + i::Isolate* internal_isolate = reinterpret_cast(isolate()); + i::HandleScope handle_scope(internal_isolate); + i::ValueDeserializer deserializer( + internal_isolate, + i::Vector(&data[0], static_cast(data.size()))); + Maybe header_result = deserializer.ReadHeader(); + if (header_result.IsNothing()) return; + ASSERT_TRUE(header_result.ToChecked()); + ASSERT_TRUE(deserializer.ReadObject().is_null()); + } + + Local EvaluateScriptForInput(const char* utf8_source) { + Local source = StringFromUtf8(utf8_source); + Local