diff --git a/.cargo/config.toml b/.cargo/config.toml index 942eb90da..981b93099 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,8 @@ [alias] # Neon defines mutually exclusive feature flags which prevents using `cargo clippy --all-features` # The following aliases simplify linting the entire workspace +check-napi = "check --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p electron-tests -p napi-tests --features proc-macros,try-catch-api,napi-experimental" +check-legacy = "check --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p tests -p static_tests --features event-handler-api,proc-macros,try-catch-api,legacy-runtime" clippy-legacy = "clippy --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p tests -p static_tests --features event-handler-api,proc-macros,try-catch-api,legacy-runtime -- -A clippy::missing_safety_doc" clippy-napi = "clippy --all-targets --no-default-features -p neon -p neon-runtime -p neon-build -p neon-macros -p electron-tests -p napi-tests --features proc-macros,try-catch-api,napi-experimental -- -A clippy::missing_safety_doc" neon-test = "test --no-default-features --features napi-experimental" diff --git a/crates/neon-runtime/src/napi/arraybuffer.rs b/crates/neon-runtime/src/napi/arraybuffer.rs index 96e454608..68e14c35a 100644 --- a/crates/neon-runtime/src/napi/arraybuffer.rs +++ b/crates/neon-runtime/src/napi/arraybuffer.rs @@ -2,13 +2,21 @@ use crate::raw::{Env, Local}; use std::mem::MaybeUninit; use std::os::raw::c_void; use std::ptr::null_mut; +use std::slice; use crate::napi::bindings as napi; -pub unsafe fn new(out: &mut Local, env: Env, size: u32) -> bool { - let status = napi::create_arraybuffer(env, size as usize, null_mut(), out as *mut _); +pub unsafe fn new(env: Env, len: usize) -> Result { + let mut buf = MaybeUninit::uninit(); + let status = napi::create_arraybuffer(env, len, null_mut(), buf.as_mut_ptr()); - status == napi::Status::Ok + if status == napi::Status::PendingException { + return Err(status); + } + + assert_eq!(status, napi::Status::Ok); + + Ok(buf.assume_init()) } pub unsafe fn data(env: Env, base_out: &mut *mut c_void, obj: Local) -> usize { @@ -48,3 +56,22 @@ where unsafe extern "C" fn drop_external(_env: Env, _data: *mut c_void, hint: *mut c_void) { Box::::from_raw(hint as *mut _); } + +/// # Safety +/// * Caller must ensure `env` and `buf` are valid +/// * The lifetime `'a` does not exceed the lifetime of `Env` or `buf` +pub unsafe fn as_mut_slice<'a>(env: Env, buf: Local) -> &'a mut [u8] { + let mut data = MaybeUninit::uninit(); + let mut size = 0usize; + + assert_eq!( + napi::get_arraybuffer_info(env, buf, data.as_mut_ptr(), &mut size as *mut _), + napi::Status::Ok, + ); + + if size == 0 { + return &mut []; + } + + slice::from_raw_parts_mut(data.assume_init().cast(), size) +} diff --git a/crates/neon-runtime/src/napi/bindings/functions.rs b/crates/neon-runtime/src/napi/bindings/functions.rs index b8d29b16e..268667a87 100644 --- a/crates/neon-runtime/src/napi/bindings/functions.rs +++ b/crates/neon-runtime/src/napi/bindings/functions.rs @@ -53,6 +53,7 @@ mod napi1 { fn close_handle_scope(env: Env, scope: HandleScope) -> Status; fn is_arraybuffer(env: Env, value: Value, result: *mut bool) -> Status; + fn is_typedarray(env: Env, value: Value, result: *mut bool) -> Status; fn is_buffer(env: Env, value: Value, result: *mut bool) -> Status; fn is_error(env: Env, value: Value, result: *mut bool) -> Status; fn is_array(env: Env, value: Value, result: *mut bool) -> Status; @@ -90,6 +91,16 @@ mod napi1 { byte_length: *mut usize, ) -> Status; + fn get_typedarray_info( + env: Env, + typedarray: Value, + typ: *mut TypedArrayType, + length: *mut usize, + data: *mut *mut c_void, + buf: *mut Value, + offset: *mut usize, + ) -> Status; + fn create_buffer( env: Env, length: usize, diff --git a/crates/neon-runtime/src/napi/bindings/mod.rs b/crates/neon-runtime/src/napi/bindings/mod.rs index fba148c8d..8771255bd 100644 --- a/crates/neon-runtime/src/napi/bindings/mod.rs +++ b/crates/neon-runtime/src/napi/bindings/mod.rs @@ -169,6 +169,7 @@ macro_rules! generate { use std::sync::Once; pub(crate) use functions::*; +pub use types::TypedArrayType; pub(crate) use types::*; mod functions; diff --git a/crates/neon-runtime/src/napi/bindings/types.rs b/crates/neon-runtime/src/napi/bindings/types.rs index 8a82f3f34..55f86c384 100644 --- a/crates/neon-runtime/src/napi/bindings/types.rs +++ b/crates/neon-runtime/src/napi/bindings/types.rs @@ -111,6 +111,23 @@ pub(crate) enum ValueType { BigInt = 9, } +#[allow(dead_code)] +#[repr(u32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum TypedArrayType { + I8 = 0, + U8 = 1, + U8Clamped = 2, + I16 = 3, + U16 = 4, + I32 = 5, + U32 = 6, + F32 = 7, + F64 = 8, + I64 = 9, + U64 = 10, +} + #[allow(dead_code)] #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] diff --git a/crates/neon-runtime/src/napi/buffer.rs b/crates/neon-runtime/src/napi/buffer.rs index 530243672..398cccb06 100644 --- a/crates/neon-runtime/src/napi/buffer.rs +++ b/crates/neon-runtime/src/napi/buffer.rs @@ -1,27 +1,30 @@ use crate::raw::{Env, Local}; use std::mem::MaybeUninit; use std::os::raw::c_void; -use std::ptr::null_mut; +use std::slice; use crate::napi::bindings as napi; -pub unsafe fn new(env: Env, out: &mut Local, size: u32) -> bool { - let mut bytes = null_mut(); - let status = napi::create_buffer(env, size as usize, &mut bytes as *mut _, out as *mut _); - if status == napi::Status::Ok { - // zero-initialize it. If performance is critical, JsBuffer::uninitialized can be used - // instead. - std::ptr::write_bytes(bytes, 0, size as usize); - true - } else { - false - } +pub unsafe fn new(env: Env, len: usize) -> Result { + let (buf, bytes) = uninitialized(env, len)?; + + std::ptr::write_bytes(bytes, 0, len); + + Ok(buf) } -pub unsafe fn uninitialized(env: Env, out: &mut Local, size: u32) -> bool { - let mut bytes = null_mut(); - let status = napi::create_buffer(env, size as usize, &mut bytes as *mut _, out as *mut _); - status == napi::Status::Ok +pub unsafe fn uninitialized(env: Env, len: usize) -> Result<(Local, *mut u8), napi::Status> { + let mut buf = MaybeUninit::uninit(); + let mut bytes = MaybeUninit::uninit(); + let status = napi::create_buffer(env, len, bytes.as_mut_ptr(), buf.as_mut_ptr()); + + if status == napi::Status::PendingException { + return Err(status); + } + + assert_eq!(status, napi::Status::Ok); + + Ok((buf.assume_init(), bytes.assume_init().cast())) } pub unsafe fn new_external(env: Env, data: T) -> Local @@ -61,3 +64,22 @@ pub unsafe fn data(env: Env, base_out: &mut *mut c_void, obj: Local) -> usize { unsafe extern "C" fn drop_external(_env: Env, _data: *mut c_void, hint: *mut c_void) { Box::::from_raw(hint as *mut _); } + +/// # Safety +/// * Caller must ensure `env` and `buf` are valid +/// * The lifetime `'a` does not exceed the lifetime of `Env` or `buf` +pub unsafe fn as_mut_slice<'a>(env: Env, buf: Local) -> &'a mut [u8] { + let mut data = MaybeUninit::uninit(); + let mut size = 0usize; + + assert_eq!( + napi::get_buffer_info(env, buf, data.as_mut_ptr(), &mut size as *mut _), + napi::Status::Ok, + ); + + if size == 0 { + return &mut []; + } + + slice::from_raw_parts_mut(data.assume_init().cast(), size) +} diff --git a/crates/neon-runtime/src/napi/mod.rs b/crates/neon-runtime/src/napi/mod.rs index 2f2520cd7..5ab9f8484 100644 --- a/crates/neon-runtime/src/napi/mod.rs +++ b/crates/neon-runtime/src/napi/mod.rs @@ -20,6 +20,7 @@ pub mod string; pub mod tag; #[cfg(feature = "napi-4")] pub mod tsfn; +pub mod typedarray; mod bindings; pub use bindings::*; diff --git a/crates/neon-runtime/src/napi/tag.rs b/crates/neon-runtime/src/napi/tag.rs index d0cd048b3..d1b969182 100644 --- a/crates/neon-runtime/src/napi/tag.rs +++ b/crates/neon-runtime/src/napi/tag.rs @@ -80,6 +80,16 @@ pub unsafe fn is_arraybuffer(env: Env, val: Local) -> bool { result } +/// Is `val` a TypedArray instance? +pub unsafe fn is_typedarray(env: Env, val: Local) -> bool { + let mut result = false; + assert_eq!( + napi::is_typedarray(env, val, &mut result as *mut _), + napi::Status::Ok + ); + result +} + #[cfg(feature = "napi-5")] pub unsafe fn is_date(env: Env, val: Local) -> bool { let mut result = false; diff --git a/crates/neon-runtime/src/napi/typedarray.rs b/crates/neon-runtime/src/napi/typedarray.rs new file mode 100644 index 000000000..b902f9da9 --- /dev/null +++ b/crates/neon-runtime/src/napi/typedarray.rs @@ -0,0 +1,40 @@ +use std::ffi::c_void; +use std::mem::MaybeUninit; + +use crate::napi::bindings::{self as napi, TypedArrayType}; +use crate::raw::{Env, Local}; + +#[derive(Debug)] +/// Information describing a JavaScript [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) +pub struct TypedArrayInfo { + pub typ: TypedArrayType, + pub length: usize, + pub data: *mut c_void, + pub buf: Local, + pub offset: usize, +} + +/// Get [information](TypedArrayInfo) describing a JavaScript `TypedArray` +/// +/// # Safety +/// * `env` must be valid `napi_env` for the current scope +/// * `value` must be a handle pointing to a `TypedArray` +pub unsafe fn info(env: Env, value: Local) -> TypedArrayInfo { + let mut info = MaybeUninit::::zeroed(); + let ptr = info.as_mut_ptr(); + + assert_eq!( + napi::get_typedarray_info( + env, + value, + &mut (*ptr).typ, + &mut (*ptr).length, + &mut (*ptr).data, + &mut (*ptr).buf, + &mut (*ptr).offset, + ), + napi::Status::Ok, + ); + + info.assume_init() +} diff --git a/src/context/mod.rs b/src/context/mod.rs index e0fdb9313..93254b774 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -148,7 +148,9 @@ pub(crate) mod internal; +#[cfg(feature = "legacy-runtime")] use crate::borrow::internal::Ledger; +#[cfg(feature = "legacy-runtime")] use crate::borrow::{Borrow, BorrowMut, Ref, RefMut}; use crate::context::internal::Env; #[cfg(all(feature = "napi-4", feature = "channel-api"))] @@ -160,21 +162,23 @@ use crate::lifecycle::InstanceData; use crate::object::class::Class; use crate::object::{Object, This}; use crate::result::{JsResult, NeonResult, Throw}; -use crate::types::binary::{JsArrayBuffer, JsBuffer}; #[cfg(feature = "napi-1")] use crate::types::boxed::{Finalize, JsBox}; +#[cfg(feature = "napi-1")] +pub use crate::types::buffer::lock::Lock; #[cfg(feature = "napi-5")] use crate::types::date::{DateError, JsDate}; use crate::types::error::JsError; use crate::types::{ - JsArray, JsBoolean, JsFunction, JsNull, JsNumber, JsObject, JsString, JsUndefined, JsValue, - StringResult, Value, + JsArray, JsArrayBuffer, JsBoolean, JsBuffer, JsFunction, JsNull, JsNumber, JsObject, JsString, + JsUndefined, JsValue, StringResult, Value, }; use neon_runtime; use neon_runtime::raw; #[cfg(feature = "napi-1")] use smallvec::SmallVec; use std; +#[cfg(feature = "legacy-runtime")] use std::cell::RefCell; use std::convert::Into; use std::marker::PhantomData; @@ -267,6 +271,7 @@ pub enum CallKind { Call, } +#[cfg(feature = "legacy-runtime")] /// A temporary lock of an execution context. /// /// While a lock is alive, no JavaScript code can be executed in the execution context. @@ -278,6 +283,7 @@ pub struct Lock<'a> { phantom: PhantomData<&'a ()>, } +#[cfg(feature = "legacy-runtime")] impl<'a> Lock<'a> { fn new(env: Env) -> Self { Lock { @@ -294,14 +300,27 @@ impl<'a> Lock<'a> { /// /// A context has a lifetime `'a`, which ensures the safety of handles managed by the JS garbage collector. All handles created during the lifetime of a context are kept alive for that duration and cannot outlive the context. pub trait Context<'a>: ContextInternal<'a> { + #[cfg(feature = "legacy-runtime")] /// Lock the JavaScript engine, returning an RAII guard that keeps the lock active as long as the guard is alive. /// /// If this is not the currently active context (for example, if it was used to spawn a scoped context with `execute_scoped` or `compute_scoped`), this method will panic. - fn lock(&self) -> Lock<'_> { + fn lock(&mut self) -> Lock<'_> { self.check_active(); Lock::new(self.env()) } + #[cfg(feature = "napi-1")] + /// Lock the JavaScript engine, returning an RAII guard that keeps the lock active as long as the guard is alive. + /// + /// If this is not the currently active context (for example, if it was used to spawn a scoped context with `execute_scoped` or `compute_scoped`), this method will panic. + fn lock<'b>(&'b mut self) -> Lock + where + 'a: 'b, + { + Lock::new(self) + } + + #[cfg(feature = "legacy-runtime")] /// Convenience method for locking the JavaScript engine and borrowing a single JS value's internals. /// /// # Example: @@ -321,7 +340,7 @@ pub trait Context<'a>: ContextInternal<'a> { /// We may be able to generalize this compatibly in the future when the Rust bug is fixed, /// but while the extra `&` is a small ergonomics regression, this API is still a nice /// convenience. - fn borrow<'c, V, T, F>(&self, v: &'c Handle, f: F) -> T + fn borrow<'c, V, T, F>(&mut self, v: &'c Handle, f: F) -> T where V: Value, &'c V: Borrow, @@ -332,6 +351,7 @@ pub trait Context<'a>: ContextInternal<'a> { f(contents) } + #[cfg(feature = "legacy-runtime")] /// Convenience method for locking the JavaScript engine and mutably borrowing a single JS value's internals. /// /// # Example: @@ -353,7 +373,7 @@ pub trait Context<'a>: ContextInternal<'a> { /// We may be able to generalize this compatibly in the future when the Rust bug is fixed, /// but while the extra `&mut` is a small ergonomics regression, this API is still a nice /// convenience. - fn borrow_mut<'c, V, T, F>(&self, v: &'c mut Handle, f: F) -> T + fn borrow_mut<'c, V, T, F>(&mut self, v: &'c mut Handle, f: F) -> T where V: Value, &'c mut V: BorrowMut, @@ -369,7 +389,7 @@ pub trait Context<'a>: ContextInternal<'a> { /// Handles created in the new scope are kept alive only for the duration of the computation and cannot escape. /// /// This method can be useful for limiting the life of temporary values created during long-running computations, to prevent leaks. - fn execute_scoped(&self, f: F) -> T + fn execute_scoped(&mut self, f: F) -> T where F: for<'b> FnOnce(ExecuteContext<'b>) -> T, { @@ -385,7 +405,7 @@ pub trait Context<'a>: ContextInternal<'a> { /// Handles created in the new scope are kept alive only for the duration of the computation and cannot escape, with the exception of the result value, which is rooted in the outer context. /// /// This method can be useful for limiting the life of temporary values created during long-running computations, to prevent leaks. - fn compute_scoped(&self, f: F) -> JsResult<'a, V> + fn compute_scoped(&mut self, f: F) -> JsResult<'a, V> where V: Value, F: for<'b, 'c> FnOnce(ComputeContext<'b, 'c>) -> JsResult<'b, V>, @@ -467,16 +487,29 @@ pub trait Context<'a>: ContextInternal<'a> { JsArray::new(self, 0) } + #[cfg(feature = "legacy-runtime")] /// Convenience method for creating an empty `JsArrayBuffer` value. fn array_buffer(&mut self, size: u32) -> JsResult<'a, JsArrayBuffer> { JsArrayBuffer::new(self, size) } + #[cfg(feature = "napi-1")] + /// Convenience method for creating an empty `JsArrayBuffer` value. + fn array_buffer(&mut self, size: usize) -> JsResult<'a, JsArrayBuffer> { + JsArrayBuffer::new(self, size) + } + + #[cfg(feature = "legacy-runtime")] /// Convenience method for creating an empty `JsBuffer` value. fn buffer(&mut self, size: u32) -> JsResult<'a, JsBuffer> { JsBuffer::new(self, size) } + #[cfg(feature = "napi-1")] + /// Convenience method for creating an empty `JsBuffer` value. + fn buffer(&mut self, size: usize) -> JsResult<'a, JsBuffer> { + JsBuffer::new(self, size) + } /// Convenience method for creating a `JsDate` value. #[cfg(feature = "napi-5")] #[cfg_attr(docsrs, doc(cfg(feature = "napi-5")))] diff --git a/src/lib.rs b/src/lib.rs index a77efd138..a39835ece 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,7 @@ //! [supported]: https://github.com/neon-bindings/neon#platform-support #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(feature = "legacy-runtime")] pub mod borrow; pub mod context; #[cfg(any( diff --git a/src/prelude.rs b/src/prelude.rs index 858a9a6f4..08ab087fc 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,5 +1,6 @@ //! Convenience module for the most common Neon imports. +#[cfg(feature = "legacy-runtime")] #[doc(no_inline)] pub use crate::borrow::{Borrow, BorrowMut}; #[doc(no_inline)] @@ -30,13 +31,19 @@ pub use crate::object::Object; #[doc(no_inline)] pub use crate::register_module; #[doc(no_inline)] -pub use crate::result::{JsResult, JsResultExt, NeonResult}; +pub use crate::result::{JsResult, JsResultExt, NeonResult, ResultExt as NeonResultExt}; #[cfg(feature = "legacy-runtime")] pub use crate::task::Task; +#[cfg(feature = "legacy-runtime")] +#[doc(no_inline)] +pub use crate::types::BinaryData; +#[cfg(feature = "napi-1")] +#[doc(no_inline)] +pub use crate::types::JsTypedArray; #[doc(no_inline)] pub use crate::types::{ - BinaryData, JsArray, JsArrayBuffer, JsBoolean, JsBuffer, JsError, JsFunction, JsNull, JsNumber, - JsObject, JsString, JsUndefined, JsValue, Value, + JsArray, JsArrayBuffer, JsBoolean, JsBuffer, JsError, JsFunction, JsNull, JsNumber, JsObject, + JsString, JsUndefined, JsValue, Value, }; #[cfg(feature = "napi-1")] #[doc(no_inline)] diff --git a/src/result/mod.rs b/src/result/mod.rs index 0a8a72885..4b9a756b2 100644 --- a/src/result/mod.rs +++ b/src/result/mod.rs @@ -66,3 +66,9 @@ pub type JsResult<'b, T> = NeonResult>; pub trait JsResultExt<'a, V: Value> { fn or_throw<'b, C: Context<'b>>(self, cx: &mut C) -> JsResult<'a, V>; } + +/// Extension trait for converting Rust [`Result`](std::result::Result) values +/// into [`NeonResult`](NeonResult) values by throwing JavaScript exceptions. +pub trait ResultExt { + fn or_throw<'a, C: Context<'a>>(self, cx: &mut C) -> NeonResult; +} diff --git a/src/types/binary.rs b/src/types/binary.rs index a0e7d1d6b..98dba5d27 100644 --- a/src/types/binary.rs +++ b/src/types/binary.rs @@ -1,11 +1,7 @@ -//! Types and traits representing binary JavaScript data. - use crate::borrow::internal::Pointer; use crate::borrow::{Borrow, BorrowMut, LoanError, Ref, RefMut}; use crate::context::internal::Env; use crate::context::{Context, Lock}; -#[cfg(feature = "napi-1")] -use crate::handle::Handle; use crate::handle::Managed; use crate::result::JsResult; use crate::types::internal::ValueInternal; @@ -31,7 +27,7 @@ impl JsBuffer { }) } - /// Constructs a new `Buffer` object, safely zero-filled. + /// Constructs a new `Buffer` object with uninitialized memory pub unsafe fn uninitialized<'a, C: Context<'a>>( cx: &mut C, size: u32, @@ -41,19 +37,6 @@ impl JsBuffer { neon_runtime::buffer::uninitialized(env.to_raw(), out, size) }) } - - #[cfg(feature = "napi-1")] - /// Construct a new `Buffer` from bytes allocated by Rust - pub fn external<'a, C, T>(cx: &mut C, data: T) -> Handle<'a, JsBuffer> - where - C: Context<'a>, - T: AsMut<[u8]> + Send, - { - let env = cx.env().to_raw(); - let value = unsafe { neon_runtime::buffer::new_external(env, data) }; - - Handle::new_internal(JsBuffer(value)) - } } impl Managed for JsBuffer { @@ -92,19 +75,6 @@ impl JsArrayBuffer { neon_runtime::arraybuffer::new(out, mem::transmute(cx.env()), size) }) } - - #[cfg(feature = "napi-1")] - /// Construct a new `ArrayBuffer` from bytes allocated by Rust - pub fn external<'a, C, T>(cx: &mut C, data: T) -> Handle<'a, JsArrayBuffer> - where - C: Context<'a>, - T: AsMut<[u8]> + Send, - { - let env = cx.env().to_raw(); - let value = unsafe { neon_runtime::arraybuffer::new_external(env, data) }; - - Handle::new_internal(JsArrayBuffer(value)) - } } impl Managed for JsArrayBuffer { diff --git a/src/types/buffer/lock.rs b/src/types/buffer/lock.rs new file mode 100644 index 000000000..dcb7f129e --- /dev/null +++ b/src/types/buffer/lock.rs @@ -0,0 +1,194 @@ +use std::cell::RefCell; +use std::ops::Range; + +use crate::context::Context; + +use super::{BorrowError, Ref, RefMut}; + +#[derive(Debug)] +/// A temporary lock of an execution context. +/// +/// While a lock is alive, no JavaScript code can be executed in the execution context. +/// +/// Values that support the `Borrow` trait may be dynamically borrowed by passing a +/// [`Lock`]. +pub struct Lock<'cx, C> { + pub(super) cx: &'cx C, + pub(super) ledger: RefCell, +} + +impl<'a: 'cx, 'cx, C> Lock<'cx, C> +where + C: Context<'a>, +{ + /// Constructs a new [`Lock`] and locks the VM. See also [`Context::lock`]. + pub fn new(cx: &'cx mut C) -> Lock<'cx, C> { + Lock { + cx, + ledger: Default::default(), + } + } +} + +#[derive(Debug, Default)] +// Bookkeeping for dynamically check borrowing rules +// +// Ranges are open on the end: `[start, end)` +pub(super) struct Ledger { + // Mutable borrows. Should never overlap with other borrows. + pub(super) owned: Vec>, + + // Immutable borrows. May overlap or contain duplicates. + pub(super) shared: Vec>, +} + +impl Ledger { + // Convert a slice of arbitrary type and size to a range of bytes addresses + // + // Alignment does not matter because we are only interested in bytes. + pub(super) fn slice_to_range(data: &[T]) -> Range<*const u8> { + let Range { start, end } = data.as_ptr_range(); + + (start.cast())..(end.cast()) + } + + // Dynamically check a slice conforms to borrow rules before returning by + // using interior mutability of the ledger. + pub(super) fn try_borrow<'a, T>( + ledger: &'a RefCell, + data: &'a [T], + ) -> Result, BorrowError> { + ledger.borrow_mut().try_add_borrow(data)?; + + Ok(Ref { ledger, data }) + } + + // Dynamically check a mutable slice conforms to borrow rules before returning by + // using interior mutability of the ledger. + pub(super) fn try_borrow_mut<'a, T>( + ledger: &'a RefCell, + data: &'a mut [T], + ) -> Result, BorrowError> { + ledger.borrow_mut().try_add_borrow_mut(data)?; + + Ok(RefMut { ledger, data }) + } + + // Try to add an immutable borrow to the ledger + fn try_add_borrow(&mut self, data: &[T]) -> Result<(), BorrowError> { + let range = Self::slice_to_range(data); + + // Check if the borrow overlaps with any active mutable borrow + check_overlap(&self.owned, &range)?; + + // Record a record of the immutable borrow + self.shared.push(range); + + Ok(()) + } + + // Try to add a mutable borrow to the ledger + fn try_add_borrow_mut(&mut self, data: &mut [T]) -> Result<(), BorrowError> { + let range = Self::slice_to_range(data); + + // Check if the borrow overlaps with any active mutable borrow + check_overlap(&self.owned, &range)?; + + // Check if the borrow overlaps with any active immutable borrow + check_overlap(&self.shared, &range)?; + + // Record a record of the mutable borrow + self.owned.push(range); + + Ok(()) + } +} + +fn is_disjoint(a: &Range<*const u8>, b: &Range<*const u8>) -> bool { + b.start >= a.end || a.start >= b.end +} + +fn check_overlap( + existing: &[Range<*const u8>], + range: &Range<*const u8>, +) -> Result<(), BorrowError> { + if existing.iter().all(|i| is_disjoint(i, range)) { + Ok(()) + } else { + Err(BorrowError::new()) + } +} + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + use std::error::Error; + use std::mem; + use std::slice; + + use super::{BorrowError, Ledger}; + + // Super unsafe, but we only use it for testing `Ledger` + fn unsafe_aliased_slice(data: &mut [T]) -> &'static mut [T] { + unsafe { slice::from_raw_parts_mut(data.as_mut_ptr(), data.len()) } + } + + #[test] + fn test_overlapping_immutable_borrows() -> Result<(), Box> { + let ledger = RefCell::new(Ledger::default()); + let data = vec![0u8; 128]; + + Ledger::try_borrow(&ledger, &data[0..10])?; + Ledger::try_borrow(&ledger, &data[0..100])?; + Ledger::try_borrow(&ledger, &data[20..])?; + + Ok(()) + } + + #[test] + fn test_nonoverlapping_borrows() -> Result<(), Box> { + let ledger = RefCell::new(Ledger::default()); + let mut data = vec![0; 16]; + let (a, b) = data.split_at_mut(4); + + let _a = Ledger::try_borrow_mut(&ledger, a)?; + let _b = Ledger::try_borrow(&ledger, b)?; + + Ok(()) + } + + #[test] + fn test_overlapping_borrows() -> Result<(), Box> { + let ledger = RefCell::new(Ledger::default()); + let mut data = vec![0; 16]; + let a = unsafe_aliased_slice(&mut data[4..8]); + let b = unsafe_aliased_slice(&mut data[6..12]); + let ab = Ledger::try_borrow(&ledger, a)?; + + // Should fail because it overlaps + assert_eq!( + Ledger::try_borrow_mut(&ledger, b).unwrap_err(), + BorrowError::new(), + ); + + // Drop the first borrow + mem::drop(ab); + + // Should succeed because previous borrow was dropped + let bb = Ledger::try_borrow_mut(&ledger, b)?; + + // Should fail because it overlaps + assert_eq!( + Ledger::try_borrow(&ledger, a).unwrap_err(), + BorrowError::new(), + ); + + // Drop the second borrow + mem::drop(bb); + + // Should succeed because previous borrow was dropped + let _ab = Ledger::try_borrow(&ledger, a)?; + + Ok(()) + } +} diff --git a/src/types/buffer/mod.rs b/src/types/buffer/mod.rs new file mode 100644 index 000000000..75229f139 --- /dev/null +++ b/src/types/buffer/mod.rs @@ -0,0 +1,157 @@ +use std::cell::RefCell; +use std::error::Error; +use std::fmt::{self, Debug, Display}; +use std::ops::{Deref, DerefMut}; + +use crate::context::Context; +use crate::result::{NeonResult, ResultExt}; + +use self::lock::{Ledger, Lock}; + +pub(crate) mod lock; +pub(super) mod types; + +/// A trait for borrowing binary data from JavaScript values +/// +/// Provides both statically and dynamically checked borrowing. Mutable borrows +/// are guaranteed not to overlap with other borrows. +pub trait TypedArray: private::Sealed { + type Item; + + /// Statically checked immutable borrow of binary data. + /// + /// This may not be used if a mutable borrow is in scope. For the dynamically + /// checked variant see [`TypedArray::try_borrow`]. + fn as_slice<'a: 'b, 'b, C>(&'b self, cx: &'b C) -> &'b [Self::Item] + where + C: Context<'a>; + + /// Statically checked mutable borrow of binary data. + /// + /// This may not be used if any other borrow is in scope. For the dynamically + /// checked variant see [`TypedArray::try_borrow_mut`]. + fn as_mut_slice<'a: 'b, 'b, C>(&'b mut self, cx: &'b mut C) -> &'b mut [Self::Item] + where + C: Context<'a>; + + /// Dynamically checked immutable borrow of binary data, returning an error if the + /// the borrow would overlap with a mutable borrow. + /// + /// The borrow lasts until [`Ref`] exits scope. + /// + /// This is the dynamically checked version of [`TypedArray::as_slice`]. + fn try_borrow<'a: 'b, 'b, C>( + &self, + lock: &'b Lock<'b, C>, + ) -> Result, BorrowError> + where + C: Context<'a>; + + /// Dynamically checked mutable borrow of binary data, returning an error if the + /// the borrow would overlap with an active borrow. + /// + /// The borrow lasts until [`RefMut`] exits scope. + /// + /// This is the dynamically checked version of [`TypedArray::as_mut_slice`]. + fn try_borrow_mut<'a: 'b, 'b, C>( + &mut self, + lock: &'b Lock<'b, C>, + ) -> Result, BorrowError> + where + C: Context<'a>; +} + +#[derive(Debug)] +/// Wraps binary data immutably borrowed from a JavaScript value. +pub struct Ref<'a, T> { + data: &'a [T], + ledger: &'a RefCell, +} + +#[derive(Debug)] +/// Wraps binary data mutably borrowed from a JavaScript value. +pub struct RefMut<'a, T> { + data: &'a mut [T], + ledger: &'a RefCell, +} + +impl<'a, T> Deref for Ref<'a, T> { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl<'a, T> Deref for RefMut<'a, T> { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl<'a, T> DerefMut for RefMut<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +impl<'a, T> Drop for Ref<'a, T> { + fn drop(&mut self) { + let mut ledger = self.ledger.borrow_mut(); + let range = Ledger::slice_to_range(&self.data); + let i = ledger.shared.iter().rposition(|r| r == &range).unwrap(); + + ledger.shared.remove(i); + } +} + +impl<'a, T> Drop for RefMut<'a, T> { + fn drop(&mut self) { + let mut ledger = self.ledger.borrow_mut(); + let range = Ledger::slice_to_range(&self.data); + let i = ledger.owned.iter().rposition(|r| r == &range).unwrap(); + + ledger.owned.remove(i); + } +} + +#[derive(Eq, PartialEq)] +/// An error returned by [`TypedArray::try_borrow`] or [`TypedArray::try_borrow_mut`] indicating +/// that a mutable borrow would overlap with another borrow. +/// +/// [`BorrowError`] may be converted to an exception with [`ResultExt::or_throw`]. +pub struct BorrowError { + _private: (), +} + +impl BorrowError { + fn new() -> Self { + BorrowError { _private: () } + } +} + +impl Error for BorrowError {} + +impl Display for BorrowError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt("Borrow overlaps with an active mutable borrow", f) + } +} + +impl Debug for BorrowError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BorrowError").finish() + } +} + +impl ResultExt for Result { + fn or_throw<'a, C: Context<'a>>(self, cx: &mut C) -> NeonResult { + self.or_else(|_| cx.throw_error("BorrowError")) + } +} + +mod private { + pub trait Sealed {} +} diff --git a/src/types/buffer/types.rs b/src/types/buffer/types.rs new file mode 100644 index 000000000..b24a23635 --- /dev/null +++ b/src/types/buffer/types.rs @@ -0,0 +1,358 @@ +use std::marker::PhantomData; +use std::slice; + +use neon_runtime::{raw, TypedArrayType}; + +use crate::context::{internal::Env, Context}; +use crate::handle::{Handle, Managed}; +use crate::result::{JsResult, Throw}; +use crate::types::{internal::ValueInternal, Object, Value}; + +use super::lock::{Ledger, Lock}; +use super::{private, BorrowError, Ref, RefMut, TypedArray}; + +/// The Node [`Buffer`](https://nodejs.org/api/buffer.html) type. +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct JsBuffer(raw::Local); + +impl JsBuffer { + /// Constructs a new `Buffer` object, safely zero-filled. + pub fn new<'a, C: Context<'a>>(cx: &mut C, len: usize) -> JsResult<'a, Self> { + let result = unsafe { neon_runtime::buffer::new(cx.env().to_raw(), len) }; + + if let Ok(buf) = result { + Ok(Handle::new_internal(Self(buf))) + } else { + Err(Throw) + } + } + + /// Constructs a new `Buffer` object with uninitialized memory + pub unsafe fn uninitialized<'a, C: Context<'a>>(cx: &mut C, len: usize) -> JsResult<'a, Self> { + let result = neon_runtime::buffer::uninitialized(cx.env().to_raw(), len); + + if let Ok((buf, _)) = result { + Ok(Handle::new_internal(Self(buf))) + } else { + Err(Throw) + } + } + + /// Construct a new `Buffer` from bytes allocated by Rust + pub fn external<'a, C, T>(cx: &mut C, data: T) -> Handle<'a, Self> + where + C: Context<'a>, + T: AsMut<[u8]> + Send, + { + let env = cx.env().to_raw(); + let value = unsafe { neon_runtime::buffer::new_external(env, data) }; + + Handle::new_internal(Self(value)) + } +} + +impl Managed for JsBuffer { + fn to_raw(self) -> raw::Local { + self.0 + } + + fn from_raw(_env: Env, h: raw::Local) -> Self { + Self(h) + } +} + +impl ValueInternal for JsBuffer { + fn name() -> String { + "Buffer".to_string() + } + + fn is_typeof(env: Env, other: Other) -> bool { + unsafe { neon_runtime::tag::is_buffer(env.to_raw(), other.to_raw()) } + } +} + +impl Value for JsBuffer {} + +impl Object for JsBuffer {} + +impl private::Sealed for JsBuffer {} + +impl TypedArray for JsBuffer { + type Item = u8; + + fn as_slice<'a: 'b, 'b, C>(&'b self, cx: &'b C) -> &'b [Self::Item] + where + C: Context<'a>, + { + unsafe { neon_runtime::buffer::as_mut_slice(cx.env().to_raw(), self.to_raw()) } + } + + fn as_mut_slice<'a: 'b, 'b, C>(&'b mut self, cx: &'b mut C) -> &'b mut [Self::Item] + where + C: Context<'a>, + { + unsafe { neon_runtime::buffer::as_mut_slice(cx.env().to_raw(), self.to_raw()) } + } + + fn try_borrow<'a: 'b, 'b, C>( + &self, + lock: &'b Lock<'b, C>, + ) -> Result, BorrowError> + where + C: Context<'a>, + { + // The borrowed data must be guarded by `Ledger` before returning + Ledger::try_borrow(&lock.ledger, unsafe { + neon_runtime::buffer::as_mut_slice(lock.cx.env().to_raw(), self.to_raw()) + }) + } + + fn try_borrow_mut<'a: 'b, 'b, C>( + &mut self, + lock: &'b Lock<'b, C>, + ) -> Result, BorrowError> + where + C: Context<'a>, + { + // The borrowed data must be guarded by `Ledger` before returning + Ledger::try_borrow_mut(&lock.ledger, unsafe { + neon_runtime::buffer::as_mut_slice(lock.cx.env().to_raw(), self.to_raw()) + }) + } +} + +/// The standard JS [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) type. +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct JsArrayBuffer(raw::Local); + +impl JsArrayBuffer { + /// Constructs a new `JsArrayBuffer` object, safely zero-filled. + pub fn new<'a, C: Context<'a>>(cx: &mut C, len: usize) -> JsResult<'a, Self> { + let result = unsafe { neon_runtime::arraybuffer::new(cx.env().to_raw(), len) }; + + if let Ok(buf) = result { + Ok(Handle::new_internal(Self(buf))) + } else { + Err(Throw) + } + } + + /// Construct a new `JsArrayBuffer` from bytes allocated by Rust + pub fn external<'a, C, T>(cx: &mut C, data: T) -> Handle<'a, Self> + where + C: Context<'a>, + T: AsMut<[u8]> + Send, + { + let env = cx.env().to_raw(); + let value = unsafe { neon_runtime::arraybuffer::new_external(env, data) }; + + Handle::new_internal(JsArrayBuffer(value)) + } +} + +impl Managed for JsArrayBuffer { + fn to_raw(self) -> raw::Local { + self.0 + } + + fn from_raw(_env: Env, h: raw::Local) -> Self { + Self(h) + } +} + +impl ValueInternal for JsArrayBuffer { + fn name() -> String { + "JsArrayBuffer".to_string() + } + + fn is_typeof(env: Env, other: Other) -> bool { + unsafe { neon_runtime::tag::is_arraybuffer(env.to_raw(), other.to_raw()) } + } +} + +impl Value for JsArrayBuffer {} + +impl Object for JsArrayBuffer {} + +impl private::Sealed for JsArrayBuffer {} + +impl TypedArray for JsArrayBuffer { + type Item = u8; + + fn as_slice<'a: 'b, 'b, C>(&'b self, cx: &'b C) -> &'b [Self::Item] + where + C: Context<'a>, + { + unsafe { neon_runtime::arraybuffer::as_mut_slice(cx.env().to_raw(), self.to_raw()) } + } + + fn as_mut_slice<'a: 'b, 'b, C>(&'b mut self, cx: &'b mut C) -> &'b mut [Self::Item] + where + C: Context<'a>, + { + unsafe { neon_runtime::arraybuffer::as_mut_slice(cx.env().to_raw(), self.to_raw()) } + } + + fn try_borrow<'a: 'b, 'b, C>( + &self, + lock: &'b Lock<'b, C>, + ) -> Result, BorrowError> + where + C: Context<'a>, + { + // The borrowed data must be guarded by `Ledger` before returning + Ledger::try_borrow(&lock.ledger, unsafe { + neon_runtime::arraybuffer::as_mut_slice(lock.cx.env().to_raw(), self.to_raw()) + }) + } + + fn try_borrow_mut<'a: 'b, 'b, C>( + &mut self, + lock: &'b Lock<'b, C>, + ) -> Result, BorrowError> + where + C: Context<'a>, + { + // The borrowed data must be guarded by `Ledger` before returning + Ledger::try_borrow_mut(&lock.ledger, unsafe { + neon_runtime::arraybuffer::as_mut_slice(lock.cx.env().to_raw(), self.to_raw()) + }) + } +} + +/// The standard JS [`TypedArray`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) type. +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct JsTypedArray { + local: raw::Local, + _type: PhantomData, +} + +impl private::Sealed for JsTypedArray {} + +impl Managed for JsTypedArray { + fn to_raw(self) -> raw::Local { + self.local + } + + fn from_raw(_env: Env, local: raw::Local) -> Self { + Self { + local, + _type: PhantomData, + } + } +} + +impl TypedArray for JsTypedArray { + type Item = T; + + fn as_slice<'a: 'b, 'b, C>(&'b self, cx: &'b C) -> &'b [Self::Item] + where + C: Context<'a>, + { + unsafe { + let env = cx.env().to_raw(); + let value = self.to_raw(); + let info = neon_runtime::typedarray::info(env, value); + + slice::from_raw_parts(info.data.cast(), info.length) + } + } + + fn as_mut_slice<'a: 'b, 'b, C>(&'b mut self, cx: &'b mut C) -> &'b mut [Self::Item] + where + C: Context<'a>, + { + unsafe { + let env = cx.env().to_raw(); + let value = self.to_raw(); + let info = neon_runtime::typedarray::info(env, value); + + slice::from_raw_parts_mut(info.data.cast(), info.length) + } + } + + fn try_borrow<'a: 'b, 'b, C>( + &self, + lock: &'b Lock<'b, C>, + ) -> Result, BorrowError> + where + C: Context<'a>, + { + unsafe { + let env = lock.cx.env().to_raw(); + let value = self.to_raw(); + let info = neon_runtime::typedarray::info(env, value); + + // The borrowed data must be guarded by `Ledger` before returning + Ledger::try_borrow( + &lock.ledger, + slice::from_raw_parts(info.data.cast(), info.length), + ) + } + } + + fn try_borrow_mut<'a: 'b, 'b, C>( + &mut self, + lock: &'b Lock<'b, C>, + ) -> Result, BorrowError> + where + C: Context<'a>, + { + unsafe { + let env = lock.cx.env().to_raw(); + let value = self.to_raw(); + let info = neon_runtime::typedarray::info(env, value); + + // The borrowed data must be guarded by `Ledger` before returning + Ledger::try_borrow_mut( + &lock.ledger, + slice::from_raw_parts_mut(info.data.cast(), info.length), + ) + } + } +} + +macro_rules! impl_typed_array { + ($name:expr, $typ:ty, $($pattern:pat)|+$(,)?) => { + impl Value for JsTypedArray<$typ> {} + + impl Object for JsTypedArray<$typ> {} + + impl ValueInternal for JsTypedArray<$typ> { + fn name() -> String { + $name.to_string() + } + + fn is_typeof(env: Env, other: Other) -> bool { + let env = env.to_raw(); + let other = other.to_raw(); + + if unsafe { !neon_runtime::tag::is_typedarray(env, other) } { + return false; + } + + let info = unsafe { neon_runtime::typedarray::info(env, other) }; + + matches!(info.typ, $($pattern)|+) + } + } + }; +} + +impl_typed_array!("Int8Array", i8, TypedArrayType::I8); +impl_typed_array!( + "Uint8Array", + u8, + TypedArrayType::U8 | TypedArrayType::U8Clamped, +); +impl_typed_array!("Int16Array", i16, TypedArrayType::I16); +impl_typed_array!("Uint16Array", u16, TypedArrayType::U16); +impl_typed_array!("Int32Array", i32, TypedArrayType::I32); +impl_typed_array!("Uint32Array", u32, TypedArrayType::U32); +impl_typed_array!("Float32Array", f32, TypedArrayType::F32); +impl_typed_array!("Float64Array", f64, TypedArrayType::F64); +impl_typed_array!("BigInt64Array", i64, TypedArrayType::I64); +impl_typed_array!("BigUint64Array", u64, TypedArrayType::U64); diff --git a/src/types/mod.rs b/src/types/mod.rs index 9106e10d3..3d4316e36 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -72,9 +72,12 @@ //! [types]: https://raw.githubusercontent.com/neon-bindings/neon/main/doc/types.jpg //! [unknown]: https://mariusschulz.com/blog/the-unknown-type-in-typescript#the-unknown-type -pub(crate) mod binary; +#[cfg(feature = "legacy-runtime")] +pub mod binary; #[cfg(feature = "napi-1")] pub(crate) mod boxed; +#[cfg(feature = "napi-1")] +pub mod buffer; #[cfg(feature = "napi-5")] pub(crate) mod date; pub(crate) mod error; @@ -99,9 +102,12 @@ use std::fmt::Debug; use std::marker::PhantomData; use std::os::raw::c_void; +#[cfg(feature = "legacy-runtime")] pub use self::binary::{BinaryData, BinaryViewType, JsArrayBuffer, JsBuffer}; #[cfg(feature = "napi-1")] pub use self::boxed::{Finalize, JsBox}; +#[cfg(feature = "napi-1")] +pub use self::buffer::types::{JsArrayBuffer, JsBuffer, JsTypedArray}; #[cfg(feature = "napi-5")] pub use self::date::{DateError, DateErrorKind, JsDate}; pub use self::error::JsError; diff --git a/test/napi/lib/objects.js b/test/napi/lib/objects.js index 18d11f77e..4a68c77c1 100644 --- a/test/napi/lib/objects.js +++ b/test/napi/lib/objects.js @@ -22,7 +22,59 @@ describe('JsObject', function() { assert.deepEqual({number: 9000, string: 'hello node'}, addon.return_js_object_with_mixed_content()); }); - it('gets a 16-byte, zeroed ArrayBuffer', function() { + it('correctly reads a TypedArray using the borrow API', function () { + var b = new ArrayBuffer(32); + var a = new Int32Array(b, 4, 4); + a[0] = 49; + a[1] = 1350; + a[2] = 11; + a[3] = 237; + assert.equal(addon.read_typed_array_with_borrow(a, 0), 49); + assert.equal(addon.read_typed_array_with_borrow(a, 1), 1350); + assert.equal(addon.read_typed_array_with_borrow(a, 2), 11); + assert.equal(addon.read_typed_array_with_borrow(a, 3), 237); + }); + + it('correctly writes to a TypedArray using the borrow_mut API', function () { + var b = new ArrayBuffer(32); + var a = new Int32Array(b, 4, 4); + addon.write_typed_array_with_borrow_mut(a, 0, 43); + assert.equal(a[0], 43); + addon.write_typed_array_with_borrow_mut(a, 1, 1000); + assert.equal(a[1], 1000); + addon.write_typed_array_with_borrow_mut(a, 2, 22); + assert.equal(a[2], 22); + addon.write_typed_array_with_borrow_mut(a, 3, 243); + assert.equal(a[3], 243); + }); + + it('correctly reads a Buffer as a typed array', function () { + var a = Buffer.from([49, 135, 11, 237]); + assert.equal(addon.read_u8_typed_array(a, 0), 49); + assert.equal(addon.read_u8_typed_array(a, 1), 135); + assert.equal(addon.read_u8_typed_array(a, 2), 11); + assert.equal(addon.read_u8_typed_array(a, 3), 237); + }); + + it('copies the contents of one typed array to another', function () { + const a = new Uint32Array([1, 2, 3, 4]); + const b = new Uint32Array(a.length); + + addon.copy_typed_array(a, b); + + assert.deepEqual([...a], [...b]); + }); + + it('cannot borrow overlapping buffers', function () { + const buf = new ArrayBuffer(20); + const arr = new Uint32Array(buf); + const a = new Uint32Array(buf, 4, 2); + const b = new Uint32Array(buf, 8, 2); + + assert.throws(() => addon.copy_typed_array(a, b)); + }); + + it('gets a 16-byte, zeroed ArrayBuffer', function () { var b = addon.return_array_buffer(); assert.equal(b.byteLength, 16); assert.equal((new Uint32Array(b))[0], 0); @@ -38,47 +90,47 @@ describe('JsObject', function() { a[1] = 133; a[2] = 9; a[3] = 88888888; - assert.equal(addon.read_array_buffer_with_lock(b, 0), 47); - assert.equal(addon.read_array_buffer_with_lock(b, 1), 133); - assert.equal(addon.read_array_buffer_with_lock(b, 2), 9); - assert.equal(addon.read_array_buffer_with_lock(b, 3), 88888888); + assert.equal(addon.read_array_buffer_with_lock(a, 0), 47); + assert.equal(addon.read_array_buffer_with_lock(a, 1), 133); + assert.equal(addon.read_array_buffer_with_lock(a, 2), 9); + assert.equal(addon.read_array_buffer_with_lock(a, 3), 88888888); }); it('correctly reads an ArrayBuffer using the borrow API', function() { - var b = new ArrayBuffer(16); - var a = new Uint32Array(b); + var b = new ArrayBuffer(4); + var a = new Uint8Array(b); a[0] = 49; a[1] = 135; a[2] = 11; - a[3] = 89898989; + a[3] = 237; assert.equal(addon.read_array_buffer_with_borrow(b, 0), 49); assert.equal(addon.read_array_buffer_with_borrow(b, 1), 135); assert.equal(addon.read_array_buffer_with_borrow(b, 2), 11); - assert.equal(addon.read_array_buffer_with_borrow(b, 3), 89898989); + assert.equal(addon.read_array_buffer_with_borrow(b, 3), 237); }); - it('correctly writes to an ArrayBuffer using the lock API', function() { + it('correctly writes to an ArrayBuffer using the lock API', function () { var b = new ArrayBuffer(16); - addon.write_array_buffer_with_lock(b, 0, 999); - assert.equal((new Uint32Array(b))[0], 999); - addon.write_array_buffer_with_lock(b, 1, 111); - assert.equal((new Uint32Array(b))[1], 111); - addon.write_array_buffer_with_lock(b, 2, 121212); - assert.equal((new Uint32Array(b))[2], 121212); - addon.write_array_buffer_with_lock(b, 3, 99991111); - assert.equal((new Uint32Array(b))[3], 99991111); + addon.write_array_buffer_with_lock(b, 0, 3); + assert.equal((new Uint8Array(b))[0], 3); + addon.write_array_buffer_with_lock(b, 1, 42); + assert.equal((new Uint8Array(b))[1], 42); + addon.write_array_buffer_with_lock(b, 2, 127); + assert.equal((new Uint8Array(b))[2], 127); + addon.write_array_buffer_with_lock(b, 3, 255); + assert.equal((new Uint8Array(b))[3], 255); }); it('correctly writes to an ArrayBuffer using the borrow_mut API', function() { - var b = new ArrayBuffer(16); - addon.write_array_buffer_with_borrow_mut(b, 0, 434); - assert.equal((new Uint32Array(b))[0], 434); + var b = new ArrayBuffer(4); + addon.write_array_buffer_with_borrow_mut(b, 0, 43); + assert.equal((new Uint8Array(b))[0], 43); addon.write_array_buffer_with_borrow_mut(b, 1, 100); - assert.equal((new Uint32Array(b))[1], 100); + assert.equal((new Uint8Array(b))[1], 100); addon.write_array_buffer_with_borrow_mut(b, 2, 22); - assert.equal((new Uint32Array(b))[2], 22); - addon.write_array_buffer_with_borrow_mut(b, 3, 400100); - assert.equal((new Uint32Array(b))[3], 400100); + assert.equal((new Uint8Array(b))[2], 22); + addon.write_array_buffer_with_borrow_mut(b, 3, 243); + assert.equal((new Uint8Array(b))[3], 243); }); it('gets a 16-byte, uninitialized Buffer', function() { @@ -107,52 +159,47 @@ describe('JsObject', function() { it('correctly reads a Buffer using the lock API', function() { var b = Buffer.allocUnsafe(16); - b.writeUInt32LE(147, 0); - b.writeUInt32LE(1133, 4); - b.writeUInt32LE(109, 8); - b.writeUInt32LE(189189, 12); + b.writeUInt8(147, 0); + b.writeUInt8(113, 1); + b.writeUInt8(109, 2); + b.writeUInt8(189, 3); assert.equal(addon.read_buffer_with_lock(b, 0), 147); - assert.equal(addon.read_buffer_with_lock(b, 1), 1133); + assert.equal(addon.read_buffer_with_lock(b, 1), 113); assert.equal(addon.read_buffer_with_lock(b, 2), 109); - assert.equal(addon.read_buffer_with_lock(b, 3), 189189); + assert.equal(addon.read_buffer_with_lock(b, 3), 189); }); it('correctly reads a Buffer using the borrow API', function() { - var b = Buffer.allocUnsafe(16); - b.writeUInt32LE(149, 0); - b.writeUInt32LE(2244, 4); - b.writeUInt32LE(707, 8); - b.writeUInt32LE(22914478, 12); + var b = Buffer.from([149, 224, 70, 229]); assert.equal(addon.read_buffer_with_borrow(b, 0), 149); - assert.equal(addon.read_buffer_with_borrow(b, 1), 2244); - assert.equal(addon.read_buffer_with_borrow(b, 2), 707); - assert.equal(addon.read_buffer_with_borrow(b, 3), 22914478); + assert.equal(addon.read_buffer_with_borrow(b, 1), 224); + assert.equal(addon.read_buffer_with_borrow(b, 2), 70); + assert.equal(addon.read_buffer_with_borrow(b, 3), 229); }); it('correctly writes to a Buffer using the lock API', function() { var b = Buffer.allocUnsafe(16); b.fill(0); addon.write_buffer_with_lock(b, 0, 6); - assert.equal(b.readUInt32LE(0), 6); - addon.write_buffer_with_lock(b, 1, 6000001); - assert.equal(b.readUInt32LE(4), 6000001); - addon.write_buffer_with_lock(b, 2, 4500); - assert.equal(b.readUInt32LE(8), 4500); - addon.write_buffer_with_lock(b, 3, 421600); - assert.equal(b.readUInt32LE(12), 421600); + assert.equal(b.readUInt8(0), 6); + addon.write_buffer_with_lock(b, 1, 61); + assert.equal(b.readUInt8(1), 61); + addon.write_buffer_with_lock(b, 2, 45); + assert.equal(b.readUInt8(2), 45); + addon.write_buffer_with_lock(b, 3, 216); + assert.equal(b.readUInt8(3), 216); }); it('correctly writes to a Buffer using the borrow_mut API', function() { - var b = Buffer.allocUnsafe(16); - b.fill(0); + var b = Buffer.alloc(4); addon.write_buffer_with_borrow_mut(b, 0, 16); - assert.equal(b.readUInt32LE(0), 16); - addon.write_buffer_with_borrow_mut(b, 1, 16000001); - assert.equal(b.readUInt32LE(4), 16000001); + assert.equal(b[0], 16); + addon.write_buffer_with_borrow_mut(b, 1, 100); + assert.equal(b[1], 100); addon.write_buffer_with_borrow_mut(b, 2, 232); - assert.equal(b.readUInt32LE(8), 232); - addon.write_buffer_with_borrow_mut(b, 3, 66012); - assert.equal(b.readUInt32LE(12), 66012); + assert.equal(b[2], 232); + addon.write_buffer_with_borrow_mut(b, 3, 55); + assert.equal(b[3], 55); }); it('returns only own properties from get_own_property_names', function() { diff --git a/test/napi/lib/types.js b/test/napi/lib/types.js index 7b963840b..3bbb977ae 100644 --- a/test/napi/lib/types.js +++ b/test/napi/lib/types.js @@ -19,6 +19,11 @@ describe('type checks', function() { assert(!addon.is_array_buffer('hello world')); }); + it('is_uint32_array', function () { + assert(addon.is_uint32_array(new Uint32Array(0))); + assert(!addon.is_uint32_array(new Uint16Array(0))); + }); + it('is_boolean', function () { assert(addon.is_boolean(true)); assert(addon.is_boolean(false)); diff --git a/test/napi/src/js/objects.rs b/test/napi/src/js/objects.rs index dbb4db792..f94db5ae2 100644 --- a/test/napi/src/js/objects.rs +++ b/test/napi/src/js/objects.rs @@ -1,4 +1,5 @@ use neon::prelude::*; +use neon::types::buffer::TypedArray; pub fn return_js_global_object(mut cx: FunctionContext) -> JsResult { Ok(cx.global()) @@ -37,58 +38,88 @@ pub fn return_array_buffer(mut cx: FunctionContext) -> JsResult { } pub fn read_array_buffer_with_lock(mut cx: FunctionContext) -> JsResult { - let b: Handle = cx.argument(0)?; - let i = cx.argument::(1)?.value(&mut cx) as u32 as usize; - let x = { - let guard = cx.lock(); - let data = b.borrow(&guard); - let slice = data.as_slice::(); - slice[i] - }; - Ok(cx.number(x)) + let buf = cx.argument::>(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let lock = cx.lock(); + let n = buf.try_borrow(&lock).map(|buf| buf[i]).or_throw(&mut cx)?; + + Ok(cx.number(n)) } pub fn read_array_buffer_with_borrow(mut cx: FunctionContext) -> JsResult { - let b: Handle = cx.argument(0)?; - let i = cx.argument::(1)?.value(&mut cx) as u32 as usize; - let x = cx.borrow(&b, |data| data.as_slice::()[i]); - Ok(cx.number(x)) -} + let buf = cx.argument::(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = buf.as_slice(&cx)[i]; -pub fn sum_array_buffer_with_borrow(mut cx: FunctionContext) -> JsResult { - let b: Handle = cx.argument(0)?; - let x: u8 = cx.borrow(&b, |data| data.as_slice::().iter().sum()); - Ok(cx.number(x)) + Ok(cx.number(n as f64)) } pub fn write_array_buffer_with_lock(mut cx: FunctionContext) -> JsResult { let mut b: Handle = cx.argument(0)?; let i = cx.argument::(1)?.value(&mut cx) as u32 as usize; - let x = cx.argument::(2)?.value(&mut cx) as u32; - { - let guard = cx.lock(); - let data = b.borrow_mut(&guard); - let slice = data.as_mut_slice::(); - slice[i] = x; - } + let x = cx.argument::(2)?.value(&mut cx) as u8; + let lock = cx.lock(); + + b.try_borrow_mut(&lock) + .map(|mut slice| { + slice[i] = x; + }) + .or_throw(&mut cx)?; + Ok(cx.undefined()) } pub fn write_array_buffer_with_borrow_mut(mut cx: FunctionContext) -> JsResult { - let mut b: Handle = cx.argument(0)?; - let i = cx.argument::(1)?.value(&mut cx) as u32 as usize; - let x = cx.argument::(2)?.value(&mut cx) as u32; - cx.borrow_mut(&mut b, |data| { - data.as_mut_slice::()[i] = x; - }); + let mut buf = cx.argument::(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = cx.argument::(2)?.value(&mut cx) as u8; + + buf.as_mut_slice(&mut cx)[i] = n; + Ok(cx.undefined()) } -pub fn increment_array_buffer_with_borrow_mut(mut cx: FunctionContext) -> JsResult { - let mut b: Handle = cx.argument(0)?; - cx.borrow_mut(&mut b, |data| { - data.as_mut_slice::().iter_mut().for_each(|x| *x += 1); - }); +pub fn read_typed_array_with_borrow(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::>(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = buf.as_slice(&cx)[i]; + + Ok(cx.number(n as f64)) +} + +pub fn write_typed_array_with_borrow_mut(mut cx: FunctionContext) -> JsResult { + let mut buf = cx.argument::>(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = cx.argument::(2)?.value(&mut cx) as i32; + + buf.as_mut_slice(&mut cx)[i] = n; + + Ok(cx.undefined()) +} + +pub fn read_u8_typed_array(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::>(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = buf.as_slice(&cx)[i]; + + Ok(cx.number(n as f64)) +} + +pub fn copy_typed_array(mut cx: FunctionContext) -> JsResult { + let source = cx.argument::>(0)?; + let mut dest = cx.argument::>(1)?; + let mut run = || { + let lock = cx.lock(); + let source = source.try_borrow(&lock)?; + let mut dest = dest.try_borrow_mut(&lock)?; + + dest.copy_from_slice(&source); + + Ok(()) + }; + + run().or_throw(&mut cx)?; + Ok(cx.undefined()) } @@ -118,56 +149,43 @@ pub fn return_external_array_buffer(mut cx: FunctionContext) -> JsResult JsResult { let b: Handle = cx.argument(0)?; - let i = cx.argument::(1)?.value(&mut cx) as u32 as usize; - let x = { - let guard = cx.lock(); - let data = b.borrow(&guard); - let slice = data.as_slice::(); - slice[i] - }; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let lock = cx.lock(); + let x = b + .try_borrow(&lock) + .map(|slice| slice[i]) + .or_throw(&mut cx)?; + Ok(cx.number(x)) } pub fn read_buffer_with_borrow(mut cx: FunctionContext) -> JsResult { - let b: Handle = cx.argument(0)?; - let i = cx.argument::(1)?.value(&mut cx) as u32 as usize; - let x = cx.borrow(&b, |data| data.as_slice::()[i]); - Ok(cx.number(x)) -} + let buf = cx.argument::(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = buf.as_slice(&cx)[i]; -pub fn sum_buffer_with_borrow(mut cx: FunctionContext) -> JsResult { - let b: Handle = cx.argument(0)?; - let x: u8 = cx.borrow(&b, |data| data.as_slice::().iter().sum()); - Ok(cx.number(x)) + Ok(cx.number(n as f64)) } pub fn write_buffer_with_lock(mut cx: FunctionContext) -> JsResult { let mut b: Handle = cx.argument(0)?; - let i = cx.argument::(1)?.value(&mut cx) as u32 as usize; - let x = cx.argument::(2)?.value(&mut cx) as u32; - { - let guard = cx.lock(); - let data = b.borrow_mut(&guard); - let slice = data.as_mut_slice::(); - slice[i] = x; - } + let i = cx.argument::(1)?.value(&mut cx) as usize; + let x = cx.argument::(2)?.value(&mut cx) as u8; + let lock = cx.lock(); + + b.try_borrow_mut(&lock) + .map(|mut slice| slice[i] = x) + .or_throw(&mut cx)?; + Ok(cx.undefined()) } pub fn write_buffer_with_borrow_mut(mut cx: FunctionContext) -> JsResult { - let mut b: Handle = cx.argument(0)?; - let i = cx.argument::(1)?.value(&mut cx) as u32 as usize; - let x = cx.argument::(2)?.value(&mut cx) as u32; - cx.borrow_mut(&mut b, |data| { - data.as_mut_slice::()[i] = x; - }); - Ok(cx.undefined()) -} + let mut buf = cx.argument::(0)?; + let i = cx.argument::(1)?.value(&mut cx) as usize; + let n = cx.argument::(2)?.value(&mut cx) as u8; + + buf.as_mut_slice(&mut cx)[i] = n; -pub fn increment_buffer_with_borrow_mut(mut cx: FunctionContext) -> JsResult { - let mut b: Handle = cx.argument(0)?; - cx.borrow_mut(&mut b, |data| { - data.as_mut_slice::().iter_mut().for_each(|x| *x += 1); - }); Ok(cx.undefined()) } diff --git a/test/napi/src/js/types.rs b/test/napi/src/js/types.rs index 9c75fa05a..6115a1f72 100644 --- a/test/napi/src/js/types.rs +++ b/test/napi/src/js/types.rs @@ -18,6 +18,12 @@ pub fn is_array_buffer(mut cx: FunctionContext) -> JsResult { Ok(cx.boolean(result)) } +pub fn is_uint32_array(mut cx: FunctionContext) -> JsResult { + let val: Handle = cx.argument(0)?; + let result = val.is_a::, _>(&mut cx); + Ok(cx.boolean(result)) +} + pub fn is_boolean(mut cx: FunctionContext) -> JsResult { let val: Handle = cx.argument(0)?; let result = val.is_a::(&mut cx); diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index f081c07a1..221ba73a2 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -175,29 +175,26 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { "read_array_buffer_with_borrow", read_array_buffer_with_borrow, )?; - cx.export_function("sum_array_buffer_with_borrow", sum_array_buffer_with_borrow)?; cx.export_function("write_array_buffer_with_lock", write_array_buffer_with_lock)?; cx.export_function( "write_array_buffer_with_borrow_mut", write_array_buffer_with_borrow_mut, )?; + cx.export_function("read_typed_array_with_borrow", read_typed_array_with_borrow)?; cx.export_function( - "increment_array_buffer_with_borrow_mut", - increment_array_buffer_with_borrow_mut, + "write_typed_array_with_borrow_mut", + write_typed_array_with_borrow_mut, )?; + cx.export_function("read_u8_typed_array", read_u8_typed_array)?; + cx.export_function("copy_typed_array", copy_typed_array)?; cx.export_function("return_uninitialized_buffer", return_uninitialized_buffer)?; cx.export_function("return_buffer", return_buffer)?; cx.export_function("return_external_buffer", return_external_buffer)?; cx.export_function("return_external_array_buffer", return_external_array_buffer)?; cx.export_function("read_buffer_with_lock", read_buffer_with_lock)?; cx.export_function("read_buffer_with_borrow", read_buffer_with_borrow)?; - cx.export_function("sum_buffer_with_borrow", sum_buffer_with_borrow)?; cx.export_function("write_buffer_with_lock", write_buffer_with_lock)?; cx.export_function("write_buffer_with_borrow_mut", write_buffer_with_borrow_mut)?; - cx.export_function( - "increment_buffer_with_borrow_mut", - increment_buffer_with_borrow_mut, - )?; cx.export_function("create_date", create_date)?; cx.export_function("get_date_value", get_date_value)?; @@ -211,6 +208,7 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { cx.export_function("is_array", is_array)?; cx.export_function("is_array_buffer", is_array_buffer)?; + cx.export_function("is_uint32_array", is_uint32_array)?; cx.export_function("is_boolean", is_boolean)?; cx.export_function("is_buffer", is_buffer)?; cx.export_function("is_error", is_error)?;