diff --git a/core/runtime.rs b/core/runtime.rs index bea9908ed8..cb8b242e98 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -3051,6 +3051,82 @@ assertEquals(1, notify_return_value); assert!(r.open(scope).is_undefined()); } + #[test] + fn test_op_detached_buffer() { + use serde_v8::DetachedBuffer; + + #[op] + fn op_sum_take(b: DetachedBuffer) -> Result { + Ok(b.as_ref().iter().clone().map(|x| *x as u64).sum()) + } + + #[op] + fn op_boomerang( + b: DetachedBuffer, + ) -> Result { + Ok(b) + } + + let ext = Extension::builder() + .ops(vec![op_sum_take::decl(), op_boomerang::decl()]) + .build(); + + let mut runtime = JsRuntime::new(RuntimeOptions { + extensions: vec![ext], + ..Default::default() + }); + + runtime + .execute_script( + "test.js", + r#" + const a1 = new Uint8Array([1,2,3]); + const a1b = a1.subarray(0, 3); + const a2 = new Uint8Array([5,10,15]); + const a2b = a2.subarray(0, 3); + + + if (!(a1.length > 0 && a1b.length > 0)) { + throw new Error("a1 & a1b should have a length"); + } + let sum = Deno.core.opSync('op_sum_take', a1b); + if (sum !== 6) { + throw new Error(`Bad sum: ${sum}`); + } + if (a1.length > 0 || a1b.length > 0) { + throw new Error("expecting a1 & a1b to be detached"); + } + + const a3 = Deno.core.opSync('op_boomerang', a2b); + if (a3.byteLength != 3) { + throw new Error(`Expected a3.byteLength === 3, got ${a3.byteLength}`); + } + if (a3[0] !== 5 || a3[1] !== 10) { + throw new Error(`Invalid a3: ${a3[0]}, ${a3[1]}`); + } + if (a2.byteLength > 0 || a2b.byteLength > 0) { + throw new Error("expecting a2 & a2b to be detached, a3 re-attached"); + } + + const wmem = new WebAssembly.Memory({ initial: 1, maximum: 2 }); + const w32 = new Uint32Array(wmem.buffer); + w32[0] = 1; w32[1] = 2; w32[2] = 3; + const assertWasmThrow = (() => { + try { + let sum = Deno.core.opSync('op_sum_take', w32.subarray(0, 2)); + return false; + } catch(e) { + return e.message.includes('ExpectedDetachable'); + } + }); + if (!assertWasmThrow()) { + throw new Error("expected wasm mem to not be detachable"); + } + "#, + ) + .unwrap(); + } + #[test] fn test_op_unstable_disabling() { #[op] diff --git a/serde_v8/de.rs b/serde_v8/de.rs index 002b741cf3..f5024e5eab 100644 --- a/serde_v8/de.rs +++ b/serde_v8/de.rs @@ -7,7 +7,7 @@ use crate::keys::{v8_struct_key, KeyCache}; use crate::magic::transl8::FromV8; use crate::magic::transl8::{visit_magic, MagicType}; use crate::payload::ValueType; -use crate::{magic, Buffer, ByteString, U16String}; +use crate::{magic, Buffer, ByteString, DetachedBuffer, U16String}; pub struct Deserializer<'a, 'b, 's> { input: v8::Local<'a, v8::Value>, @@ -328,6 +328,9 @@ impl<'de, 'a, 'b, 's, 'x> de::Deserializer<'de> Buffer::MAGIC_NAME => { visit_magic(visitor, Buffer::from_v8(self.scope, self.input)?) } + DetachedBuffer::MAGIC_NAME => { + visit_magic(visitor, DetachedBuffer::from_v8(self.scope, self.input)?) + } ByteString::MAGIC_NAME => { visit_magic(visitor, ByteString::from_v8(self.scope, self.input)?) } diff --git a/serde_v8/error.rs b/serde_v8/error.rs index e4f7f363be..cf66f8e860 100644 --- a/serde_v8/error.rs +++ b/serde_v8/error.rs @@ -17,6 +17,7 @@ pub enum Error { ExpectedEnum, ExpectedObject, ExpectedBuffer, + ExpectedDetachable, ExpectedUtf8, ExpectedLatin1, diff --git a/serde_v8/lib.rs b/serde_v8/lib.rs index 203e1d0044..945138384b 100644 --- a/serde_v8/lib.rs +++ b/serde_v8/lib.rs @@ -13,6 +13,7 @@ pub use error::{Error, Result}; pub use keys::KeyCache; pub use magic::buffer::MagicBuffer as Buffer; pub use magic::bytestring::ByteString; +pub use magic::detached_buffer::DetachedBuffer; pub use magic::string_or_buffer::StringOrBuffer; pub use magic::u16string::U16String; pub use magic::Value; diff --git a/serde_v8/magic/detached_buffer.rs b/serde_v8/magic/detached_buffer.rs new file mode 100644 index 0000000000..1435a0fa6a --- /dev/null +++ b/serde_v8/magic/detached_buffer.rs @@ -0,0 +1,69 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use core::ops::Range; +use std::ops::Deref; +use std::ops::DerefMut; + +use super::transl8::FromV8; +use super::transl8::ToV8; +use super::zero_copy_buf::to_ranged_buffer; +use super::zero_copy_buf::ZeroCopyBuf; +use crate::magic::transl8::impl_magic; + +// A buffer that detaches when deserialized from JS +pub struct DetachedBuffer(ZeroCopyBuf); +impl_magic!(DetachedBuffer); + +impl AsRef<[u8]> for DetachedBuffer { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl AsMut<[u8]> for DetachedBuffer { + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut() + } +} + +impl Deref for DetachedBuffer { + type Target = [u8]; + fn deref(&self) -> &[u8] { + self.0.deref() + } +} + +impl DerefMut for DetachedBuffer { + fn deref_mut(&mut self) -> &mut [u8] { + self.0.deref_mut() + } +} + +impl ToV8 for DetachedBuffer { + fn to_v8<'a>( + &self, + scope: &mut v8::HandleScope<'a>, + ) -> Result, crate::Error> { + let buffer = v8::ArrayBuffer::with_backing_store(scope, &self.0.store); + let Range { start, end } = self.0.range; + let (off, len) = (start, end - start); + let v = v8::Uint8Array::new(scope, buffer, off, len).unwrap(); + Ok(v.into()) + } +} + +impl FromV8 for DetachedBuffer { + fn from_v8( + scope: &mut v8::HandleScope, + value: v8::Local, + ) -> Result { + let (b, range) = + to_ranged_buffer(scope, value).or(Err(crate::Error::ExpectedBuffer))?; + if !b.is_detachable() { + return Err(crate::Error::ExpectedDetachable); + } + let store = b.get_backing_store(); + b.detach(); // Detach + Ok(Self(ZeroCopyBuf { store, range })) + } +} diff --git a/serde_v8/magic/mod.rs b/serde_v8/magic/mod.rs index bc86c6a7c9..4f5398bdaf 100644 --- a/serde_v8/magic/mod.rs +++ b/serde_v8/magic/mod.rs @@ -1,6 +1,7 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. pub mod buffer; pub mod bytestring; +pub mod detached_buffer; pub mod string_or_buffer; pub mod transl8; pub mod u16string; diff --git a/serde_v8/magic/zero_copy_buf.rs b/serde_v8/magic/zero_copy_buf.rs index dcd969aea0..a9dc6334c2 100644 --- a/serde_v8/magic/zero_copy_buf.rs +++ b/serde_v8/magic/zero_copy_buf.rs @@ -19,8 +19,8 @@ use super::transl8::FromV8; /// `let copy = Vec::from(&*zero_copy_buf);` #[derive(Clone)] pub struct ZeroCopyBuf { - store: v8::SharedRef, - range: Range, + pub(crate) store: v8::SharedRef, + pub(crate) range: Range, } unsafe impl Send for ZeroCopyBuf {} @@ -40,17 +40,6 @@ impl ZeroCopyBuf { Ok(Self { store, range }) } - pub fn from_view( - scope: &mut v8::HandleScope, - view: v8::Local, - ) -> Result { - let buffer = view.buffer(scope).ok_or(v8::DataError::NoData { - expected: "view to have a buffer", - })?; - let (offset, len) = (view.byte_offset(), view.byte_length()); - Self::from_buffer(buffer, offset..offset + len) - } - fn as_slice(&self) -> &[u8] { unsafe { &*(&self.store[self.range.clone()] as *const _ as *const [u8]) } } @@ -61,21 +50,32 @@ impl ZeroCopyBuf { } } +pub(crate) fn to_ranged_buffer<'s>( + scope: &mut v8::HandleScope<'s>, + value: v8::Local, +) -> Result<(v8::Local<'s, v8::ArrayBuffer>, Range), v8::DataError> { + if value.is_array_buffer_view() { + let view: v8::Local = value.try_into()?; + let (offset, len) = (view.byte_offset(), view.byte_length()); + let buffer = view.buffer(scope).ok_or(v8::DataError::NoData { + expected: "view to have a buffer", + })?; + let buffer = v8::Local::new(scope, buffer); // recreate handle to avoid lifetime issues + return Ok((buffer, offset..offset + len)); + } + let b: v8::Local = value.try_into()?; + let b = v8::Local::new(scope, b); // recreate handle to avoid lifetime issues + Ok((b, 0..b.byte_length())) +} + impl FromV8 for ZeroCopyBuf { fn from_v8( scope: &mut v8::HandleScope, value: v8::Local, ) -> Result { - if value.is_array_buffer() { - value - .try_into() - .and_then(|b| Self::from_buffer(b, 0..b.byte_length())) - } else { - value - .try_into() - .and_then(|view| Self::from_view(scope, view)) - } - .map_err(|_| crate::Error::ExpectedBuffer) + to_ranged_buffer(scope, value) + .and_then(|(b, r)| Self::from_buffer(b, r)) + .map_err(|_| crate::Error::ExpectedBuffer) } } diff --git a/serde_v8/ser.rs b/serde_v8/ser.rs index 5241aeaeff..867d4b7952 100644 --- a/serde_v8/ser.rs +++ b/serde_v8/ser.rs @@ -8,7 +8,7 @@ use crate::error::{Error, Result}; use crate::keys::v8_struct_key; use crate::magic::transl8::MAGIC_FIELD; use crate::magic::transl8::{opaque_deref, opaque_recv, MagicType, ToV8}; -use crate::{magic, Buffer, ByteString, U16String}; +use crate::{magic, Buffer, ByteString, DetachedBuffer, U16String}; type JsValue<'s> = v8::Local<'s, v8::Value>; type JsResult<'s> = Result>; @@ -261,6 +261,7 @@ impl<'a, 'b, 'c, T: MagicType + ToV8> ser::SerializeStruct pub enum StructSerializers<'a, 'b, 'c> { Magic(MagicalSerializer<'a, 'b, 'c, magic::Value<'a>>), MagicBuffer(MagicalSerializer<'a, 'b, 'c, Buffer>), + MagicDetached(MagicalSerializer<'a, 'b, 'c, DetachedBuffer>), MagicByteString(MagicalSerializer<'a, 'b, 'c, ByteString>), MagicU16String(MagicalSerializer<'a, 'b, 'c, U16String>), Regular(ObjectSerializer<'a, 'b, 'c>), @@ -278,6 +279,7 @@ impl<'a, 'b, 'c> ser::SerializeStruct for StructSerializers<'a, 'b, 'c> { match self { StructSerializers::Magic(s) => s.serialize_field(key, value), StructSerializers::MagicBuffer(s) => s.serialize_field(key, value), + StructSerializers::MagicDetached(s) => s.serialize_field(key, value), StructSerializers::MagicByteString(s) => s.serialize_field(key, value), StructSerializers::MagicU16String(s) => s.serialize_field(key, value), StructSerializers::Regular(s) => s.serialize_field(key, value), @@ -288,6 +290,7 @@ impl<'a, 'b, 'c> ser::SerializeStruct for StructSerializers<'a, 'b, 'c> { match self { StructSerializers::Magic(s) => s.end(), StructSerializers::MagicBuffer(s) => s.end(), + StructSerializers::MagicDetached(s) => s.end(), StructSerializers::MagicByteString(s) => s.end(), StructSerializers::MagicU16String(s) => s.end(), StructSerializers::Regular(s) => s.end(), @@ -528,6 +531,10 @@ impl<'a, 'b, 'c> ser::Serializer for Serializer<'a, 'b, 'c> { let m = MagicalSerializer::::new(self.scope); Ok(StructSerializers::MagicBuffer(m)) } + DetachedBuffer::MAGIC_NAME => { + let m = MagicalSerializer::::new(self.scope); + Ok(StructSerializers::MagicDetached(m)) + } magic::Value::MAGIC_NAME => { let m = MagicalSerializer::>::new(self.scope); Ok(StructSerializers::Magic(m))