Blink-compatible serialization of ArrayBuffer transfer.
The embedder is expected to arrange for the array buffer contents to be transferred into a v8::ArrayBuffer in the receiving context (generally by assuming ownership of the externalized backing store). BUG=chromium:148757 Review-Url: https://codereview.chromium.org/2275033003 Cr-Commit-Position: refs/heads/master@{#38948}
This commit is contained in:
parent
7a57dd1540
commit
864728085d
16
include/v8.h
16
include/v8.h
@ -70,6 +70,7 @@ namespace v8 {
|
||||
|
||||
class AccessorSignature;
|
||||
class Array;
|
||||
class ArrayBuffer;
|
||||
class Boolean;
|
||||
class BooleanObject;
|
||||
class Context;
|
||||
@ -1689,6 +1690,14 @@ class V8_EXPORT ValueSerializer {
|
||||
*/
|
||||
std::vector<uint8_t> ReleaseBuffer();
|
||||
|
||||
/*
|
||||
* Marks an ArrayBuffer as havings its contents transferred out of band.
|
||||
* Pass the corresponding JSArrayBuffer in the deserializing context to
|
||||
* ValueDeserializer::TransferArrayBuffer.
|
||||
*/
|
||||
void TransferArrayBuffer(uint32_t transfer_id,
|
||||
Local<ArrayBuffer> array_buffer);
|
||||
|
||||
private:
|
||||
ValueSerializer(const ValueSerializer&) = delete;
|
||||
void operator=(const ValueSerializer&) = delete;
|
||||
@ -1721,6 +1730,13 @@ class V8_EXPORT ValueDeserializer {
|
||||
*/
|
||||
V8_WARN_UNUSED_RESULT MaybeLocal<Value> ReadValue(Local<Context> context);
|
||||
|
||||
/*
|
||||
* Accepts the array buffer corresponding to the one passed previously to
|
||||
* ValueSerializer::TransferArrayBuffer.
|
||||
*/
|
||||
void TransferArrayBuffer(uint32_t transfer_id,
|
||||
Local<ArrayBuffer> array_buffer);
|
||||
|
||||
/*
|
||||
* Must be called before ReadHeader to enable support for reading the legacy
|
||||
* wire format (i.e., which predates this being shipped).
|
||||
|
12
src/api.cc
12
src/api.cc
@ -2865,6 +2865,12 @@ std::vector<uint8_t> ValueSerializer::ReleaseBuffer() {
|
||||
return private_->serializer.ReleaseBuffer();
|
||||
}
|
||||
|
||||
void ValueSerializer::TransferArrayBuffer(uint32_t transfer_id,
|
||||
Local<ArrayBuffer> array_buffer) {
|
||||
private_->serializer.TransferArrayBuffer(transfer_id,
|
||||
Utils::OpenHandle(*array_buffer));
|
||||
}
|
||||
|
||||
struct ValueDeserializer::PrivateData {
|
||||
PrivateData(i::Isolate* i, i::Vector<const uint8_t> data)
|
||||
: isolate(i), deserializer(i, data) {}
|
||||
@ -2929,6 +2935,12 @@ MaybeLocal<Value> ValueDeserializer::ReadValue(Local<Context> context) {
|
||||
RETURN_ESCAPED(value);
|
||||
}
|
||||
|
||||
void ValueDeserializer::TransferArrayBuffer(uint32_t transfer_id,
|
||||
Local<ArrayBuffer> array_buffer) {
|
||||
private_->deserializer.TransferArrayBuffer(transfer_id,
|
||||
Utils::OpenHandle(*array_buffer));
|
||||
}
|
||||
|
||||
// --- D a t a ---
|
||||
|
||||
bool Value::FullIsUndefined() const {
|
||||
|
@ -92,12 +92,15 @@ enum class SerializationTag : uint8_t {
|
||||
kEndJSSet = ',',
|
||||
// Array buffer. byteLength:uint32_t, then raw data.
|
||||
kArrayBuffer = 'B',
|
||||
// Array buffer (transferred). transferID:uint32_t
|
||||
kArrayBufferTransfer = 't',
|
||||
};
|
||||
|
||||
ValueSerializer::ValueSerializer(Isolate* isolate)
|
||||
: isolate_(isolate),
|
||||
zone_(isolate->allocator()),
|
||||
id_map_(isolate->heap(), &zone_) {}
|
||||
id_map_(isolate->heap(), &zone_),
|
||||
array_buffer_transfer_map_(isolate->heap(), &zone_) {}
|
||||
|
||||
ValueSerializer::~ValueSerializer() {}
|
||||
|
||||
@ -172,6 +175,12 @@ uint8_t* ValueSerializer::ReserveRawBytes(size_t bytes) {
|
||||
return &buffer_[old_size];
|
||||
}
|
||||
|
||||
void ValueSerializer::TransferArrayBuffer(uint32_t transfer_id,
|
||||
Handle<JSArrayBuffer> array_buffer) {
|
||||
DCHECK(!array_buffer_transfer_map_.Find(array_buffer));
|
||||
array_buffer_transfer_map_.Set(array_buffer, transfer_id);
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteObject(Handle<Object> object) {
|
||||
if (object->IsSmi()) {
|
||||
WriteSmi(Smi::cast(*object));
|
||||
@ -500,6 +509,14 @@ Maybe<bool> ValueSerializer::WriteJSSet(Handle<JSSet> set) {
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::WriteJSArrayBuffer(JSArrayBuffer* array_buffer) {
|
||||
uint32_t* transfer_entry = array_buffer_transfer_map_.Find(array_buffer);
|
||||
if (transfer_entry) {
|
||||
DCHECK(array_buffer->was_neutered());
|
||||
WriteTag(SerializationTag::kArrayBufferTransfer);
|
||||
WriteVarint(*transfer_entry);
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
if (array_buffer->was_neutered()) return Nothing<bool>();
|
||||
double byte_length = array_buffer->byte_length()->Number();
|
||||
if (byte_length > std::numeric_limits<uint32_t>::max()) {
|
||||
@ -550,6 +567,11 @@ ValueDeserializer::ValueDeserializer(Isolate* isolate,
|
||||
|
||||
ValueDeserializer::~ValueDeserializer() {
|
||||
GlobalHandles::Destroy(Handle<Object>::cast(id_map_).location());
|
||||
|
||||
Handle<Object> transfer_map_handle;
|
||||
if (array_buffer_transfer_map_.ToHandle(&transfer_map_handle)) {
|
||||
GlobalHandles::Destroy(transfer_map_handle.location());
|
||||
}
|
||||
}
|
||||
|
||||
Maybe<bool> ValueDeserializer::ReadHeader() {
|
||||
@ -645,6 +667,26 @@ Maybe<Vector<const uint8_t>> ValueDeserializer::ReadRawBytes(int size) {
|
||||
return Just(Vector<const uint8_t>(start, size));
|
||||
}
|
||||
|
||||
void ValueDeserializer::TransferArrayBuffer(
|
||||
uint32_t transfer_id, Handle<JSArrayBuffer> array_buffer) {
|
||||
if (array_buffer_transfer_map_.is_null()) {
|
||||
array_buffer_transfer_map_ =
|
||||
Handle<SeededNumberDictionary>::cast(isolate_->global_handles()->Create(
|
||||
*SeededNumberDictionary::New(isolate_, 0)));
|
||||
}
|
||||
Handle<SeededNumberDictionary> dictionary =
|
||||
array_buffer_transfer_map_.ToHandleChecked();
|
||||
const bool used_as_prototype = false;
|
||||
Handle<SeededNumberDictionary> new_dictionary =
|
||||
SeededNumberDictionary::AtNumberPut(dictionary, transfer_id, array_buffer,
|
||||
used_as_prototype);
|
||||
if (!new_dictionary.is_identical_to(dictionary)) {
|
||||
GlobalHandles::Destroy(Handle<Object>::cast(dictionary).location());
|
||||
array_buffer_transfer_map_ = Handle<SeededNumberDictionary>::cast(
|
||||
isolate_->global_handles()->Create(*new_dictionary));
|
||||
}
|
||||
}
|
||||
|
||||
MaybeHandle<Object> ValueDeserializer::ReadObject() {
|
||||
SerializationTag tag;
|
||||
if (!ReadTag().To(&tag)) return MaybeHandle<Object>();
|
||||
@ -706,6 +748,8 @@ MaybeHandle<Object> ValueDeserializer::ReadObject() {
|
||||
return ReadJSSet();
|
||||
case SerializationTag::kArrayBuffer:
|
||||
return ReadJSArrayBuffer();
|
||||
case SerializationTag::kArrayBufferTransfer:
|
||||
return ReadTransferredJSArrayBuffer();
|
||||
default:
|
||||
return MaybeHandle<Object>();
|
||||
}
|
||||
@ -992,6 +1036,24 @@ MaybeHandle<JSArrayBuffer> ValueDeserializer::ReadJSArrayBuffer() {
|
||||
return array_buffer;
|
||||
}
|
||||
|
||||
MaybeHandle<JSArrayBuffer> ValueDeserializer::ReadTransferredJSArrayBuffer() {
|
||||
uint32_t id = next_id_++;
|
||||
uint32_t transfer_id;
|
||||
Handle<SeededNumberDictionary> transfer_map;
|
||||
if (!ReadVarint<uint32_t>().To(&transfer_id) ||
|
||||
!array_buffer_transfer_map_.ToHandle(&transfer_map)) {
|
||||
return MaybeHandle<JSArrayBuffer>();
|
||||
}
|
||||
int index = transfer_map->FindEntry(isolate_, transfer_id);
|
||||
if (index == SeededNumberDictionary::kNotFound) {
|
||||
return MaybeHandle<JSArrayBuffer>();
|
||||
}
|
||||
Handle<JSArrayBuffer> array_buffer(
|
||||
JSArrayBuffer::cast(transfer_map->ValueAt(index)), isolate_);
|
||||
AddObjectWithID(id, array_buffer);
|
||||
return array_buffer;
|
||||
}
|
||||
|
||||
Maybe<uint32_t> ValueDeserializer::ReadJSObjectProperties(
|
||||
Handle<JSObject> object, SerializationTag end_tag) {
|
||||
for (uint32_t num_properties = 0;; num_properties++) {
|
||||
|
@ -59,6 +59,14 @@ class ValueSerializer {
|
||||
*/
|
||||
std::vector<uint8_t> ReleaseBuffer() { return std::move(buffer_); }
|
||||
|
||||
/*
|
||||
* Marks an ArrayBuffer as havings its contents transferred out of band.
|
||||
* Pass the corresponding JSArrayBuffer in the deserializing context to
|
||||
* ValueDeserializer::TransferArrayBuffer.
|
||||
*/
|
||||
void TransferArrayBuffer(uint32_t transfer_id,
|
||||
Handle<JSArrayBuffer> array_buffer);
|
||||
|
||||
private:
|
||||
// Writing the wire format.
|
||||
void WriteTag(SerializationTag tag);
|
||||
@ -105,6 +113,9 @@ class ValueSerializer {
|
||||
IdentityMap<uint32_t> id_map_;
|
||||
uint32_t next_id_ = 0;
|
||||
|
||||
// A similar map, for transferred array buffers.
|
||||
IdentityMap<uint32_t> array_buffer_transfer_map_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ValueSerializer);
|
||||
};
|
||||
|
||||
@ -144,6 +155,13 @@ class ValueDeserializer {
|
||||
MaybeHandle<Object> ReadObjectUsingEntireBufferForLegacyFormat()
|
||||
WARN_UNUSED_RESULT;
|
||||
|
||||
/*
|
||||
* Accepts the array buffer corresponding to the one passed previously to
|
||||
* ValueSerializer::TransferArrayBuffer.
|
||||
*/
|
||||
void TransferArrayBuffer(uint32_t transfer_id,
|
||||
Handle<JSArrayBuffer> array_buffer);
|
||||
|
||||
private:
|
||||
// Reading the wire format.
|
||||
Maybe<SerializationTag> PeekTag() const WARN_UNUSED_RESULT;
|
||||
@ -169,6 +187,7 @@ class ValueDeserializer {
|
||||
MaybeHandle<JSMap> ReadJSMap() WARN_UNUSED_RESULT;
|
||||
MaybeHandle<JSSet> ReadJSSet() WARN_UNUSED_RESULT;
|
||||
MaybeHandle<JSArrayBuffer> ReadJSArrayBuffer() WARN_UNUSED_RESULT;
|
||||
MaybeHandle<JSArrayBuffer> ReadTransferredJSArrayBuffer() WARN_UNUSED_RESULT;
|
||||
|
||||
/*
|
||||
* Reads key-value pairs into the object until the specified end tag is
|
||||
@ -186,9 +205,12 @@ class ValueDeserializer {
|
||||
const uint8_t* position_;
|
||||
const uint8_t* const end_;
|
||||
uint32_t version_ = 0;
|
||||
Handle<SeededNumberDictionary> id_map_; // Always a global handle.
|
||||
uint32_t next_id_ = 0;
|
||||
|
||||
// Always global handles.
|
||||
Handle<SeededNumberDictionary> id_map_;
|
||||
MaybeHandle<SeededNumberDictionary> array_buffer_transfer_map_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ValueDeserializer);
|
||||
};
|
||||
|
||||
|
@ -29,6 +29,10 @@ class ValueSerializerTest : public TestWithIsolate {
|
||||
return deserialization_context_;
|
||||
}
|
||||
|
||||
// Overridden in more specific fixtures.
|
||||
virtual void BeforeEncode(ValueSerializer*) {}
|
||||
virtual void BeforeDecode(ValueDeserializer*) {}
|
||||
|
||||
template <typename InputFunctor, typename OutputFunctor>
|
||||
void RoundTripTest(const InputFunctor& input_functor,
|
||||
const OutputFunctor& output_functor) {
|
||||
@ -49,6 +53,7 @@ class ValueSerializerTest : public TestWithIsolate {
|
||||
Maybe<std::vector<uint8_t>> DoEncode(Local<Value> value) {
|
||||
Local<Context> context = serialization_context();
|
||||
ValueSerializer serializer(isolate());
|
||||
BeforeEncode(&serializer);
|
||||
serializer.WriteHeader();
|
||||
if (!serializer.WriteValue(context, value).FromMaybe(false)) {
|
||||
return Nothing<std::vector<uint8_t>>();
|
||||
@ -90,6 +95,7 @@ class ValueSerializerTest : public TestWithIsolate {
|
||||
ValueDeserializer deserializer(isolate(), &data[0],
|
||||
static_cast<int>(data.size()));
|
||||
deserializer.SetSupportsLegacyWireFormat(true);
|
||||
BeforeDecode(&deserializer);
|
||||
ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false));
|
||||
Local<Value> result;
|
||||
ASSERT_TRUE(deserializer.ReadValue(context).ToLocal(&result));
|
||||
@ -112,6 +118,7 @@ class ValueSerializerTest : public TestWithIsolate {
|
||||
ValueDeserializer deserializer(isolate(), &data[0],
|
||||
static_cast<int>(data.size()));
|
||||
deserializer.SetSupportsLegacyWireFormat(true);
|
||||
BeforeDecode(&deserializer);
|
||||
ASSERT_TRUE(deserializer.ReadHeader().FromMaybe(false));
|
||||
ASSERT_EQ(0, deserializer.GetWireFormatVersion());
|
||||
Local<Value> result;
|
||||
@ -133,6 +140,7 @@ class ValueSerializerTest : public TestWithIsolate {
|
||||
ValueDeserializer deserializer(isolate(), &data[0],
|
||||
static_cast<int>(data.size()));
|
||||
deserializer.SetSupportsLegacyWireFormat(true);
|
||||
BeforeDecode(&deserializer);
|
||||
Maybe<bool> header_result = deserializer.ReadHeader();
|
||||
if (header_result.IsNothing()) return;
|
||||
ASSERT_TRUE(header_result.ToChecked());
|
||||
@ -1597,5 +1605,74 @@ TEST_F(ValueSerializerTest, DecodeInvalidArrayBuffer) {
|
||||
InvalidDecodeTest({0xff, 0x09, 0x42, 0xff, 0xff, 0x00});
|
||||
}
|
||||
|
||||
// Includes an ArrayBuffer wrapper marked for transfer from the serialization
|
||||
// context to the deserialization context.
|
||||
class ValueSerializerTestWithArrayBufferTransfer : public ValueSerializerTest {
|
||||
protected:
|
||||
static const size_t kTestByteLength = 4;
|
||||
|
||||
ValueSerializerTestWithArrayBufferTransfer() {
|
||||
{
|
||||
Context::Scope scope(serialization_context());
|
||||
input_buffer_ = ArrayBuffer::New(isolate(), nullptr, 0);
|
||||
input_buffer_->Neuter();
|
||||
}
|
||||
{
|
||||
Context::Scope scope(deserialization_context());
|
||||
output_buffer_ = ArrayBuffer::New(isolate(), kTestByteLength);
|
||||
const uint8_t data[kTestByteLength] = {0x00, 0x01, 0x80, 0xff};
|
||||
memcpy(output_buffer_->GetContents().Data(), data, kTestByteLength);
|
||||
}
|
||||
}
|
||||
|
||||
const Local<ArrayBuffer>& input_buffer() { return input_buffer_; }
|
||||
const Local<ArrayBuffer>& output_buffer() { return output_buffer_; }
|
||||
|
||||
void BeforeEncode(ValueSerializer* serializer) override {
|
||||
serializer->TransferArrayBuffer(0, input_buffer_);
|
||||
}
|
||||
|
||||
void BeforeDecode(ValueDeserializer* deserializer) override {
|
||||
deserializer->TransferArrayBuffer(0, output_buffer_);
|
||||
}
|
||||
|
||||
private:
|
||||
Local<ArrayBuffer> input_buffer_;
|
||||
Local<ArrayBuffer> output_buffer_;
|
||||
};
|
||||
|
||||
TEST_F(ValueSerializerTestWithArrayBufferTransfer,
|
||||
RoundTripArrayBufferTransfer) {
|
||||
RoundTripTest([this]() { return input_buffer(); },
|
||||
[this](Local<Value> value) {
|
||||
ASSERT_TRUE(value->IsArrayBuffer());
|
||||
EXPECT_EQ(output_buffer(), value);
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool(
|
||||
"new Uint8Array(result).toString() === '0,1,128,255'"));
|
||||
});
|
||||
RoundTripTest(
|
||||
[this]() {
|
||||
Local<Object> object = Object::New(isolate());
|
||||
EXPECT_TRUE(object
|
||||
->CreateDataProperty(serialization_context(),
|
||||
StringFromUtf8("a"),
|
||||
input_buffer())
|
||||
.FromMaybe(false));
|
||||
EXPECT_TRUE(object
|
||||
->CreateDataProperty(serialization_context(),
|
||||
StringFromUtf8("b"),
|
||||
input_buffer())
|
||||
.FromMaybe(false));
|
||||
return object;
|
||||
},
|
||||
[this](Local<Value> value) {
|
||||
EXPECT_TRUE(
|
||||
EvaluateScriptForResultBool("result.a instanceof ArrayBuffer"));
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool("result.a === result.b"));
|
||||
EXPECT_TRUE(EvaluateScriptForResultBool(
|
||||
"new Uint8Array(result.a).toString() === '0,1,128,255'"));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace v8
|
||||
|
Loading…
Reference in New Issue
Block a user