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:
jbroman 2016-08-26 08:47:15 -07:00 committed by Commit bot
parent 7a57dd1540
commit 864728085d
5 changed files with 191 additions and 2 deletions

View File

@ -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).

View File

@ -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 {

View File

@ -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++) {

View File

@ -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);
};

View File

@ -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