1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 23:34:47 -05:00

feat(serde_v8): deserialize ArrayBuffers (#13436)

Previously we would only deserialize `ArrayBufferView`s as zero-copy bufs

This avoids rewrapping `ArrayBuffers` in `ArrayBufferView`s when implementing APIs that take [BufferSource](https://webidl.spec.whatwg.org/#BufferSource) args passed through the op-layer
This commit is contained in:
Aaron O'Mullan 2022-01-20 15:11:09 +01:00 committed by GitHub
parent 443d8fc41c
commit 1cc38f5155
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 112 additions and 42 deletions

View file

@ -134,9 +134,20 @@ impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de>
ValueType::ArrayBufferView => { ValueType::ArrayBufferView => {
v8::Local::<v8::ArrayBufferView>::try_from(self.input) v8::Local::<v8::ArrayBufferView>::try_from(self.input)
.and_then(|view| { .and_then(|view| {
magic::zero_copy_buf::ZeroCopyBuf::try_new(self.scope, view) magic::zero_copy_buf::ZeroCopyBuf::try_from((
&mut *self.scope,
view,
))
}) })
.map_err(|_| Error::ExpectedInteger) .map_err(|_| Error::ExpectedBuffer)
.and_then(|zb| visitor.visit_byte_buf(Vec::from(&*zb)))
}
ValueType::ArrayBuffer => {
v8::Local::<v8::ArrayBuffer>::try_from(self.input)
.and_then(|buffer| {
magic::zero_copy_buf::ZeroCopyBuf::try_from(buffer)
})
.map_err(|_| Error::ExpectedBuffer)
.and_then(|zb| visitor.visit_byte_buf(Vec::from(&*zb))) .and_then(|zb| visitor.visit_byte_buf(Vec::from(&*zb)))
} }
} }
@ -339,12 +350,20 @@ impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de>
// Magic Buffer // Magic Buffer
if name == magic::buffer::BUF_NAME { if name == magic::buffer::BUF_NAME {
let zero_copy_buf = let zero_copy_buf = match self.input.is_array_buffer() {
v8::Local::<v8::ArrayBufferView>::try_from(self.input) // ArrayBuffer
true => v8::Local::<v8::ArrayBuffer>::try_from(self.input)
.and_then(magic::zero_copy_buf::ZeroCopyBuf::try_from),
// maybe ArrayBufferView
false => v8::Local::<v8::ArrayBufferView>::try_from(self.input)
.and_then(|view| { .and_then(|view| {
magic::zero_copy_buf::ZeroCopyBuf::try_new(self.scope, view) magic::zero_copy_buf::ZeroCopyBuf::try_from((
}) &mut *self.scope,
.map_err(|_| Error::ExpectedArray)?; view,
))
}),
}
.map_err(|_| Error::ExpectedBuffer)?;
let data: [u8; 32] = unsafe { std::mem::transmute(zero_copy_buf) }; let data: [u8; 32] = unsafe { std::mem::transmute(zero_copy_buf) };
return visitor.visit_bytes(&data); return visitor.visit_bytes(&data);
} }

View file

@ -16,6 +16,7 @@ pub enum Error {
ExpectedMap, ExpectedMap,
ExpectedEnum, ExpectedEnum,
ExpectedObject, ExpectedObject,
ExpectedBuffer,
ExpectedUtf8, ExpectedUtf8,

View file

@ -1,5 +1,6 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use std::convert::TryFrom;
use std::fmt; use std::fmt;
use std::ops::Deref; use std::ops::Deref;
use std::ops::DerefMut; use std::ops::DerefMut;
@ -15,25 +16,32 @@ pub enum MagicBuffer {
} }
impl MagicBuffer { impl MagicBuffer {
pub fn new<'s>(
scope: &mut v8::HandleScope<'s>,
view: v8::Local<v8::ArrayBufferView>,
) -> Self {
Self::try_new(scope, view).unwrap()
}
pub fn try_new<'s>(
scope: &mut v8::HandleScope<'s>,
view: v8::Local<v8::ArrayBufferView>,
) -> Result<Self, v8::DataError> {
Ok(Self::FromV8(ZeroCopyBuf::try_new(scope, view)?))
}
pub fn empty() -> Self { pub fn empty() -> Self {
MagicBuffer::ToV8(Mutex::new(Some(vec![0_u8; 0].into_boxed_slice()))) MagicBuffer::ToV8(Mutex::new(Some(vec![0_u8; 0].into_boxed_slice())))
} }
} }
impl<'s> TryFrom<v8::Local<'s, v8::ArrayBuffer>> for MagicBuffer {
type Error = v8::DataError;
fn try_from(buffer: v8::Local<v8::ArrayBuffer>) -> Result<Self, Self::Error> {
Ok(Self::FromV8(ZeroCopyBuf::try_from(buffer)?))
}
}
// TODO(@AaronO): consider streamlining this as "ScopedValue" ?
type ScopedView<'a, 'b, 's> = (
&'s mut v8::HandleScope<'a>,
v8::Local<'b, v8::ArrayBufferView>,
);
impl<'a, 'b, 's> TryFrom<ScopedView<'a, 'b, 's>> for MagicBuffer {
type Error = v8::DataError;
fn try_from(
scoped_view: ScopedView<'a, 'b, 's>,
) -> Result<Self, Self::Error> {
Ok(Self::FromV8(ZeroCopyBuf::try_from(scoped_view)?))
}
}
impl Clone for MagicBuffer { impl Clone for MagicBuffer {
fn clone(&self) -> Self { fn clone(&self) -> Self {
match self { match self {

View file

@ -25,31 +25,46 @@ pub struct ZeroCopyBuf {
unsafe impl Send for ZeroCopyBuf {} unsafe impl Send for ZeroCopyBuf {}
impl ZeroCopyBuf { impl ZeroCopyBuf {
pub fn new<'s>( pub fn from_buffer(
scope: &mut v8::HandleScope<'s>, buffer: v8::Local<v8::ArrayBuffer>,
view: v8::Local<v8::ArrayBufferView>, byte_offset: usize,
) -> Self { byte_length: usize,
Self::try_new(scope, view).unwrap()
}
pub fn try_new<'s>(
scope: &mut v8::HandleScope<'s>,
view: v8::Local<v8::ArrayBufferView>,
) -> Result<Self, v8::DataError> { ) -> Result<Self, v8::DataError> {
let backing_store = view.buffer(scope).unwrap().get_backing_store(); let backing_store = buffer.get_backing_store();
if backing_store.is_shared() { match backing_store.is_shared() {
return Err(v8::DataError::BadType { true => Err(v8::DataError::BadType {
actual: "shared ArrayBufferView", actual: "shared ArrayBufferView",
expected: "non-shared ArrayBufferView", expected: "non-shared ArrayBufferView",
}); }),
false => Ok(Self {
backing_store,
byte_offset,
byte_length,
}),
} }
let byte_offset = view.byte_offset(); }
let byte_length = view.byte_length(); }
Ok(Self {
backing_store, impl<'s> TryFrom<v8::Local<'s, v8::ArrayBuffer>> for ZeroCopyBuf {
byte_offset, type Error = v8::DataError;
byte_length, fn try_from(buffer: v8::Local<v8::ArrayBuffer>) -> Result<Self, Self::Error> {
}) Self::from_buffer(buffer, 0, buffer.byte_length())
}
}
// TODO(@AaronO): consider streamlining this as "ScopedValue" ?
type ScopedView<'a, 'b, 's> = (
&'s mut v8::HandleScope<'a>,
v8::Local<'b, v8::ArrayBufferView>,
);
impl<'a, 'b, 's> TryFrom<ScopedView<'a, 'b, 's>> for ZeroCopyBuf {
type Error = v8::DataError;
fn try_from(
scoped_view: ScopedView<'a, 'b, 's>,
) -> Result<Self, Self::Error> {
let (scope, view) = scoped_view;
let buffer = view.buffer(scope).unwrap();
Self::from_buffer(buffer, view.byte_offset(), view.byte_length())
} }
} }

View file

@ -11,6 +11,7 @@ pub enum ValueType {
Number, Number,
String, String,
Array, Array,
ArrayBuffer,
ArrayBufferView, ArrayBufferView,
Object, Object,
} }
@ -25,6 +26,8 @@ impl ValueType {
return Self::String; return Self::String;
} else if v.is_array() { } else if v.is_array() {
return Self::Array; return Self::Array;
} else if v.is_array_buffer() {
return Self::ArrayBuffer;
} else if v.is_array_buffer_view() { } else if v.is_array_buffer_view() {
return Self::ArrayBufferView; return Self::ArrayBufferView;
} else if v.is_object() { } else if v.is_object() {

View file

@ -2,6 +2,7 @@
use serde::Deserialize; use serde::Deserialize;
use serde_v8::utils::{js_exec, v8_do}; use serde_v8::utils::{js_exec, v8_do};
use serde_v8::Buffer;
use serde_v8::Error; use serde_v8::Error;
#[derive(Debug, Deserialize, PartialEq)] #[derive(Debug, Deserialize, PartialEq)]
@ -192,6 +193,29 @@ fn de_string_or_buffer() {
); );
} }
#[test]
fn de_buffers() {
// ArrayBufferView
dedo("new Uint8Array([97])", |scope, v| {
let buf: Buffer = serde_v8::from_v8(scope, v).unwrap();
assert_eq!(&*buf, &[97]);
});
// ArrayBuffer
dedo("(new Uint8Array([97])).buffer", |scope, v| {
let buf: Buffer = serde_v8::from_v8(scope, v).unwrap();
assert_eq!(&*buf, &[97]);
});
dedo(
"(Uint8Array.from([0x68, 0x65, 0x6C, 0x6C, 0x6F]))",
|scope, v| {
let buf: Buffer = serde_v8::from_v8(scope, v).unwrap();
assert_eq!(&*buf, &[0x68, 0x65, 0x6C, 0x6C, 0x6F]);
},
);
}
//// ////
// JSON tests: serde_json::Value compatibility // JSON tests: serde_json::Value compatibility
//// ////