From bf277f4f8e90c221bdb82f2cf19f20166665de78 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Mon, 2 Oct 2023 12:08:51 -0600 Subject: [PATCH] feat: new_backing_store_from_bytes and empty for ArrayBuffer and SharedArrayBuffer (#1334) --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/array_buffer.rs | 143 +++++++++++++++++++++++++++---------- src/binding.cc | 6 ++ src/shared_array_buffer.rs | 114 ++++++++++++++++++++++------- tests/test_api.rs | 23 ++++++ 6 files changed, 230 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dcca202b..d43d4d8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,6 +127,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + [[package]] name = "calloop" version = "0.9.3" @@ -1315,6 +1321,7 @@ version = "0.78.0" dependencies = [ "align-data", "bitflags 1.3.2", + "bytes", "fslock", "once_cell", "trybuild", diff --git a/Cargo.toml b/Cargo.toml index 72823d52..016f9f6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ fslock = "0.1.8" which = "4.2.5" [dev-dependencies] +bytes = "1" align-data = "0.1.0" fslock = "0.1.8" trybuild = "1.0.61" diff --git a/src/array_buffer.rs b/src/array_buffer.rs index 523ea45d..a7dff16c 100644 --- a/src/array_buffer.rs +++ b/src/array_buffer.rs @@ -5,7 +5,6 @@ use std::ffi::c_void; use std::ops::Deref; use std::ptr; use std::ptr::null; -use std::ptr::null_mut; use std::ptr::NonNull; use std::slice; @@ -60,6 +59,7 @@ extern "C" { deleter: BackingStoreDeleterCallback, deleter_data: *mut c_void, ) -> *mut BackingStore; + fn v8__BackingStore__EmptyBackingStore(shared: bool) -> *mut BackingStore; fn v8__BackingStore__Data(this: *const BackingStore) -> *mut c_void; fn v8__BackingStore__ByteLength(this: *const BackingStore) -> usize; @@ -246,25 +246,50 @@ pub type BackingStoreDeleterCallback = unsafe extern "C" fn( deleter_data: *mut c_void, ); -pub unsafe extern "C" fn boxed_slice_deleter_callback( - data: *mut c_void, - byte_length: usize, - _deleter_data: *mut c_void, -) { - let slice_ptr = ptr::slice_from_raw_parts_mut(data as *mut u8, byte_length); - let b = Box::from_raw(slice_ptr); - drop(b); +pub(crate) mod sealed { + pub trait Rawable { + fn into_raw(self) -> *const (); + unsafe fn drop_raw(ptr: *const (), size: usize); + } } -pub unsafe extern "C" fn vec_deleter_callback( - data: *mut c_void, - byte_length: usize, - deleter_data: *mut c_void, -) { - let capacity = deleter_data as usize; - drop(Vec::from_raw_parts(data as *mut u8, byte_length, capacity)) +impl sealed::Rawable<[u8]> for Vec { + unsafe fn drop_raw(ptr: *const (), size: usize) { + as sealed::Rawable<[u8]>>::drop_raw(ptr, size); + } + + fn into_raw(self) -> *const () { + self.into_boxed_slice().into_raw() + } } +macro_rules! rawable { + ($container:ident) => { + impl sealed::Rawable for $container { + fn into_raw(self) -> *const () { + Self::into_raw(self) as _ + } + + unsafe fn drop_raw(ptr: *const (), _len: usize) { + _ = Self::from_raw(ptr as _); + } + } + + impl sealed::Rawable<[u8]> for $container<[u8]> { + fn into_raw(self) -> *const () { + Self::into_raw(self) as _ + } + + unsafe fn drop_raw(ptr: *const (), len: usize) { + _ = Self::from_raw(ptr::slice_from_raw_parts_mut(ptr as _, len)); + } + } + }; +} + +// Implement Rawable for single-ownership container types +rawable!(Box); + /// A wrapper around the backing store (i.e. the raw memory) of an array buffer. /// See a document linked in http://crbug.com/v8/9908 for more information. /// @@ -396,6 +421,16 @@ impl ArrayBuffer { .unwrap() } + /// Create a new, empty ArrayBuffer. + #[inline(always)] + pub fn empty<'s>(scope: &mut HandleScope<'s>) -> Local<'s, ArrayBuffer> { + // SAFETY: This is a v8-provided empty backing store + let backing_store = unsafe { + UniqueRef::from_raw(v8__BackingStore__EmptyBackingStore(false)) + }; + Self::with_backing_store(scope, &backing_store.make_shared()) + } + /// Data length in bytes. #[inline(always)] pub fn byte_length(&self) -> usize { @@ -489,16 +524,7 @@ impl ArrayBuffer { pub fn new_backing_store_from_boxed_slice( data: Box<[u8]>, ) -> UniqueRef { - let byte_length = data.len(); - let data_ptr = Box::into_raw(data) as *mut c_void; - unsafe { - UniqueRef::from_raw(v8__ArrayBuffer__NewBackingStore__with_data( - data_ptr, - byte_length, - boxed_slice_deleter_callback, - null_mut(), - )) - } + Self::new_backing_store_from_bytes(data) } /// Returns a new standalone BackingStore that takes over the ownership of @@ -509,20 +535,59 @@ impl ArrayBuffer { /// The result can be later passed to ArrayBuffer::New. The raw pointer /// to the buffer must not be passed again to any V8 API function. #[inline(always)] - pub fn new_backing_store_from_vec( - mut data: Vec, - ) -> UniqueRef { - let byte_length = data.len(); - let capacity = data.capacity(); - let data_ptr = data.as_mut_ptr() as *mut c_void; - std::mem::forget(data); + pub fn new_backing_store_from_vec(data: Vec) -> UniqueRef { + Self::new_backing_store_from_bytes(data) + } + + /// Returns a new standalone BackingStore backed by a container that dereferences + /// to a mutable slice of bytes. The object is dereferenced once, and the resulting slice's + /// memory is used for the lifetime of the buffer. + /// + /// This method may be called with most single-ownership containers that implement `AsMut<[u8]>`, including + /// `Box<[u8]>`, and `Vec`. This will also support most other mutable bytes containers (including `bytes::BytesMut`), + /// though these buffers will need to be boxed to manage ownership of memory. + /// + /// ``` + /// // Vector of bytes + /// let backing_store = v8::ArrayBuffer::new_backing_store_from_bytes(vec![1, 2, 3]); + /// // Boxes slice of bytes + /// let boxed_slice: Box<[u8]> = vec![1, 2, 3].into_boxed_slice(); + /// let backing_store = v8::ArrayBuffer::new_backing_store_from_bytes(boxed_slice); + /// // BytesMut from bytes crate + /// let backing_store = v8::ArrayBuffer::new_backing_store_from_bytes(Box::new(bytes::BytesMut::new())); + /// ``` + #[inline(always)] + pub fn new_backing_store_from_bytes( + mut bytes: T, + ) -> UniqueRef + where + U: ?Sized, + U: AsMut<[u8]>, + T: AsMut, + T: sealed::Rawable, + { + let len = bytes.as_mut().as_mut().len(); + let slice = bytes.as_mut().as_mut().as_mut_ptr(); + let ptr = T::into_raw(bytes); + + extern "C" fn drop_rawable, U: ?Sized>( + _ptr: *mut c_void, + len: usize, + data: *mut c_void, + ) { + // SAFETY: We know that data is a raw T from above + unsafe { >::drop_raw(data as _, len) } + } + + // SAFETY: We are extending the lifetime of a slice, but we're locking away the box that we + // derefed from so there's no way to get another mutable reference. unsafe { - UniqueRef::from_raw(v8__ArrayBuffer__NewBackingStore__with_data( - data_ptr, - byte_length, - vec_deleter_callback, - capacity as *mut c_void, - )) + Self::new_backing_store_from_ptr( + slice as _, + len, + drop_rawable::, + ptr as _, + ) } } diff --git a/src/binding.cc b/src/binding.cc index dd8ccaca..ed4e778b 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -866,6 +866,12 @@ two_pointers_t v8__ArrayBuffer__GetBackingStore(const v8::ArrayBuffer& self) { return make_pod(ptr_to_local(&self)->GetBackingStore()); } +v8::BackingStore* v8__BackingStore__EmptyBackingStore(bool shared) { + std::unique_ptr u = + i::BackingStore::EmptyBackingStore(shared ? i::SharedFlag::kShared : i::SharedFlag::kNotShared); + return static_cast(u.release()); +} + bool v8__BackingStore__IsResizableByUserJavaScript( const v8::BackingStore& self) { return ptr_to_local(&self)->IsResizableByUserJavaScript(); diff --git a/src/shared_array_buffer.rs b/src/shared_array_buffer.rs index 0259adc3..d414c5ed 100644 --- a/src/shared_array_buffer.rs +++ b/src/shared_array_buffer.rs @@ -1,10 +1,7 @@ // Copyright 2019-2021 the Deno authors. All rights reserved. MIT license. use std::ffi::c_void; -use std::ptr::null_mut; -use crate::array_buffer::boxed_slice_deleter_callback; -use crate::array_buffer::vec_deleter_callback; use crate::support::SharedRef; use crate::support::UniqueRef; use crate::BackingStore; @@ -38,6 +35,7 @@ extern "C" { deleter: BackingStoreDeleterCallback, deleter_data: *mut c_void, ) -> *mut BackingStore; + fn v8__BackingStore__EmptyBackingStore(shared: bool) -> *mut BackingStore; } impl SharedArrayBuffer { @@ -76,6 +74,17 @@ impl SharedArrayBuffer { .unwrap() } + /// Create a new, empty SharedArrayBuffer. + #[inline(always)] + pub fn empty<'s>( + scope: &mut HandleScope<'s>, + ) -> Local<'s, SharedArrayBuffer> { + // SAFETY: This is a v8-provided empty backing store + let backing_store = + unsafe { UniqueRef::from_raw(v8__BackingStore__EmptyBackingStore(true)) }; + Self::with_backing_store(scope, &backing_store.make_shared()) + } + /// Data length in bytes. #[inline(always)] pub fn byte_length(&self) -> usize { @@ -124,16 +133,7 @@ impl SharedArrayBuffer { pub fn new_backing_store_from_boxed_slice( data: Box<[u8]>, ) -> UniqueRef { - let byte_length = data.len(); - let data_ptr = Box::into_raw(data) as *mut c_void; - unsafe { - UniqueRef::from_raw(v8__SharedArrayBuffer__NewBackingStore__with_data( - data_ptr, - byte_length, - boxed_slice_deleter_callback, - null_mut(), - )) - } + Self::new_backing_store_from_bytes(data) } /// Returns a new standalone BackingStore that takes over the ownership of @@ -144,19 +144,83 @@ impl SharedArrayBuffer { /// The result can be later passed to SharedArrayBuffer::New. The raw pointer /// to the buffer must not be passed again to any V8 API function. #[inline(always)] - pub fn new_backing_store_from_vec( - mut data: Vec, - ) -> UniqueRef { - let byte_length = data.len(); - let data_ptr = data.as_mut_ptr() as *mut c_void; - std::mem::forget(data); + pub fn new_backing_store_from_vec(data: Vec) -> UniqueRef { + Self::new_backing_store_from_bytes(data) + } + + /// Returns a new standalone BackingStore backed by a container that dereferences + /// to a mutable slice of bytes. The object is dereferenced once, and the resulting slice's + /// memory is used for the lifetime of the buffer. + /// + /// This method may be called with most single-ownership containers that implement `AsMut<[u8]>`, including + /// `Box<[u8]>`, and `Vec`. This will also support most other mutable bytes containers (including `bytes::BytesMut`), + /// though these buffers will need to be boxed to manage ownership of memory. + /// + /// ``` + /// // Vector of bytes + /// let backing_store = v8::ArrayBuffer::new_backing_store_from_bytes(vec![1, 2, 3]); + /// // Boxes slice of bytes + /// let boxed_slice: Box<[u8]> = vec![1, 2, 3].into_boxed_slice(); + /// let backing_store = v8::ArrayBuffer::new_backing_store_from_bytes(boxed_slice); + /// // BytesMut from bytes crate + /// let backing_store = v8::ArrayBuffer::new_backing_store_from_bytes(Box::new(bytes::BytesMut::new())); + /// ``` + #[inline(always)] + pub fn new_backing_store_from_bytes( + mut bytes: T, + ) -> UniqueRef + where + U: ?Sized, + U: AsMut<[u8]>, + T: AsMut, + T: crate::array_buffer::sealed::Rawable, + { + let len = bytes.as_mut().as_mut().len(); + let slice = bytes.as_mut().as_mut().as_mut_ptr(); + let ptr = T::into_raw(bytes); + + extern "C" fn drop_rawable< + T: crate::array_buffer::sealed::Rawable, + U: ?Sized, + >( + _ptr: *mut c_void, + len: usize, + data: *mut c_void, + ) { + // SAFETY: We know that data is a raw T from above + unsafe { + >::drop_raw(data as _, len) + } + } + + // SAFETY: We are extending the lifetime of a slice, but we're locking away the box that we + // derefed from so there's no way to get another mutable reference. unsafe { - UniqueRef::from_raw(v8__SharedArrayBuffer__NewBackingStore__with_data( - data_ptr, - byte_length, - vec_deleter_callback, - null_mut(), - )) + Self::new_backing_store_from_ptr( + slice as _, + len, + drop_rawable::, + ptr as _, + ) } } + + /// Returns a new standalone shared BackingStore backed by given ptr. + /// + /// SAFETY: This API consumes raw pointers so is inherently + /// unsafe. Usually you should use new_backing_store_from_boxed_slice. + #[inline(always)] + pub unsafe fn new_backing_store_from_ptr( + data_ptr: *mut c_void, + byte_length: usize, + deleter_callback: BackingStoreDeleterCallback, + deleter_data: *mut c_void, + ) -> UniqueRef { + UniqueRef::from_raw(v8__SharedArrayBuffer__NewBackingStore__with_data( + data_ptr, + byte_length, + deleter_callback, + deleter_data, + )) + } } diff --git a/tests/test_api.rs b/tests/test_api.rs index 577442ab..491a9fc7 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -828,6 +828,24 @@ fn array_buffer() { assert_eq!(10, shared_bs_2.byte_length()); assert_eq!(shared_bs_2[0].get(), 0); assert_eq!(shared_bs_2[9].get(), 9); + + // Empty + let ab = v8::ArrayBuffer::empty(scope); + assert_eq!(0, ab.byte_length()); + assert!(!ab.get_backing_store().is_shared()); + + // From a bytes::BytesMut + let mut data = bytes::BytesMut::new(); + data.extend_from_slice(&[0; 16]); + data[0] = 1; + let unique_bs = + v8::ArrayBuffer::new_backing_store_from_bytes(Box::new(data)); + assert_eq!(unique_bs.get(0).unwrap().get(), 1); + + let ab = + v8::ArrayBuffer::with_backing_store(scope, &unique_bs.make_shared()); + assert_eq!(ab.byte_length(), 16); + assert_eq!(ab.get_backing_store().get(0).unwrap().get(), 1); } } @@ -5607,6 +5625,11 @@ fn shared_array_buffer() { assert_eq!(shared_bs_3.byte_length(), 10); assert_eq!(shared_bs_3[0].get(), 0); assert_eq!(shared_bs_3[9].get(), 9); + + // Empty + let ab = v8::SharedArrayBuffer::empty(scope); + assert_eq!(ab.byte_length(), 0); + assert!(ab.get_backing_store().is_shared()); } }