Commit 58cac650 authored by jbroman's avatar jbroman Committed by Commit bot

Add an experimental public API for value serialization.

Suitably scary warnings attached, as this will yet evolve (notably to handle
host objects, which are not currently handled).

Unit tests adjusted to use the public version of ValueSerializer, eliminating
any need they have to access v8::internal.

With this, Blink can begin using this code experimentally behind a flag as it
develops.

BUG=chromium:148757

Review-Url: https://codereview.chromium.org/2274693002
Cr-Commit-Position: refs/heads/master@{#38915}
parent b7f796a6
......@@ -1659,6 +1659,91 @@ class V8_EXPORT JSON {
Local<String> gap = Local<String>());
};
/**
* Value serialization compatible with the HTML structured clone algorithm.
* The format is backward-compatible (i.e. safe to store to disk).
*
* WARNING: This API is under development, and changes (including incompatible
* changes to the API or wire format) may occur without notice until this
* warning is removed.
*/
class V8_EXPORT ValueSerializer {
public:
explicit ValueSerializer(Isolate* isolate);
~ValueSerializer();
/*
* Writes out a header, which includes the format version.
*/
void WriteHeader();
/*
* Serializes a JavaScript value into the buffer.
*/
V8_WARN_UNUSED_RESULT Maybe<bool> WriteValue(Local<Context> context,
Local<Value> value);
/*
* 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();
private:
ValueSerializer(const ValueSerializer&) = delete;
void operator=(const ValueSerializer&) = delete;
struct PrivateData;
PrivateData* private_;
};
/**
* Deserializes values from data written with ValueSerializer, or a compatible
* implementation.
*
* WARNING: This API is under development, and changes (including incompatible
* changes to the API or wire format) may occur without notice until this
* warning is removed.
*/
class V8_EXPORT ValueDeserializer {
public:
ValueDeserializer(Isolate* isolate, const uint8_t* data, size_t size);
~ValueDeserializer();
/*
* Reads and validates a header (including the format version).
* May, for example, reject an invalid or unsupported wire format.
*/
V8_WARN_UNUSED_RESULT Maybe<bool> ReadHeader();
/*
* Deserializes a JavaScript value from the buffer.
*/
V8_WARN_UNUSED_RESULT MaybeLocal<Value> ReadValue(Local<Context> context);
/*
* Must be called before ReadHeader to enable support for reading the legacy
* wire format (i.e., which predates this being shipped).
*
* Don't use this unless you need to read data written by previous versions of
* blink::ScriptValueSerializer.
*/
void SetSupportsLegacyWireFormat(bool supports_legacy_wire_format);
/*
* Reads the underlying wire format version. Likely mostly to be useful to
* legacy code reading old wire format versions. Must be called after
* ReadHeader.
*/
uint32_t GetWireFormatVersion() const;
private:
ValueDeserializer(const ValueDeserializer&) = delete;
void operator=(const ValueDeserializer&) = delete;
struct PrivateData;
PrivateData* private_;
};
/**
* A map whose keys are referenced weakly. It is similar to JavaScript WeakMap
......
......@@ -24,6 +24,7 @@
#include "src/base/functional.h"
#include "src/base/platform/platform.h"
#include "src/base/platform/time.h"
#include "src/base/safe_conversions.h"
#include "src/base/utils/random-number-generator.h"
#include "src/bootstrapper.h"
#include "src/char-predicates-inl.h"
......@@ -68,6 +69,7 @@
#include "src/unicode-inl.h"
#include "src/v8.h"
#include "src/v8threads.h"
#include "src/value-serializer.h"
#include "src/version.h"
#include "src/vm-state-inl.h"
#include "src/wasm/wasm-module.h"
......@@ -2832,6 +2834,101 @@ MaybeLocal<String> JSON::Stringify(Local<Context> context,
RETURN_ESCAPED(result);
}
// --- V a l u e S e r i a l i z a t i o n ---
struct ValueSerializer::PrivateData {
explicit PrivateData(i::Isolate* i) : isolate(i), serializer(i) {}
i::Isolate* isolate;
i::ValueSerializer serializer;
};
ValueSerializer::ValueSerializer(Isolate* isolate)
: private_(new PrivateData(reinterpret_cast<i::Isolate*>(isolate))) {}
ValueSerializer::~ValueSerializer() { delete private_; }
void ValueSerializer::WriteHeader() { private_->serializer.WriteHeader(); }
Maybe<bool> ValueSerializer::WriteValue(Local<Context> context,
Local<Value> value) {
PREPARE_FOR_EXECUTION_PRIMITIVE(context, ValueSerializer, WriteValue, bool);
i::Handle<i::Object> object = Utils::OpenHandle(*value);
Maybe<bool> result = private_->serializer.WriteObject(object);
if (result.IsNothing()) {
has_pending_exception = private_->isolate->has_pending_exception();
RETURN_ON_FAILED_EXECUTION_PRIMITIVE(bool);
}
return result;
}
std::vector<uint8_t> ValueSerializer::ReleaseBuffer() {
return private_->serializer.ReleaseBuffer();
}
struct ValueDeserializer::PrivateData {
PrivateData(i::Isolate* i, i::Vector<const uint8_t> data)
: isolate(i), deserializer(i, data) {}
i::Isolate* isolate;
i::ValueDeserializer deserializer;
bool supports_legacy_wire_format = false;
};
ValueDeserializer::ValueDeserializer(Isolate* isolate, const uint8_t* data,
size_t size) {
if (base::IsValueInRangeForNumericType<int>(size)) {
private_ =
new PrivateData(reinterpret_cast<i::Isolate*>(isolate),
i::Vector<const uint8_t>(data, static_cast<int>(size)));
} else {
private_ = nullptr;
}
}
ValueDeserializer::~ValueDeserializer() { delete private_; }
Maybe<bool> ValueDeserializer::ReadHeader() {
// TODO(jbroman): Today, all wire formats are "legacy". When a more supported
// format is added, compare the version of the internal serializer to the
// minimum non-legacy version number.
if (!private_ || !private_->deserializer.ReadHeader().FromMaybe(false) ||
!private_->supports_legacy_wire_format) {
delete private_;
private_ = nullptr;
return Nothing<bool>();
}
return Just(true);
}
void ValueDeserializer::SetSupportsLegacyWireFormat(
bool supports_legacy_wire_format) {
if (!private_) return;
private_->supports_legacy_wire_format = supports_legacy_wire_format;
}
uint32_t ValueDeserializer::GetWireFormatVersion() const {
CHECK(private_);
return private_->deserializer.GetWireFormatVersion();
}
MaybeLocal<Value> ValueDeserializer::ReadValue(Local<Context> context) {
CHECK(private_);
PREPARE_FOR_EXECUTION(context, ValueDeserializer, ReadValue, Value);
i::MaybeHandle<i::Object> result;
if (GetWireFormatVersion() > 0) {
result = private_->deserializer.ReadObject();
} else {
result =
private_->deserializer.ReadObjectUsingEntireBufferForLegacyFormat();
}
Local<Value> value;
if (!ToLocal(result, &value)) {
has_pending_exception = private_->isolate->has_pending_exception();
RETURN_ON_FAILED_EXECUTION(Value);
return MaybeLocal<Value>();
}
RETURN_ESCAPED(value);
}
// --- D a t a ---
bool Value::FullIsUndefined() const {
......
......@@ -657,7 +657,9 @@ class RuntimeCallTimer {
V(UnboundScript_GetName) \
V(UnboundScript_GetSourceMappingURL) \
V(UnboundScript_GetSourceURL) \
V(Value_TypeOf)
V(Value_TypeOf) \
V(ValueDeserializer_ReadValue) \
V(ValueSerializer_WriteValue)
#define FOR_EACH_MANUAL_COUNTER(V) \
V(AccessorGetterCallback) \
......
......@@ -122,6 +122,13 @@ class ValueDeserializer {
*/
Maybe<bool> ReadHeader() WARN_UNUSED_RESULT;
/*
* Reads the underlying wire format version. Likely mostly to be useful to
* legacy code reading old wire format versions. Must be called after
* ReadHeader.
*/
uint32_t GetWireFormatVersion() const { return version_; }
/*
* Deserializes a V8 object from the buffer.
*/
......
......@@ -47,19 +47,13 @@ class ValueSerializerTest : public TestWithIsolate {
}
Maybe<std::vector<uint8_t>> DoEncode(Local<Value> value) {
// This approximates what the API implementation would do.
// 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::ValueSerializer serializer(internal_isolate);
Local<Context> context = serialization_context();
ValueSerializer serializer(isolate());
serializer.WriteHeader();
if (serializer.WriteObject(Utils::OpenHandle(*value)).FromMaybe(false)) {
return Just(serializer.ReleaseBuffer());
if (!serializer.WriteValue(context, value).FromMaybe(false)) {
return Nothing<std::vector<uint8_t>>();
}
if (internal_isolate->has_pending_exception()) {
internal_isolate->OptionalRescheduleException(true);
}
return Nothing<std::vector<uint8_t>>();
return Just(serializer.ReleaseBuffer());
}
template <typename InputFunctor, typename EncodedDataFunctor>
......@@ -90,24 +84,21 @@ class ValueSerializerTest : public TestWithIsolate {
template <typename OutputFunctor>
void DecodeTest(const std::vector<uint8_t>& data,
const OutputFunctor& output_functor) {
Context::Scope scope(deserialization_context());
Local<Context> context = deserialization_context();
Context::Scope scope(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())));
ValueDeserializer deserializer(isolate(), &data[0],
static_cast<int>(data.size()));
deserializer.SetSupportsLegacyWireFormat(true);
ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false));
Local<Value> result;
ASSERT_TRUE(ToLocal<Value>(deserializer.ReadObject(), &result));
ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result));
ASSERT_FALSE(result.IsEmpty());
ASSERT_FALSE(try_catch.HasCaught());
ASSERT_TRUE(deserialization_context()
->Global()
->CreateDataProperty(deserialization_context_,
StringFromUtf8("result"), result)
.FromMaybe(false));
ASSERT_TRUE(
context->Global()
->CreateDataProperty(context, StringFromUtf8("result"), result)
.FromMaybe(false));
output_functor(result);
ASSERT_FALSE(try_catch.HasCaught());
}
......@@ -115,43 +106,37 @@ class ValueSerializerTest : public TestWithIsolate {
template <typename OutputFunctor>
void DecodeTestForVersion0(const std::vector<uint8_t>& data,
const OutputFunctor& output_functor) {
Context::Scope scope(deserialization_context());
Local<Context> context = deserialization_context();
Context::Scope scope(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.
ValueDeserializer deserializer(isolate(), &data[0],
static_cast<int>(data.size()));
deserializer.SetSupportsLegacyWireFormat(true);
ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false));
// TODO(jbroman): Check version 0.
ASSERT_EQ(0, deserializer.GetWireFormatVersion());
Local<Value> result;
ASSERT_TRUE(ToLocal<Value>(
deserializer.ReadObjectUsingEntireBufferForLegacyFormat(), &result));
ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result));
ASSERT_FALSE(result.IsEmpty());
ASSERT_FALSE(try_catch.HasCaught());
ASSERT_TRUE(deserialization_context()
->Global()
->CreateDataProperty(deserialization_context_,
StringFromUtf8("result"), result)
.FromMaybe(false));
ASSERT_TRUE(
context->Global()
->CreateDataProperty(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());
Local<Context> context = deserialization_context();
Context::Scope scope(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())));
ValueDeserializer deserializer(isolate(), &data[0],
static_cast<int>(data.size()));
deserializer.SetSupportsLegacyWireFormat(true);
Maybe<bool> header_result = deserializer.ReadHeader();
if (header_result.IsNothing()) return;
ASSERT_TRUE(header_result.ToChecked());
ASSERT_TRUE(deserializer.ReadObject().is_null());
ASSERT_TRUE(deserializer.ReadValue(context).IsEmpty());
}
Local<Value> EvaluateScriptForInput(const char* utf8_source) {
......
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