diff --git a/.cargo/config.toml b/.cargo/config.toml index 4f56ff8cf..0ed60eaa5 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,5 +3,5 @@ # The following aliases simplify linting the entire workspace neon-check = " check --all --all-targets --features napi-experimental,futures" neon-clippy = "clippy --all --all-targets --features napi-experimental,futures -- -A clippy::missing_safety_doc" -neon-test = " test --all --features=napi-experimental,futures" -neon-doc = " rustdoc -p neon --features=napi-experimental,futures -- --cfg docsrs" +neon-test = " test --all --features=doc-comment,napi-experimental,futures" +neon-doc = " rustdoc -p neon --features=doc-dependencies,napi-experimental,futures -- --cfg docsrs" diff --git a/crates/neon/Cargo.toml b/crates/neon/Cargo.toml index 718ad4952..0b673d609 100644 --- a/crates/neon/Cargo.toml +++ b/crates/neon/Cargo.toml @@ -25,6 +25,8 @@ semver = "1" smallvec = "1.4.2" once_cell = "1.10.0" neon-macros = { version = "=1.0.0-alpha.1", path = "../neon-macros" } +aquamarine = { version = "0.1.11", optional = true } +doc-comment = { version = "0.3.3", optional = true } [dependencies.tokio] version = "1.18.2" @@ -70,9 +72,13 @@ task-api = [] # DEPRECATED: This is always enabled and should be removed. proc-macros = [] +# Enables the optional dependencies that are only used for generating the API docs. +doc-dependencies = ["doc-comment", "aquamarine"] + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] features = [ "futures", "napi-experimental", + "doc-dependencies", ] diff --git a/crates/neon/src/context/mod.rs b/crates/neon/src/context/mod.rs index c8f325a8c..e51953f47 100644 --- a/crates/neon/src/context/mod.rs +++ b/crates/neon/src/context/mod.rs @@ -322,12 +322,12 @@ pub trait Context<'a>: ContextInternal<'a> { /// Convenience method for creating a `JsNull` value. fn null(&mut self) -> Handle<'a, JsNull> { - return JsNull::new(self); + JsNull::new(self) } /// Convenience method for creating a `JsUndefined` value. fn undefined(&mut self) -> Handle<'a, JsUndefined> { - return JsUndefined::new(self); + JsUndefined::new(self) } /// Convenience method for creating an empty `JsObject` value. diff --git a/crates/neon/src/handle/mod.rs b/crates/neon/src/handle/mod.rs index 0d61fded0..57f6aaa67 100644 --- a/crates/neon/src/handle/mod.rs +++ b/crates/neon/src/handle/mod.rs @@ -88,7 +88,7 @@ pub struct Handle<'a, T: Managed + 'a> { impl<'a, T: Managed> Clone for Handle<'a, T> { fn clone(&self) -> Self { Self { - value: self.value.clone(), + value: self.value, phantom: PhantomData, } } diff --git a/crates/neon/src/lib.rs b/crates/neon/src/lib.rs index f34d75744..dd1f9d0d7 100644 --- a/crates/neon/src/lib.rs +++ b/crates/neon/src/lib.rs @@ -89,7 +89,13 @@ pub mod result; mod sys; #[cfg(feature = "napi-6")] pub mod thread; -pub mod types; +// To use the #[aquamarine] attribute on the top-level neon::types module docs, we have to +// use this hack so we can keep the module docs in a separate file. +// See: https://github.com/mersinvald/aquamarine/issues/5#issuecomment-1168816499 +mod types_docs; +mod types_impl; + +pub use types_docs::exports as types; #[doc(hidden)] pub mod macro_internal; diff --git a/crates/neon/src/lifecycle.rs b/crates/neon/src/lifecycle.rs index ae2fc877b..f81c6ea86 100644 --- a/crates/neon/src/lifecycle.rs +++ b/crates/neon/src/lifecycle.rs @@ -131,7 +131,7 @@ impl LocalCell { // Kick off a new transaction and drop it before getting the result. { let mut tx = TryInitTransaction::new(cx, id); - tx.run(|cx| Ok(f(cx)?))?; + tx.run(|cx| f(cx))?; } // If we're here, the transaction has succeeded, so get the result. @@ -195,11 +195,9 @@ impl<'cx, 'a, C: Context<'cx>> TryInitTransaction<'cx, 'a, C> { InstanceData::locals(self.cx).get(self.id) } + #[allow(clippy::wrong_self_convention)] fn is_trying(&mut self) -> bool { - match self.cell() { - LocalCell::Trying => true, - _ => false, - } + matches!(self.cell(), LocalCell::Trying) } } diff --git a/crates/neon/src/prelude.rs b/crates/neon/src/prelude.rs index 13ad48f43..01b87f55e 100644 --- a/crates/neon/src/prelude.rs +++ b/crates/neon/src/prelude.rs @@ -11,8 +11,10 @@ pub use crate::{ result::{JsResult, NeonResult, ResultExt as NeonResultExt}, types::{ boxed::{Finalize, JsBox}, - JsArray, JsArrayBuffer, JsBoolean, JsBuffer, JsError, JsFunction, JsNull, JsNumber, - JsObject, JsPromise, JsString, JsTypedArray, JsUndefined, JsValue, Value, + JsArray, JsArrayBuffer, JsBigInt64Array, JsBigUint64Array, JsBoolean, JsBuffer, JsError, + JsFloat32Array, JsFloat64Array, JsFunction, JsInt16Array, JsInt32Array, JsInt8Array, + JsNull, JsNumber, JsObject, JsPromise, JsString, JsTypedArray, JsUint16Array, + JsUint32Array, JsUint8Array, JsUndefined, JsValue, Value, }, }; diff --git a/crates/neon/src/sys/arraybuffer.rs b/crates/neon/src/sys/arraybuffer.rs index 98fb4a147..6c911af42 100644 --- a/crates/neon/src/sys/arraybuffer.rs +++ b/crates/neon/src/sys/arraybuffer.rs @@ -65,3 +65,17 @@ pub unsafe fn as_mut_slice<'a>(env: Env, buf: Local) -> &'a mut [u8] { slice::from_raw_parts_mut(data.assume_init().cast(), size) } + +/// # Safety +/// * Caller must ensure `env` and `buf` are valid +pub unsafe fn size(env: Env, buf: Local) -> usize { + 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, + ); + + size +} diff --git a/crates/neon/src/sys/bindings/functions.rs b/crates/neon/src/sys/bindings/functions.rs index fcf05834b..90399b61a 100644 --- a/crates/neon/src/sys/bindings/functions.rs +++ b/crates/neon/src/sys/bindings/functions.rs @@ -90,6 +90,15 @@ mod napi1 { byte_length: *mut usize, ) -> Status; + fn create_typedarray( + env: Env, + type_: TypedArrayType, + length: usize, + arraybuffer: Value, + byte_offset: usize, + result: *mut Value, + ) -> Status; + fn get_typedarray_info( env: Env, typedarray: Value, diff --git a/crates/neon/src/sys/bindings/mod.rs b/crates/neon/src/sys/bindings/mod.rs index 2e26252e6..1f7bb3436 100644 --- a/crates/neon/src/sys/bindings/mod.rs +++ b/crates/neon/src/sys/bindings/mod.rs @@ -3,7 +3,7 @@ //! These types are manually copied from bindings generated from `bindgen`. To //! update, use the following approach: //! -//! * Run `cargo build` with `--cfg neon=dev` at least once to install `nodejs-sys` +//! * Run a debug build of Neon at least once to install `nodejs-sys` //! * Open the generated bindings at `target/debug/build/nodejs-sys-*/out/bindings.rs` //! * Copy the types needed into `types.rs` and `functions.rs` //! * Modify to match Rust naming conventions: diff --git a/crates/neon/src/sys/buffer.rs b/crates/neon/src/sys/buffer.rs index a991c4a00..7bd43fff5 100644 --- a/crates/neon/src/sys/buffer.rs +++ b/crates/neon/src/sys/buffer.rs @@ -74,3 +74,17 @@ pub unsafe fn as_mut_slice<'a>(env: Env, buf: Local) -> &'a mut [u8] { slice::from_raw_parts_mut(data.assume_init().cast(), size) } + +/// # Safety +/// * Caller must ensure `env` and `buf` are valid +pub unsafe fn size(env: Env, buf: Local) -> usize { + 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, + ); + + size +} diff --git a/crates/neon/src/sys/no_panic.rs b/crates/neon/src/sys/no_panic.rs index 02fcb66bc..682d66074 100644 --- a/crates/neon/src/sys/no_panic.rs +++ b/crates/neon/src/sys/no_panic.rs @@ -210,13 +210,11 @@ unsafe fn error_from_message(env: Env, msg: &str) -> Local { let status = napi::create_error(env, ptr::null_mut(), msg, err.as_mut_ptr()); - let err = if status == napi::Status::Ok { + if status == napi::Status::Ok { err.assume_init() } else { fatal_error("Failed to create an Error"); - }; - - err + } } #[track_caller] @@ -246,7 +244,7 @@ unsafe fn panic_msg(panic: &Panic) -> Option<&str> { if let Some(msg) = panic.downcast_ref::<&str>() { Some(msg) } else if let Some(msg) = panic.downcast_ref::() { - Some(&msg) + Some(msg) } else { None } diff --git a/crates/neon/src/sys/tsfn.rs b/crates/neon/src/sys/tsfn.rs index 885994875..b58b45469 100644 --- a/crates/neon/src/sys/tsfn.rs +++ b/crates/neon/src/sys/tsfn.rs @@ -78,7 +78,7 @@ impl ThreadsafeFunction { Self { tsfn: Tsfn(result.assume_init()), - is_finalized: is_finalized, + is_finalized, callback, } } diff --git a/crates/neon/src/sys/typedarray.rs b/crates/neon/src/sys/typedarray.rs index d27109dd9..196484406 100644 --- a/crates/neon/src/sys/typedarray.rs +++ b/crates/neon/src/sys/typedarray.rs @@ -39,3 +39,22 @@ pub unsafe fn info(env: Env, value: Local) -> TypedArrayInfo { info.assume_init() } + +pub unsafe fn new( + env: Env, + typ: TypedArrayType, + buffer: Local, + offset: usize, + len: usize, +) -> Result { + let mut array = MaybeUninit::uninit(); + let status = napi::create_typedarray(env, typ, len, buffer, offset, array.as_mut_ptr()); + + if status == napi::Status::PendingException { + return Err(status); + } + + assert_eq!(status, napi::Status::Ok); + + Ok(array.assume_init()) +} diff --git a/crates/neon/src/types/buffer/mod.rs b/crates/neon/src/types/buffer/mod.rs deleted file mode 100644 index e595f54d8..000000000 --- a/crates/neon/src/types/buffer/mod.rs +++ /dev/null @@ -1,157 +0,0 @@ -use std::{ - cell::RefCell, - error::Error, - fmt::{self, Debug, Display}, - ops::{Deref, DerefMut}, -}; - -use crate::{ - context::Context, - result::{NeonResult, ResultExt}, - types::buffer::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<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] - where - C: Context<'cx>; - - /// 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<'cx, 'a, C>(&mut self, cx: &'a mut C) -> &'a mut [Self::Item] - where - C: Context<'cx>; - - /// 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<'cx, 'a, C>(&self, lock: &'a Lock) -> Result, BorrowError> - where - C: Context<'cx>; - - /// 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<'cx, 'a, C>( - &mut self, - lock: &'a Lock, - ) -> Result, BorrowError> - where - C: Context<'cx>; -} - -#[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/crates/neon/src/types/buffer/types.rs b/crates/neon/src/types/buffer/types.rs deleted file mode 100644 index dd7813367..000000000 --- a/crates/neon/src/types/buffer/types.rs +++ /dev/null @@ -1,385 +0,0 @@ -use std::{marker::PhantomData, slice}; - -use crate::{ - context::{internal::Env, Context}, - handle::{internal::TransparentNoCopyWrapper, Handle, Managed}, - result::{JsResult, Throw}, - sys::{self, raw, TypedArrayType}, - types::buffer::{ - lock::{Ledger, Lock}, - private, BorrowError, Ref, RefMut, TypedArray, - }, - types::{private::ValueInternal, Object, Value}, -}; - -/// The Node [`Buffer`](https://nodejs.org/api/buffer.html) type. -#[derive(Debug)] -#[repr(transparent)] -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 { sys::buffer::new(cx.env().to_raw(), len) }; - - if let Ok(buf) = result { - Ok(Handle::new_internal(Self(buf))) - } else { - Err(Throw::new()) - } - } - - /// 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 = sys::buffer::uninitialized(cx.env().to_raw(), len); - - if let Ok((buf, _)) = result { - Ok(Handle::new_internal(Self(buf))) - } else { - Err(Throw::new()) - } - } - - /// 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 + 'static, - { - let env = cx.env().to_raw(); - let value = unsafe { sys::buffer::new_external(env, data) }; - - Handle::new_internal(Self(value)) - } -} - -unsafe impl TransparentNoCopyWrapper for JsBuffer { - type Inner = raw::Local; - - fn into_inner(self) -> Self::Inner { - self.0 - } -} - -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 { sys::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<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] - where - C: Context<'cx>, - { - // # Safety - // Only the `Context` with the *most* narrow scope is accessible because `compute_scoped` - // and `execute_scope` take an exclusive reference to `Context`. A handle is always - // associated with a `Context` and the value will not be garbage collected while that - // `Context` is in scope. This means that the referenced data is valid *at least* as long - // as `Context`, even if the `Handle` is dropped. - unsafe { sys::buffer::as_mut_slice(cx.env().to_raw(), self.to_raw()) } - } - - fn as_mut_slice<'cx, 'a, C>(&mut self, cx: &'a mut C) -> &'a mut [Self::Item] - where - C: Context<'cx>, - { - // # Safety - // See `as_slice` - unsafe { sys::buffer::as_mut_slice(cx.env().to_raw(), self.to_raw()) } - } - - fn try_borrow<'cx, 'a, C>(&self, lock: &'a Lock) -> Result, BorrowError> - where - C: Context<'cx>, - { - // The borrowed data must be guarded by `Ledger` before returning - Ledger::try_borrow(&lock.ledger, unsafe { - sys::buffer::as_mut_slice(lock.cx.env().to_raw(), self.to_raw()) - }) - } - - fn try_borrow_mut<'cx, 'a, C>( - &mut self, - lock: &'a Lock, - ) -> Result, BorrowError> - where - C: Context<'cx>, - { - // The borrowed data must be guarded by `Ledger` before returning - Ledger::try_borrow_mut(&lock.ledger, unsafe { - sys::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. -#[derive(Debug)] -#[repr(transparent)] -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 { sys::arraybuffer::new(cx.env().to_raw(), len) }; - - if let Ok(buf) = result { - Ok(Handle::new_internal(Self(buf))) - } else { - Err(Throw::new()) - } - } - - /// 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 + 'static, - { - let env = cx.env().to_raw(); - let value = unsafe { sys::arraybuffer::new_external(env, data) }; - - Handle::new_internal(Self(value)) - } -} - -unsafe impl TransparentNoCopyWrapper for JsArrayBuffer { - type Inner = raw::Local; - - fn into_inner(self) -> Self::Inner { - self.0 - } -} - -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 { sys::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<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] - where - C: Context<'cx>, - { - unsafe { sys::arraybuffer::as_mut_slice(cx.env().to_raw(), self.to_raw()) } - } - - fn as_mut_slice<'cx, 'a, C>(&mut self, cx: &'a mut C) -> &'a mut [Self::Item] - where - C: Context<'cx>, - { - unsafe { sys::arraybuffer::as_mut_slice(cx.env().to_raw(), self.to_raw()) } - } - - fn try_borrow<'cx, 'a, C>(&self, lock: &'a Lock) -> Result, BorrowError> - where - C: Context<'cx>, - { - // The borrowed data must be guarded by `Ledger` before returning - Ledger::try_borrow(&lock.ledger, unsafe { - sys::arraybuffer::as_mut_slice(lock.cx.env().to_raw(), self.to_raw()) - }) - } - - fn try_borrow_mut<'cx, 'a, C>( - &mut self, - lock: &'a Lock, - ) -> Result, BorrowError> - where - C: Context<'cx>, - { - // The borrowed data must be guarded by `Ledger` before returning - Ledger::try_borrow_mut(&lock.ledger, unsafe { - sys::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. -#[derive(Debug)] -#[repr(transparent)] -pub struct JsTypedArray { - local: raw::Local, - _type: PhantomData, -} - -impl private::Sealed for JsTypedArray {} - -unsafe impl TransparentNoCopyWrapper for JsTypedArray { - type Inner = raw::Local; - - fn into_inner(self) -> Self::Inner { - self.local - } -} - -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<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] - where - C: Context<'cx>, - { - unsafe { - let env = cx.env().to_raw(); - let value = self.to_raw(); - let info = sys::typedarray::info(env, value); - - slice::from_raw_parts(info.data.cast(), info.length) - } - } - - fn as_mut_slice<'cx, 'a, C>(&mut self, cx: &'a mut C) -> &'a mut [Self::Item] - where - C: Context<'cx>, - { - unsafe { - let env = cx.env().to_raw(); - let value = self.to_raw(); - let info = sys::typedarray::info(env, value); - - slice::from_raw_parts_mut(info.data.cast(), info.length) - } - } - - fn try_borrow<'cx, 'b, C>( - &self, - lock: &'b Lock<'b, C>, - ) -> Result, BorrowError> - where - C: Context<'cx>, - { - unsafe { - let env = lock.cx.env().to_raw(); - let value = self.to_raw(); - let info = sys::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<'cx, 'a, C>( - &mut self, - lock: &'a Lock<'a, C>, - ) -> Result, BorrowError> - where - C: Context<'cx>, - { - unsafe { - let env = lock.cx.env().to_raw(); - let value = self.to_raw(); - let info = sys::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 { !sys::tag::is_typedarray(env, other) } { - return false; - } - - let info = unsafe { sys::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/crates/neon/src/types_docs.rs b/crates/neon/src/types_docs.rs new file mode 100644 index 000000000..6e8f4f28f --- /dev/null +++ b/crates/neon/src/types_docs.rs @@ -0,0 +1,142 @@ +#[cfg_attr(feature = "aquamarine", aquamarine::aquamarine)] +/// Representations of JavaScript's core builtin types. +/// +/// ## Modeling JavaScript Types +/// +/// All JavaScript values in Neon implement the abstract [`Value`](crate::types::Value) +/// trait, which is the most generic way to work with JavaScript values. Neon provides a +/// number of types that implement this trait, each representing a particular +/// type of JavaScript value. +/// +/// By convention, JavaScript types in Neon have the prefix `Js` in their name, +/// such as [`JsNumber`](crate::types::JsNumber) (for the JavaScript `number` +/// type) or [`JsFunction`](crate::types::JsFunction) (for the JavaScript +/// `function` type). +/// +/// ### Handles and Casts +/// +/// Access to JavaScript values in Neon works through [handles](crate::handle), +/// which ensure the safe interoperation between Rust and the JavaScript garbage +/// collector. This means, for example, a Rust variable that stores a JavaScript string +/// will have the type `Handle` rather than [`JsString`](crate::types::JsString). +/// +/// Neon types model the JavaScript type hierarchy through the use of *casts*. +/// The [`Handle::upcast()`](crate::handle::Handle::upcast) method safely converts +/// a handle to a JavaScript value of one type into a handle to a value of its +/// supertype. For example, it's safe to treat a [`JsArray`](crate::types::JsArray) +/// as a [`JsObject`](crate::types::JsObject), so you can do an "upcast" and it will +/// never fail: +/// +/// ``` +/// # use neon::prelude::*; +/// fn as_object(array: Handle) -> Handle { +/// let object: Handle = array.upcast(); +/// object +/// } +/// ``` +/// +/// Unlike upcasts, the [`Handle::downcast()`](crate::handle::Handle::downcast) method +/// requires a runtime check to test a value's type at runtime, so it can fail with +/// a [`DowncastError`](crate::handle::DowncastError): +/// +/// ``` +/// # use neon::prelude::*; +/// fn as_array<'a>( +/// cx: &mut impl Context<'a>, +/// object: Handle<'a, JsObject> +/// ) -> JsResult<'a, JsArray> { +/// object.downcast(cx).or_throw(cx) +/// } +/// ``` +/// +/// ### The JavaScript Type Hierarchy +/// +/// The top of the JavaScript type hierarchy is modeled with the Neon type +/// [`JsValue`](crate::types::JsValue). A [handle](crate::handle) to a `JsValue` +/// can refer to any JavaScript value. (For TypeScript programmers, this can be +/// thought of as similar to TypeScript's [`unknown`][unknown] type.) +/// +/// From there, the type hierarchy divides into _object types_ and _primitive +/// types_: +/// +/// ```mermaid +/// flowchart LR +/// JsValue(JsValue) +/// JsValue-->JsObject(JsObject) +/// click JsValue "./struct.JsValue.html" "JsValue" +/// click JsObject "./struct.JsObject.html" "JsObject" +/// subgraph primitives [Primitive Types] +/// JsBoolean(JsBoolean) +/// JsNumber(JsNumber) +/// JsString(JsString) +/// JsNull(JsNull) +/// JsUndefined(JsUndefined) +/// click JsBoolean "./struct.JsBoolean.html" "JsBoolean" +/// click JsNumber "./struct.JsNumber.html" "JsNumber" +/// click JsString "./struct.JsString.html" "JsString" +/// click JsNull "./struct.JsNull.html" "JsNull" +/// click JsUndefined "./struct.JsUndefined.html" "JsUndefined" +/// end +/// JsValue-->primitives +/// ``` +/// +/// The top of the object type hierarchy is [`JsObject`](crate::types::JsObject). A +/// handle to a `JsObject` can refer to any JavaScript object. +/// +/// The primitive types are the built-in JavaScript datatypes that are not object +/// types: [`JsBoolean`](crate::types::JsBoolean), [`JsNumber`](crate::types::JsNumber), +/// [`JsString`](crate::types::JsString), [`JsNull`](crate::types::JsNull), and +/// [`JsUndefined`](crate::types::JsUndefined). +/// +/// #### Object Types +/// +/// The object type hierarchy further divides into a variety of different subtypes: +/// +/// ```mermaid +/// flowchart LR +/// JsObject(JsObject) +/// click JsObject "./struct.JsObject.html" "JsObject" +/// subgraph objects [Standard Object Types] +/// JsFunction(JsFunction) +/// JsArray(JsArray) +/// JsDate(JsDate) +/// JsError(JsError) +/// click JsFunction "./struct.JsFunction.html" "JsFunction" +/// click JsArray "./struct.JsArray.html" "JsArray" +/// click JsDate "./struct.JsDate.html" "JsDate" +/// click JsError "./struct.JsError.html" "JsError" +/// end +/// subgraph typedarrays [Typed Arrays] +/// JsBuffer(JsBuffer) +/// JsArrayBuffer(JsArrayBuffer) +/// JsTypedArray("JsTypedArray<T>") +/// click JsBuffer "./struct.JsBuffer.html" "JsBuffer" +/// click JsArrayBuffer "./struct.JsArrayBuffer.html" "JsArrayBuffer" +/// click JsTypedArray "./struct.JsTypedArray.html" "JsTypedArray" +/// end +/// subgraph custom [Custom Types] +/// JsBox(JsBox) +/// click JsBox "./struct.JsBox.html" "JsBox" +/// end +/// JsObject-->objects +/// JsObject-->typedarrays +/// JsObject-->custom +/// ``` +/// +/// These include several categories of object types: +/// - **Standard object types:** [`JsFunction`](crate::types::JsFunction), +/// [`JsArray`](crate::types::JsArray), [`JsDate`](crate::types::JsDate), and +/// [`JsError`](crate::types::JsError). +/// - **Typed arrays:** [`JsBuffer`](crate::types::JsBuffer), +/// [`JsArrayBuffer`](crate::types::JsArrayBuffer), and +/// [`JsTypedArray`](crate::types::JsTypedArray). +/// - **Custom types:** [`JsBox`](crate::types::JsBox), a special Neon type that allows +/// the creation of custom objects that own Rust data structures. +/// +/// All object types implement the [`Object`](crate::object::Object) trait, which +/// allows getting and setting properties of an object. +/// +/// [unknown]: https://mariusschulz.com/blog/the-unknown-type-in-typescript#the-unknown-type +pub mod exports { + pub use crate::types_impl::*; +} diff --git a/crates/neon/src/types/boxed.rs b/crates/neon/src/types_impl/boxed.rs similarity index 100% rename from crates/neon/src/types/boxed.rs rename to crates/neon/src/types_impl/boxed.rs diff --git a/crates/neon/src/types/buffer/lock.rs b/crates/neon/src/types_impl/buffer/lock.rs similarity index 100% rename from crates/neon/src/types/buffer/lock.rs rename to crates/neon/src/types_impl/buffer/lock.rs diff --git a/crates/neon/src/types_impl/buffer/mod.rs b/crates/neon/src/types_impl/buffer/mod.rs new file mode 100644 index 000000000..6942e33b8 --- /dev/null +++ b/crates/neon/src/types_impl/buffer/mod.rs @@ -0,0 +1,298 @@ +//! Types and traits for working with binary buffers. + +use std::{ + cell::RefCell, + error::Error, + fmt::{self, Debug, Display}, + marker::PhantomData, + ops::{Deref, DerefMut}, +}; + +use crate::{ + context::Context, + handle::Handle, + result::{JsResult, NeonResult, ResultExt}, + types::{ + buffer::lock::{Ledger, Lock}, + JsArrayBuffer, JsTypedArray, + }, +}; + +pub(crate) mod lock; +pub(super) mod types; + +pub use types::Binary; + +/// A trait allowing Rust to borrow binary data from the memory buffer of JavaScript +/// [typed arrays][typed-arrays]. +/// +/// This trait provides both statically and dynamically checked borrowing. As usual +/// in Rust, mutable borrows are guaranteed not to overlap with other borrows. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn double(mut cx: FunctionContext) -> JsResult { +/// let mut array: Handle = cx.argument(0)?; +/// +/// for elem in array.as_mut_slice(&mut cx).iter_mut() { +/// *elem *= 2; +/// } +/// +/// Ok(cx.undefined()) +/// } +/// ``` +/// +/// [typed-arrays]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays +pub trait TypedArray: private::Sealed { + type Item: Binary; + + /// 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<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] + where + C: Context<'cx>; + + /// 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<'cx, 'a, C>(&mut self, cx: &'a mut C) -> &'a mut [Self::Item] + where + C: Context<'cx>; + + /// 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<'cx, 'a, C>(&self, lock: &'a Lock) -> Result, BorrowError> + where + C: Context<'cx>; + + /// 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<'cx, 'a, C>( + &mut self, + lock: &'a Lock, + ) -> Result, BorrowError> + where + C: Context<'cx>; + + /// Returns the size, in bytes, of the allocated binary data. + fn size<'cx, C>(&self, cx: &mut C) -> usize + where + C: Context<'cx>; +} + +#[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")) + } +} + +/// Represents a typed region of an [`ArrayBuffer`](crate::types::JsArrayBuffer). +/// +/// A `Region` can be created via the +/// [`Handle::region()`](crate::handle::Handle::region) or +/// [`JsTypedArray::region()`](crate::types::JsTypedArray::region) methods. +/// +/// A region is **not** checked for validity until it is converted to +/// a typed array via [`to_typed_array()`](Region::to_typed_array) or +/// [`JsTypedArray::from_region()`](crate::types::JsTypedArray::from_region). +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// # fn f(mut cx: FunctionContext) -> JsResult { +/// // Allocate a 16-byte ArrayBuffer and a uint32 array of length 2 (i.e., 8 bytes) +/// // starting at byte offset 4 of the buffer: +/// // +/// // 0 4 8 12 16 +/// // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// // buf: | | | | | | | | | | | | | | | | | +/// // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// // ^ ^ +/// // | | +/// // +-------+-------+ +/// // arr: | | | +/// // +-------+-------+ +/// // 0 1 2 +/// let buf = cx.array_buffer(16)?; +/// let arr = JsUint32Array::from_region(&mut cx, &buf.region(4, 2))?; +/// # Ok(arr) +/// # } +/// ``` +#[derive(Clone, Copy)] +pub struct Region<'cx, T: Binary> { + buffer: Handle<'cx, JsArrayBuffer>, + offset: usize, + len: usize, + phantom: PhantomData, +} + +impl<'cx, T: Binary> Region<'cx, T> { + /// Returns the handle to the region's buffer. + pub fn buffer(&self) -> Handle<'cx, JsArrayBuffer> { + self.buffer + } + + /// Returns the starting byte offset of the region. + pub fn offset(&self) -> usize { + self.offset + } + + /// Returns the number of elements of type `T` in the region. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.len + } + + /// Returns the size of the region in bytes, which is equal to + /// `(self.len() * size_of::())`. + pub fn size(&self) -> usize { + self.len * std::mem::size_of::() + } + + /// Constructs a typed array for this buffer region. + /// + /// The resulting typed array has `self.len()` elements and a size of + /// `self.size()` bytes. + /// + /// Throws an exception if the region is invalid, for example if the starting + /// offset is not properly aligned, or the length goes beyond the end of the + /// buffer. + pub fn to_typed_array<'c, C>(&self, cx: &mut C) -> JsResult<'c, JsTypedArray> + where + C: Context<'c>, + { + JsTypedArray::from_region(cx, self) + } +} + +mod private { + use super::Binary; + use crate::sys::raw; + use std::fmt::{Debug, Formatter}; + use std::marker::PhantomData; + + pub trait Sealed {} + + #[derive(Clone)] + pub struct JsTypedArrayInner { + pub(super) local: raw::Local, + pub(super) buffer: raw::Local, + pub(super) _type: PhantomData, + } + + impl Debug for JsTypedArrayInner { + fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { + f.write_str("JsTypedArrayInner { ")?; + f.write_str("local: ")?; + self.local.fmt(f)?; + f.write_str(", buffer: ")?; + self.buffer.fmt(f)?; + f.write_str(", _type: PhantomData")?; + f.write_str(" }")?; + Ok(()) + } + } + + impl Copy for JsTypedArrayInner {} +} diff --git a/crates/neon/src/types_impl/buffer/types.rs b/crates/neon/src/types_impl/buffer/types.rs new file mode 100644 index 000000000..a84c964c3 --- /dev/null +++ b/crates/neon/src/types_impl/buffer/types.rs @@ -0,0 +1,787 @@ +use std::{marker::PhantomData, slice}; + +use crate::{ + context::{internal::Env, Context}, + handle::{internal::TransparentNoCopyWrapper, Handle, Managed}, + object::Object, + result::{JsResult, Throw}, + sys::{self, raw, TypedArrayType}, + types_impl::{ + buffer::{ + lock::{Ledger, Lock}, + private::{self, JsTypedArrayInner}, + BorrowError, Ref, RefMut, Region, TypedArray, + }, + private::ValueInternal, + Value, + }, +}; + +#[cfg(feature = "doc-comment")] +use doc_comment::doc_comment; + +#[cfg(not(feature = "doc-comment"))] +macro_rules! doc_comment { + {$comment:expr, $decl:item} => { $decl }; +} + +/// The Node [`Buffer`](https://nodejs.org/api/buffer.html) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn make_sequence(mut cx: FunctionContext) -> JsResult { +/// let len = cx.argument::(0)?.value(&mut cx); +/// let mut buffer = cx.buffer(len as usize)?; +/// +/// for (i, elem) in buffer.as_mut_slice(&mut cx).iter_mut().enumerate() { +/// *elem = i as u8; +/// } +/// +/// Ok(buffer) +/// } +/// ``` +#[derive(Debug)] +#[repr(transparent)] +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 { sys::buffer::new(cx.env().to_raw(), len) }; + + if let Ok(buf) = result { + Ok(Handle::new_internal(Self(buf))) + } else { + Err(Throw::new()) + } + } + + /// 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 = sys::buffer::uninitialized(cx.env().to_raw(), len); + + if let Ok((buf, _)) = result { + Ok(Handle::new_internal(Self(buf))) + } else { + Err(Throw::new()) + } + } + + /// 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 + 'static, + { + let env = cx.env().to_raw(); + let value = unsafe { sys::buffer::new_external(env, data) }; + + Handle::new_internal(Self(value)) + } +} + +unsafe impl TransparentNoCopyWrapper for JsBuffer { + type Inner = raw::Local; + + fn into_inner(self) -> Self::Inner { + self.0 + } +} + +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 { sys::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<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] + where + C: Context<'cx>, + { + // # Safety + // Only the `Context` with the *most* narrow scope is accessible because `compute_scoped` + // and `execute_scope` take an exclusive reference to `Context`. A handle is always + // associated with a `Context` and the value will not be garbage collected while that + // `Context` is in scope. This means that the referenced data is valid *at least* as long + // as `Context`, even if the `Handle` is dropped. + unsafe { sys::buffer::as_mut_slice(cx.env().to_raw(), self.to_raw()) } + } + + fn as_mut_slice<'cx, 'a, C>(&mut self, cx: &'a mut C) -> &'a mut [Self::Item] + where + C: Context<'cx>, + { + // # Safety + // See `as_slice` + unsafe { sys::buffer::as_mut_slice(cx.env().to_raw(), self.to_raw()) } + } + + fn try_borrow<'cx, 'a, C>(&self, lock: &'a Lock) -> Result, BorrowError> + where + C: Context<'cx>, + { + // The borrowed data must be guarded by `Ledger` before returning + Ledger::try_borrow(&lock.ledger, unsafe { + sys::buffer::as_mut_slice(lock.cx.env().to_raw(), self.to_raw()) + }) + } + + fn try_borrow_mut<'cx, 'a, C>( + &mut self, + lock: &'a Lock, + ) -> Result, BorrowError> + where + C: Context<'cx>, + { + // The borrowed data must be guarded by `Ledger` before returning + Ledger::try_borrow_mut(&lock.ledger, unsafe { + sys::buffer::as_mut_slice(lock.cx.env().to_raw(), self.to_raw()) + }) + } + + fn size<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { + unsafe { sys::buffer::size(cx.env().to_raw(), self.to_raw()) } + } +} + +/// The standard JS [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) type. +/// +/// # Example +/// +/// ``` +/// # use neon::prelude::*; +/// use neon::types::buffer::TypedArray; +/// +/// fn make_sequence(mut cx: FunctionContext) -> JsResult { +/// let len = cx.argument::(0)?.value(&mut cx); +/// let mut buffer = cx.array_buffer(len as usize)?; +/// +/// for (i, elem) in buffer.as_mut_slice(&mut cx).iter_mut().enumerate() { +/// *elem = i as u8; +/// } +/// +/// Ok(buffer) +/// } +/// ``` +#[derive(Debug)] +#[repr(transparent)] +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 { sys::arraybuffer::new(cx.env().to_raw(), len) }; + + if let Ok(buf) = result { + Ok(Handle::new_internal(Self(buf))) + } else { + Err(Throw::new()) + } + } + + /// 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 + 'static, + { + let env = cx.env().to_raw(); + let value = unsafe { sys::arraybuffer::new_external(env, data) }; + + Handle::new_internal(Self(value)) + } + + /// Returns a region of this buffer. + /// + /// See also: [`Handle::region()`](Handle::region) for a more + /// ergonomic form of this method. + pub fn region<'cx, T: Binary>( + buffer: &Handle<'cx, JsArrayBuffer>, + offset: usize, + len: usize, + ) -> Region<'cx, T> { + buffer.region(offset, len) + } +} + +impl<'cx> Handle<'cx, JsArrayBuffer> { + /// Returns a [`Region`](crate::types::buffer::Region) representing a typed + /// region of this buffer, starting at `offset` and containing `len` elements + /// of type `T`. + /// + /// The region is **not** checked for validity by this method. Regions are only + /// validated when they are converted to typed arrays. + /// + /// # Example + /// + /// ``` + /// # use neon::prelude::*; + /// # fn f(mut cx: FunctionContext) -> JsResult { + /// let buf: Handle = cx.argument(0)?; + /// let region = buf.region::(64, 8); + /// println!("offset={}, len={}, size={}", region.offset(), region.len(), region.size()); + /// # Ok(cx.undefined()) + /// # } + /// ``` + /// + /// See the [`Region`](Region) documentation for more information. + pub fn region(&self, offset: usize, len: usize) -> Region<'cx, T> { + Region { + buffer: *self, + offset, + len, + phantom: PhantomData, + } + } +} + +unsafe impl TransparentNoCopyWrapper for JsArrayBuffer { + type Inner = raw::Local; + + fn into_inner(self) -> Self::Inner { + self.0 + } +} + +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 { sys::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<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] + where + C: Context<'cx>, + { + unsafe { sys::arraybuffer::as_mut_slice(cx.env().to_raw(), self.to_raw()) } + } + + fn as_mut_slice<'cx, 'a, C>(&mut self, cx: &'a mut C) -> &'a mut [Self::Item] + where + C: Context<'cx>, + { + unsafe { sys::arraybuffer::as_mut_slice(cx.env().to_raw(), self.to_raw()) } + } + + fn try_borrow<'cx, 'a, C>(&self, lock: &'a Lock) -> Result, BorrowError> + where + C: Context<'cx>, + { + // The borrowed data must be guarded by `Ledger` before returning + Ledger::try_borrow(&lock.ledger, unsafe { + sys::arraybuffer::as_mut_slice(lock.cx.env().to_raw(), self.to_raw()) + }) + } + + fn try_borrow_mut<'cx, 'a, C>( + &mut self, + lock: &'a Lock, + ) -> Result, BorrowError> + where + C: Context<'cx>, + { + // The borrowed data must be guarded by `Ledger` before returning + Ledger::try_borrow_mut(&lock.ledger, unsafe { + sys::arraybuffer::as_mut_slice(lock.cx.env().to_raw(), self.to_raw()) + }) + } + + fn size<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { + unsafe { sys::arraybuffer::size(cx.env().to_raw(), self.to_raw()) } + } +} + +/// A marker trait for all possible element types of binary buffers. +/// +/// This trait can only be implemented within the Neon library. +pub trait Binary: private::Sealed + Clone { + /// The internal Node-API enum value for this binary type. + const TYPE_TAG: TypedArrayType; +} + +/// The family of JS [typed array][typed-arrays] types. +/// +/// ## Typed Arrays +/// +/// JavaScript's [typed arrays][typed-arrays] are objects that allow efficiently reading +/// and writing raw binary data in memory. In Neon, the generic type `JsTypedArray` +/// represents a JavaScript typed array with element type `T`. For example, a JavaScript +/// [`Uint32Array`][Uint32Array] represents a compact array of 32-bit unsigned integers, +/// and is represented in Neon as a `JsTypedArray`. +/// +/// Neon also offers a set of convenience shorthands for concrete instances of +/// `JsTypedArray`, named after their corresponding JavaScript type. For example, +/// `JsTypedArray` can also be referred to as [`JsUint32Array`][JsUint32Array]. +/// +/// The following table shows the complete set of typed array types, with both their +/// JavaScript and Neon types: +/// +/// | Rust Type | Convenience Type | JavaScript Type | +/// | ------------------------------ | -------------------------------------- | ---------------------------------- | +/// | `JsTypedArray<`[`u8`][u8]`>` | [`JsUint8Array`][JsUint8Array] | [`Uint8Array`][Uint8Array] | +/// | `JsTypedArray<`[`i8`][i8]`>` | [`JsInt8Array`][JsInt8Array] | [`Int8Array`][Int8Array] | +/// | `JsTypedArray<`[`u16`][u16]`>` | [`JsUint16Array`][JsUint16Array] | [`Uint16Array`][Uint16Array] | +/// | `JsTypedArray<`[`i16`][i16]`>` | [`JsInt16Array`][JsInt16Array] | [`Int16Array`][Int16Array] | +/// | `JsTypedArray<`[`u32`][u32]`>` | [`JsUint32Array`][JsUint32Array] | [`Uint32Array`][Uint32Array] | +/// | `JsTypedArray<`[`i32`][i32]`>` | [`JsInt32Array`][JsInt32Array] | [`Int32Array`][Int32Array] | +/// | `JsTypedArray<`[`u64`][u64]`>` | [`JsBigUint64Array`][JsBigUint64Array] | [`BigUint64Array`][BigUint64Array] | +/// | `JsTypedArray<`[`i64`][i64]`>` | [`JsBigInt64Array`][JsBigInt64Array] | [`BigInt64Array`][BigInt64Array] | +/// | `JsTypedArray<`[`f32`][f32]`>` | [`JsFloat32Array`][JsFloat32Array] | [`Float32Array`][Float32Array] | +/// | `JsTypedArray<`[`f64`][f64]`>` | [`JsFloat64Array`][JsFloat64Array] | [`Float64Array`][Float64Array] | +/// +/// ### Example: Creating an integer array +/// +/// This example creates a typed array of unsigned 32-bit integers with a user-specified +/// length: +/// +/// ``` +/// # use neon::prelude::*; +/// fn create_int_array(mut cx: FunctionContext) -> JsResult> { +/// let len = cx.argument::(0)?.value(&mut cx) as usize; +/// JsTypedArray::new(&mut cx, len) +/// } +/// ``` +/// +/// ## Buffers +/// +/// Typed arrays are managed with the [`ArrayBuffer`][ArrayBuffer] type, which controls +/// the storage of the underlying data buffer, and several typed views for managing access +/// to the buffer. Neon provides access to the `ArrayBuffer` class with the +/// [`JsArrayBuffer`](crate::types::JsArrayBuffer) type. +/// +/// Node also provides a [`Buffer`][Buffer] type, which is built on top of `ArrayBuffer` +/// and provides additional functionality. Neon provides access to the `Buffer` class +/// with the [`JsBuffer`](crate::types::JsBuffer) type. +/// +/// Many of Node's I/O APIs work with these types, and they can also be used for +/// compact in-memory data structures, which can be shared efficiently between +/// JavaScript and Rust without copying. +/// +/// [u8]: std::primitive::u8 +/// [i8]: std::primitive::i8 +/// [u16]: std::primitive::u16 +/// [i16]: std::primitive::i16 +/// [u32]: std::primitive::u32 +/// [i32]: std::primitive::i32 +/// [u64]: std::primitive::u64 +/// [i64]: std::primitive::i64 +/// [f32]: std::primitive::f32 +/// [f64]: std::primitive::f64 +/// [JsUint8Array]: crate::types::JsUint8Array +/// [JsInt8Array]: crate::types::JsInt8Array +/// [JsUint16Array]: crate::types::JsUint16Array +/// [JsInt16Array]: crate::types::JsInt16Array +/// [JsUint32Array]: crate::types::JsUint32Array +/// [JsInt32Array]: crate::types::JsInt32Array +/// [JsBigUint64Array]: crate::types::JsBigUint64Array +/// [JsBigInt64Array]: crate::types::JsBigInt64Array +/// [JsFloat32Array]: crate::types::JsFloat32Array +/// [JsFloat64Array]: crate::types::JsFloat64Array +/// [Uint8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array +/// [Int8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array +/// [Uint16Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array +/// [Int16Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array +/// [Uint32Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array +/// [Int32Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array +/// [BigUint64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array +/// [BigInt64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array +/// [Float32Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array +/// [Float64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array +/// [typed-arrays]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays +/// [ArrayBuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer +/// [Buffer]: https://nodejs.org/api/buffer.html +#[derive(Debug)] +#[repr(transparent)] +pub struct JsTypedArray(JsTypedArrayInner); + +impl private::Sealed for JsTypedArray {} + +unsafe impl TransparentNoCopyWrapper for JsTypedArray { + type Inner = JsTypedArrayInner; + + fn into_inner(self) -> Self::Inner { + self.0 + } +} + +impl Managed for JsTypedArray { + fn to_raw(&self) -> raw::Local { + self.0.local + } + + // This method should be `unsafe` + // https://github.com/neon-bindings/neon/issues/885 + #[allow(clippy::not_unsafe_ptr_arg_deref)] + fn from_raw(env: Env, local: raw::Local) -> Self { + // Safety: Recomputing this information ensures that the lifetime of the + // buffer handle matches the lifetime of the typed array handle. + let info = unsafe { sys::typedarray::info(env.to_raw(), local) }; + + Self(JsTypedArrayInner { + local, + buffer: info.buf, + _type: PhantomData, + }) + } +} + +impl TypedArray for JsTypedArray { + type Item = T; + + fn as_slice<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] + where + C: Context<'cx>, + { + unsafe { + let env = cx.env().to_raw(); + let value = self.to_raw(); + let info = sys::typedarray::info(env, value); + + slice::from_raw_parts(info.data.cast(), info.length) + } + } + + fn as_mut_slice<'cx, 'a, C>(&mut self, cx: &'a mut C) -> &'a mut [Self::Item] + where + C: Context<'cx>, + { + unsafe { + let env = cx.env().to_raw(); + let value = self.to_raw(); + let info = sys::typedarray::info(env, value); + + slice::from_raw_parts_mut(info.data.cast(), info.length) + } + } + + fn try_borrow<'cx, 'b, C>( + &self, + lock: &'b Lock<'b, C>, + ) -> Result, BorrowError> + where + C: Context<'cx>, + { + unsafe { + let env = lock.cx.env().to_raw(); + let value = self.to_raw(); + let info = sys::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<'cx, 'a, C>( + &mut self, + lock: &'a Lock<'a, C>, + ) -> Result, BorrowError> + where + C: Context<'cx>, + { + unsafe { + let env = lock.cx.env().to_raw(); + let value = self.to_raw(); + let info = sys::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), + ) + } + } + + fn size<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { + self.len(cx) * std::mem::size_of::() + } +} + +impl JsTypedArray { + /// Constructs a typed array that views `buffer`. + /// + /// The resulting typed array has `(buffer.size() / size_of::())` elements. + pub fn from_buffer<'cx, 'b: 'cx, C>( + cx: &mut C, + buffer: Handle<'b, JsArrayBuffer>, + ) -> JsResult<'cx, Self> + where + C: Context<'cx>, + { + let size = buffer.size(cx); + let elt_size = std::mem::size_of::(); + let len = size / elt_size; + + if (len * elt_size) != size { + return cx.throw_range_error(format!( + "byte length of typed array should be a multiple of {}", + elt_size + )); + } + + Self::from_region(cx, &buffer.region(0, len)) + } + + /// Constructs a typed array for the specified buffer region. + /// + /// The resulting typed array has `region.len()` elements and a size of + /// `region.size()` bytes. + /// + /// Throws an exception if the region is invalid, for example if the starting + /// offset is not properly aligned, or the length goes beyond the end of the + /// buffer. + pub fn from_region<'c, 'r, C>(cx: &mut C, region: &Region<'r, T>) -> JsResult<'c, Self> + where + C: Context<'c>, + { + let &Region { + buffer, + offset, + len, + .. + } = region; + + let arr = (unsafe { + sys::typedarray::new(cx.env().to_raw(), T::TYPE_TAG, buffer.to_raw(), offset, len) + }) + .map_err(|_| Throw::new())?; + + Ok(Handle::new_internal(Self(JsTypedArrayInner { + local: arr, + buffer: buffer.to_raw(), + _type: PhantomData, + }))) + } + + /// Returns information about the backing buffer region for this typed array. + pub fn region<'cx, C>(&self, cx: &mut C) -> Region<'cx, T> + where + C: Context<'cx>, + { + let env = cx.env(); + let info = unsafe { sys::typedarray::info(env.to_raw(), self.to_raw()) }; + + Region { + buffer: Handle::new_internal(JsArrayBuffer::from_raw(cx.env(), info.buf)), + offset: info.offset, + len: info.length, + phantom: PhantomData, + } + } + + /// Constructs a new typed array of length `len`. + /// + /// The resulting typed array has a newly allocated storage buffer of + /// size `(len * size_of::())` bytes. + pub fn new<'cx, C>(cx: &mut C, len: usize) -> JsResult<'cx, Self> + where + C: Context<'cx>, + { + let buffer = cx.array_buffer(len * std::mem::size_of::())?; + Self::from_region(cx, &buffer.region(0, len)) + } + + /// Returns the [`JsArrayBuffer`](JsArrayBuffer) that owns the underlying storage buffer + /// for this typed array. + /// + /// Note that the typed array might only reference a region of the buffer; use the + /// [`offset()`](JsTypedArray::offset) and + /// [`size()`](crate::types::buffer::TypedArray::size) methods to + /// determine the region. + pub fn buffer<'cx, C>(&self, cx: &mut C) -> Handle<'cx, JsArrayBuffer> + where + C: Context<'cx>, + { + Handle::new_internal(JsArrayBuffer::from_raw(cx.env(), self.0.buffer)) + } + + /// Returns the offset (in bytes) of the typed array from the start of its + /// [`JsArrayBuffer`](JsArrayBuffer). + pub fn offset<'cx, C>(&self, cx: &mut C) -> usize + where + C: Context<'cx>, + { + let info = unsafe { sys::typedarray::info(cx.env().to_raw(), self.to_raw()) }; + info.offset + } + + /// Returns the length of the typed array, i.e. the number of elements. + /// + /// Note that, depending on the element size, this is not necessarily the same as + /// [`size()`](crate::types::buffer::TypedArray::size). In particular: + /// + /// ```ignore + /// self.size() == self.len() * size_of::() + /// ``` + #[allow(clippy::len_without_is_empty)] + pub fn len<'cx, C>(&self, cx: &mut C) -> usize + where + C: Context<'cx>, + { + let info = unsafe { sys::typedarray::info(cx.env().to_raw(), self.to_raw()) }; + info.length + } +} + +macro_rules! impl_typed_array { + ($typ:ident, $etyp:ty, $($pattern:pat)|+, $tag:ident, $alias:ident, $two:expr$(,)?) => { + impl private::Sealed for $etyp {} + + impl Binary for $etyp { + const TYPE_TAG: TypedArrayType = TypedArrayType::$tag; + } + + impl Value for JsTypedArray<$etyp> {} + + impl Object for JsTypedArray<$etyp> {} + + impl ValueInternal for JsTypedArray<$etyp> { + fn name() -> String { + stringify!($typ).to_string() + } + + fn is_typeof(env: Env, other: &Other) -> bool { + let env = env.to_raw(); + let other = other.to_raw(); + + if unsafe { !sys::tag::is_typedarray(env, other) } { + return false; + } + + let info = unsafe { sys::typedarray::info(env, other) }; + + matches!(info.typ, $($pattern)|+) + } + } + + doc_comment! { + concat!( + "The standard JS [`", + stringify!($typ), + "`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/", + stringify!($typ), + ") type. + +# Example + +``` +# use neon::prelude::*; +use neon::types::buffer::TypedArray; + +fn double(mut cx: FunctionContext) -> JsResult { + let mut array: Handle<", + stringify!($alias), + "> = cx.argument(0)?; + + for elem in array.as_mut_slice(&mut cx).iter_mut() { + *elem *= ", + stringify!($two), + "; + } + + Ok(cx.undefined()) +} +```", + ), + pub type $alias = JsTypedArray<$etyp>; + } + }; +} + +impl_typed_array!(Int8Array, i8, TypedArrayType::I8, I8, JsInt8Array, 2); +impl_typed_array!( + Uint8Array, + u8, + TypedArrayType::U8 | TypedArrayType::U8Clamped, + U8, + JsUint8Array, + 2, +); +impl_typed_array!(Int16Array, i16, TypedArrayType::I16, I16, JsInt16Array, 2); +impl_typed_array!(Uint16Array, u16, TypedArrayType::U16, U16, JsUint16Array, 2); +impl_typed_array!(Int32Array, i32, TypedArrayType::I32, I32, JsInt32Array, 2); +impl_typed_array!(Uint32Array, u32, TypedArrayType::U32, U32, JsUint32Array, 2); +impl_typed_array!( + Float32Array, + f32, + TypedArrayType::F32, + F32, + JsFloat32Array, + 2.0, +); +impl_typed_array!( + Float64Array, + f64, + TypedArrayType::F64, + F64, + JsFloat64Array, + 2.0, +); +impl_typed_array!( + BigInt64Array, + i64, + TypedArrayType::I64, + I64, + JsBigInt64Array, + 2, +); +impl_typed_array!( + BigUint64Array, + u64, + TypedArrayType::U64, + U64, + JsBigUint64Array, + 2, +); diff --git a/crates/neon/src/types/date.rs b/crates/neon/src/types_impl/date.rs similarity index 100% rename from crates/neon/src/types/date.rs rename to crates/neon/src/types_impl/date.rs diff --git a/crates/neon/src/types/error.rs b/crates/neon/src/types_impl/error.rs similarity index 96% rename from crates/neon/src/types/error.rs rename to crates/neon/src/types_impl/error.rs index bb3386595..d4e7a002d 100644 --- a/crates/neon/src/types/error.rs +++ b/crates/neon/src/types_impl/error.rs @@ -5,9 +5,10 @@ use std::panic::{catch_unwind, UnwindSafe}; use crate::{ context::{internal::Env, Context}, handle::{internal::TransparentNoCopyWrapper, Handle, Managed}, + object::Object, result::{NeonResult, Throw}, sys::{self, raw}, - types::{build, private::ValueInternal, utf8::Utf8, Object, Value}, + types::{build, private::ValueInternal, utf8::Utf8, Value}, }; /// A JS `Error` object. @@ -89,7 +90,7 @@ pub(crate) fn convert_panics NeonResult>( env: Env, f: F, ) -> NeonResult { - match catch_unwind(|| f()) { + match catch_unwind(f) { Ok(result) => result, Err(panic) => { let msg = if let Some(string) = panic.downcast_ref::() { diff --git a/crates/neon/src/types/function/mod.rs b/crates/neon/src/types_impl/function/mod.rs similarity index 100% rename from crates/neon/src/types/function/mod.rs rename to crates/neon/src/types_impl/function/mod.rs diff --git a/crates/neon/src/types/function/private.rs b/crates/neon/src/types_impl/function/private.rs similarity index 100% rename from crates/neon/src/types/function/private.rs rename to crates/neon/src/types_impl/function/private.rs diff --git a/crates/neon/src/types/mod.rs b/crates/neon/src/types_impl/mod.rs similarity index 86% rename from crates/neon/src/types/mod.rs rename to crates/neon/src/types_impl/mod.rs index fae10e5ea..047d32722 100644 --- a/crates/neon/src/types/mod.rs +++ b/crates/neon/src/types_impl/mod.rs @@ -1,76 +1,4 @@ -//! Representations of JavaScript's core builtin types. -//! -//! ## Modeling JavaScript Types -//! -//! All JavaScript values in Neon implement the abstract [`Value`] trait, which -//! is the most generic way to work with JavaScript values. Neon provides a -//! number of types that implement this trait, each representing a particular -//! type of JavaScript value. -//! -//! By convention, JavaScript types in Neon have the prefix `Js` in their name, -//! such as [`JsNumber`](crate::types::JsNumber) (for the JavaScript `number` -//! type) or [`JsFunction`](crate::types::JsFunction) (for the JavaScript -//! `function` type). -//! -//! ### Handles and Casts -//! -//! Access to JavaScript values in Neon works through [handles](crate::handle), -//! which ensure the safe interoperation between Rust and the JavaScript garbage -//! collector. This means, for example, a Rust variable that stores a JavaScript string -//! will have the type `Handle` rather than [`JsString`](crate::types::JsString). -//! -//! Neon types model the JavaScript type hierarchy through the use of *casts*. -//! The [`Handle::upcast()`](crate::handle::Handle::upcast) method safely converts -//! a handle to a JavaScript value of one type into a handle to a value of its -//! supertype. For example, it's safe to treat a [`JsArray`](crate::types::JsArray) -//! as a [`JsObject`](crate::types::JsObject), so you can do an "upcast" and it will -//! never fail: -//! -//! ``` -//! # use neon::prelude::*; -//! fn as_object(array: Handle) -> Handle { -//! let object: Handle = array.upcast(); -//! object -//! } -//! ``` -//! -//! Unlike upcasts, the [`Handle::downcast()`](crate::handle::Handle::downcast) method -//! requires a runtime check to test a value's type at runtime, so it can fail with -//! a [`DowncastError`](crate::handle::DowncastError): -//! -//! ``` -//! # use neon::prelude::*; -//! fn as_array<'a>( -//! cx: &mut impl Context<'a>, -//! object: Handle<'a, JsObject> -//! ) -> JsResult<'a, JsArray> { -//! object.downcast(cx).or_throw(cx) -//! } -//! ``` -//! -//! ### The JavaScript Type Hierarchy -//! -//! ![The Neon type hierarchy, described in detail below.][types] -//! -//! The JavaScript type hierarchy includes: -//! -//! - [`JsValue`](JsValue): This is the top of the type hierarchy, and can refer to -//! any JavaScript value. (For TypeScript programmers, this can be thought of as -//! similar to TypeScript's [`unknown`][unknown] type.) -//! - [`JsObject`](JsObject): This is the top of the object type hierarchy. Object -//! types all implement the [`Object`](crate::object::Object) trait, which allows -//! getting and setting properties. -//! - **Standard object types:** [`JsFunction`](JsFunction), [`JsArray`](JsArray), -//! [`JsDate`](JsDate), and [`JsError`](JsError). -//! - **Typed arrays:** [`JsBuffer`](JsBuffer) and [`JsArrayBuffer`](JsArrayBuffer). -//! - **Custom types:** [`JsBox`](JsBox), a special Neon type that allows the creation -//! of custom objects that own Rust data structures. -//! - **Primitive types:** These are the built-in JavaScript datatypes that are not -//! object types: [`JsNumber`](JsNumber), [`JsBoolean`](JsBoolean), -//! [`JsString`](JsString), [`JsNull`](JsNull), and [`JsUndefined`](JsUndefined). -//! -//! [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 +// See types_docs.rs for top-level module API docs. pub(crate) mod boxed; pub mod buffer; @@ -108,7 +36,11 @@ use crate::{ pub use self::{ boxed::{Finalize, JsBox}, - buffer::types::{JsArrayBuffer, JsBuffer, JsTypedArray}, + buffer::types::{ + JsArrayBuffer, JsBigInt64Array, JsBigUint64Array, JsBuffer, JsFloat32Array, JsFloat64Array, + JsInt16Array, JsInt32Array, JsInt8Array, JsTypedArray, JsUint16Array, JsUint32Array, + JsUint8Array, + }, error::JsError, promise::{Deferred, JsPromise}, }; @@ -606,6 +538,7 @@ impl JsArray { unsafe { sys::array::len(env.to_raw(), self.to_raw()) } } + #[allow(clippy::len_without_is_empty)] pub fn len<'a, C: Context<'a>>(&self, cx: &mut C) -> u32 { self.len_inner(cx.env()) } diff --git a/crates/neon/src/types/private.rs b/crates/neon/src/types_impl/private.rs similarity index 91% rename from crates/neon/src/types/private.rs rename to crates/neon/src/types_impl/private.rs index 996ba4564..f5f687e60 100644 --- a/crates/neon/src/types/private.rs +++ b/crates/neon/src/types_impl/private.rs @@ -1,7 +1,8 @@ use crate::{ context::internal::Env, + handle::{Handle, Managed}, sys::raw, - types::{Handle, Managed, Value}, + types::Value, }; pub trait ValueInternal: Managed + 'static { diff --git a/crates/neon/src/types/promise.rs b/crates/neon/src/types_impl/promise.rs similarity index 98% rename from crates/neon/src/types/promise.rs rename to crates/neon/src/types_impl/promise.rs index ef413d864..314c62bfb 100644 --- a/crates/neon/src/types/promise.rs +++ b/crates/neon/src/types_impl/promise.rs @@ -2,10 +2,11 @@ use std::ptr; use crate::{ context::{internal::Env, Context}, - handle::{internal::TransparentNoCopyWrapper, Managed}, + handle::{internal::TransparentNoCopyWrapper, Handle, Managed}, + object::Object, result::JsResult, sys::{self, no_panic::FailureBoundary, raw}, - types::{private::ValueInternal, Handle, Object, Value}, + types::{private::ValueInternal, Value}, }; #[cfg(feature = "napi-4")] @@ -245,7 +246,7 @@ impl Deferred { F: FnOnce(TaskContext) -> JsResult + Send + 'static, { channel.try_send(move |cx| { - self.try_catch_settle(cx, move |cx| complete(cx)); + self.try_catch_settle(cx, complete); Ok(()) }) } diff --git a/crates/neon/src/types/utf8.rs b/crates/neon/src/types_impl/utf8.rs similarity index 100% rename from crates/neon/src/types/utf8.rs rename to crates/neon/src/types_impl/utf8.rs diff --git a/doc/types.jpg b/doc/types.jpg deleted file mode 100644 index b86beb817..000000000 Binary files a/doc/types.jpg and /dev/null differ diff --git a/doc/types.pptx b/doc/types.pptx deleted file mode 100644 index 8d6a4946b..000000000 Binary files a/doc/types.pptx and /dev/null differ diff --git a/test/napi/lib/objects.js b/test/napi/lib/objects.js index 3df0e5c6d..705fde8b7 100644 --- a/test/napi/lib/objects.js +++ b/test/napi/lib/objects.js @@ -65,186 +65,6 @@ describe("JsObject", 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); - assert.equal(new Uint32Array(b)[1], 0); - assert.equal(new Uint32Array(b)[2], 0); - assert.equal(new Uint32Array(b)[3], 0); - }); - - it("correctly reads an ArrayBuffer using the lock API", function () { - var b = new ArrayBuffer(16); - var a = new Uint32Array(b); - a[0] = 47; - a[1] = 133; - a[2] = 9; - a[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(4); - var a = new Uint8Array(b); - a[0] = 49; - a[1] = 135; - a[2] = 11; - 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), 237); - }); - - it("correctly writes to an ArrayBuffer using the lock API", function () { - var b = new ArrayBuffer(16); - 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(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 Uint8Array(b)[1], 100); - addon.write_array_buffer_with_borrow_mut(b, 2, 22); - 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 () { - var b = addon.return_uninitialized_buffer(); - assert.ok(b.length === 16); - }); - - it("gets a 16-byte, zeroed Buffer", function () { - var b = addon.return_buffer(); - assert.ok(b.equals(Buffer.alloc(16))); - }); - - it("gets an external Buffer", function () { - var expected = "String to copy"; - var buf = addon.return_external_buffer(expected); - assert.instanceOf(buf, Buffer); - assert.strictEqual(buf.toString(), expected); - }); - - it("gets an external ArrayBuffer", function () { - var expected = "String to copy"; - var buf = addon.return_external_array_buffer(expected); - assert.instanceOf(buf, ArrayBuffer); - assert.strictEqual(Buffer.from(buf).toString(), expected); - }); - - it("correctly reads a Buffer using the lock API", function () { - var b = Buffer.allocUnsafe(16); - 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), 113); - assert.equal(addon.read_buffer_with_lock(b, 2), 109); - assert.equal(addon.read_buffer_with_lock(b, 3), 189); - }); - - it("correctly reads a Buffer using the borrow API", function () { - 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), 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.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.alloc(4); - addon.write_buffer_with_borrow_mut(b, 0, 16); - 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[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 () { var superObject = { a: 1, diff --git a/test/napi/lib/typedarrays.js b/test/napi/lib/typedarrays.js new file mode 100644 index 000000000..e53fc0ed4 --- /dev/null +++ b/test/napi/lib/typedarrays.js @@ -0,0 +1,539 @@ +var addon = require(".."); +var assert = require("chai").assert; + +const { Worker, isMainThread, parentPort } = require("worker_threads"); + +if (!isMainThread) { + parentPort.on("message", (message) => { + // transfer it back + parentPort.postMessage(message, [message]); + }); + + return; +} + +// A background thread we can transfer buffers to as a way to force +// them to be detached (see the `detach` function). +const DETACH_WORKER = new Worker(__filename); + +// Allow the test harness to spin down the background thread and exit +// when the main thread completes. +DETACH_WORKER.unref(); + +function detach(buffer) { + if (!(buffer instanceof ArrayBuffer)) { + throw new TypeError(); + } + + DETACH_WORKER.postMessage(buffer, [buffer]); + + return new Promise((resolve) => DETACH_WORKER.once("message", resolve)); +} + +describe("Typed arrays", 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); + assert.equal(new Uint32Array(b)[1], 0); + assert.equal(new Uint32Array(b)[2], 0); + assert.equal(new Uint32Array(b)[3], 0); + }); + + it("correctly reads an ArrayBuffer using the lock API", function () { + var b = new ArrayBuffer(16); + var a = new Uint32Array(b); + a[0] = 47; + a[1] = 133; + a[2] = 9; + a[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(4); + var a = new Uint8Array(b); + a[0] = 49; + a[1] = 135; + a[2] = 11; + 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), 237); + }); + + it("correctly writes to an ArrayBuffer using the lock API", function () { + var b = new ArrayBuffer(16); + 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(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 Uint8Array(b)[1], 100); + addon.write_array_buffer_with_borrow_mut(b, 2, 22); + 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 () { + var b = addon.return_uninitialized_buffer(); + assert.ok(b.length === 16); + }); + + it("gets a 16-byte, zeroed Buffer", function () { + var b = addon.return_buffer(); + assert.ok(b.equals(Buffer.alloc(16))); + }); + + it("gets an external Buffer", function () { + var expected = "String to copy"; + var buf = addon.return_external_buffer(expected); + assert.instanceOf(buf, Buffer); + assert.strictEqual(buf.toString(), expected); + }); + + it("gets an external ArrayBuffer", function () { + var expected = "String to copy"; + var buf = addon.return_external_array_buffer(expected); + assert.instanceOf(buf, ArrayBuffer); + assert.strictEqual(Buffer.from(buf).toString(), expected); + }); + + it("gets a typed array constructed from an ArrayBuffer", function () { + var b = new ArrayBuffer(64); + var i8 = addon.return_int8array_from_arraybuffer(b); + assert.strictEqual(i8.byteLength, 64); + assert.strictEqual(i8.length, 64); + i8[0] = 0x17; + i8[1] = -0x17; + assert.deepEqual([...i8.slice(0, 2)], [0x17, -0x17]); + + var b = new ArrayBuffer(64); + var i16 = addon.return_int16array_from_arraybuffer(b); + assert.strictEqual(i16.byteLength, 64); + assert.strictEqual(i16.length, 32); + i16[0] = 0x1234; + i16[1] = -1; + i16[2] = -2; + i16[3] = 0x5678; + assert.deepEqual([...i16.slice(0, 4)], [0x1234, -1, -2, 0x5678]); + var u8 = new Uint8Array(b); + assert.deepEqual( + [...u8.slice(0, 8)], + [0x34, 0x12, 0xff, 0xff, 0xfe, 0xff, 0x78, 0x56] + ); + + var b = new ArrayBuffer(64); + var u32 = addon.return_uint32array_from_arraybuffer(b); + assert.strictEqual(u32.byteLength, 64); + assert.strictEqual(u32.length, 16); + u32[0] = 0x12345678; + var u8 = new Uint8Array(b); + assert.deepEqual([...u8.slice(0, 4)], [0x78, 0x56, 0x34, 0x12]); + + var b = new ArrayBuffer(64); + var f64 = addon.return_float64array_from_arraybuffer(b); + assert.strictEqual(f64.byteLength, 64); + assert.strictEqual(f64.length, 8); + f64[0] = 1.0; + f64[1] = 2.0; + f64[2] = 3.141592653589793; + assert.deepEqual([...f64.slice(0, 3)], [1.0, 2.0, 3.141592653589793]); + assert.deepEqual( + [...new Float64Array(b).slice(0, 3)], + [1.0, 2.0, 3.141592653589793] + ); + + var b = new ArrayBuffer(64); + var u64 = addon.return_biguint64array_from_arraybuffer(b); + assert.strictEqual(u64.byteLength, 64); + assert.strictEqual(u64.length, 8); + u64[0] = 0x1234567887654321n; + u64[1] = 0xcafed00d1337c0den; + var u8 = new Uint8Array(b); + assert.deepEqual( + [...u64.slice(0, 2)], + [0x1234567887654321n, 0xcafed00d1337c0den] + ); + assert.deepEqual( + [...u8.slice(0, 16)], + [ + 0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12, 0xde, 0xc0, 0x37, 0x13, + 0x0d, 0xd0, 0xfe, 0xca, + ] + ); + }); + + it("gets a new typed array", function () { + var i32 = addon.return_new_int32array(16); + assert.strictEqual(i32.constructor, Int32Array); + assert.strictEqual(i32.byteLength, 64); + assert.strictEqual(i32.length, 16); + assert.deepEqual( + [...i32], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + }); + + it("gets correct typed array info", function () { + var buf = new ArrayBuffer(128); + + var a = addon.return_int8array_from_arraybuffer(buf); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(0, a.byteOffset); + assert.strictEqual(128, a.length); + assert.strictEqual(128, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(0, info.byteOffset); + assert.strictEqual(128, info.length); + assert.strictEqual(128, info.byteLength); + + var a = addon.return_int16array_from_arraybuffer(buf); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(0, a.byteOffset); + assert.strictEqual(64, a.length); + assert.strictEqual(128, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(0, info.byteOffset); + assert.strictEqual(64, info.length); + assert.strictEqual(128, info.byteLength); + + var a = addon.return_uint32array_from_arraybuffer(buf); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(0, a.byteOffset); + assert.strictEqual(32, a.length); + assert.strictEqual(128, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(0, info.byteOffset); + assert.strictEqual(32, info.length); + assert.strictEqual(128, info.byteLength); + + var a = addon.return_biguint64array_from_arraybuffer(buf); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(0, a.byteOffset); + assert.strictEqual(16, a.length); + assert.strictEqual(128, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(0, info.byteOffset); + assert.strictEqual(16, info.length); + assert.strictEqual(128, info.byteLength); + }); + + it("correctly constructs a view over a slice of a buffer", function () { + var buf = new ArrayBuffer(128); + + var a = addon.return_uint32array_from_arraybuffer_region(buf, 16, 4); + var info = addon.get_typed_array_info(a); + + assert.strictEqual(buf, a.buffer); + assert.strictEqual(16, a.byteOffset); + assert.strictEqual(4, a.length); + assert.strictEqual(16, a.byteLength); + + assert.strictEqual(buf, info.buffer); + assert.strictEqual(16, info.byteOffset); + assert.strictEqual(4, info.length); + assert.strictEqual(16, info.byteLength); + + a[0] = 17; + a[1] = 42; + a[2] = 100; + a[3] = 1000; + + var left = buf.slice(0, 16); + var middle = buf.slice(16, 32); + var right = buf.slice(32); + + assert.deepEqual(new Uint8Array(16), new Uint8Array(left)); + assert.deepEqual( + new Uint8Array([17, 0, 0, 0, 42, 0, 0, 0, 100, 0, 0, 0, 232, 3, 0, 0]), + new Uint8Array(middle) + ); + assert.deepEqual(new Uint8Array(96), new Uint8Array(right)); + }); + + it("properly fails to construct typed arrays with invalid arguments", function () { + var buf = new ArrayBuffer(32); + try { + addon.return_uint32array_from_arraybuffer_region(buf, 1, 4); + assert.fail("should have thrown for unaligned offset"); + } catch (expected) {} + + try { + addon.return_uint32array_from_arraybuffer_region(buf, 100, 104); + assert.fail("should have thrown for bounds check failure"); + } catch (expected) {} + + try { + addon.return_uint32array_from_arraybuffer_region(buf, 0, 5); + assert.fail("should have thrown for invalid length"); + } catch (expected) {} + + try { + addon.return_uint32array_from_arraybuffer_region(buf, 0, 10); + assert.fail("should have thrown for excessive length"); + } catch (expected) {} + }); + + it("correctly reads a Buffer using the lock API", function () { + var b = Buffer.allocUnsafe(16); + 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), 113); + assert.equal(addon.read_buffer_with_lock(b, 2), 109); + assert.equal(addon.read_buffer_with_lock(b, 3), 189); + }); + + it("correctly reads a Buffer using the borrow API", function () { + 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), 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.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.alloc(4); + addon.write_buffer_with_borrow_mut(b, 0, 16); + 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[2], 232); + addon.write_buffer_with_borrow_mut(b, 3, 55); + assert.equal(b[3], 55); + }); + + it("zeroes the byteLength when an ArrayBuffer is detached", function () { + var buf = new ArrayBuffer(16); + assert.strictEqual(buf.byteLength, 16); + assert.strictEqual(addon.get_arraybuffer_byte_length(buf), 16); + + detach(buf); + + assert.strictEqual(buf.byteLength, 0); + assert.strictEqual(addon.get_arraybuffer_byte_length(buf), 0); + }); + + function testDetach( + arr, + addonFn, + byteLengthBefore, + lengthBefore, + byteOffsetBefore + ) { + let { before, after } = addonFn(arr, (arr) => detach(arr.buffer)); + + assert.strictEqual(before.byteLength, byteLengthBefore); + assert.strictEqual(before.length, lengthBefore); + assert.strictEqual(before.byteOffset, byteOffsetBefore); + assert.strictEqual(after.byteLength, 0); + assert.strictEqual(after.length, 0); + assert.strictEqual(after.byteOffset, 0); + } + + it("provides correct metadata when detaching a typed array's buffer", function () { + var buf = new ArrayBuffer(16); + var arr = new Uint32Array(buf, 4, 2); + var buf = arr.buffer; + + assert.strictEqual(buf.byteLength, 16); + + assert.strictEqual(arr.byteLength, 8); + assert.strictEqual(arr.length, 2); + assert.strictEqual(arr.byteOffset, 4); + + var info = addon.get_typed_array_info(arr); + + assert.strictEqual(info.byteLength, 8); + assert.strictEqual(info.length, 2); + assert.strictEqual(info.byteOffset, 4); + assert.strictEqual(info.buffer, buf); + + testDetach(arr, addon.detach_same_handle, 8, 2, 4); + + var info = addon.get_typed_array_info(arr); + + assert.strictEqual(buf.byteLength, 0); + + assert.strictEqual(arr.buffer, buf); + assert.strictEqual(arr.byteLength, 0); + assert.strictEqual(arr.length, 0); + assert.strictEqual(arr.byteOffset, 0); + + assert.strictEqual(info.byteLength, 0); + assert.strictEqual(info.length, 0); + assert.strictEqual(info.byteOffset, 0); + assert.strictEqual(info.buffer, buf); + }); + + it("provides correct metadata when detaching an escaped typed array's buffer", function () { + var buf = new ArrayBuffer(16); + testDetach(new Uint32Array(buf, 4, 2), addon.detach_and_escape, 8, 2, 4); + }); + + it("provides correct metadata when detaching a casted typed array's buffer", function () { + var buf = new ArrayBuffer(16); + testDetach(new Uint32Array(buf, 4, 2), addon.detach_and_cast, 8, 2, 4); + }); + + it("provides correct metadata when detaching an un-rooted typed array's buffer", function () { + var buf = new ArrayBuffer(16); + testDetach(new Uint32Array(buf, 4, 2), addon.detach_and_unroot, 8, 2, 4); + }); + + it("doesn't validate regions without instantiating", function () { + var buf = new ArrayBuffer(64); + + try { + addon.build_f32_region(buf, 1, 4, false); + } catch (e) { + assert.fail( + "misaligned region shouldn't be validated without instantiating" + ); + } + + try { + addon.build_f32_region(buf, 0, 20, false); + } catch (e) { + assert.fail( + "region overrun shouldn't be validated without instantiating" + ); + } + + try { + addon.build_f64_region(buf, 1, 4, false); + } catch (e) { + assert.fail( + "misaligned region shouldn't be validated without instantiating" + ); + } + + try { + addon.build_f64_region(buf, 0, 10, false); + } catch (e) { + assert.fail( + "region overrun shouldn't be validated without instantiating" + ); + } + }); + + it("validates regions when instantiating", function () { + var buf = new ArrayBuffer(64); + + try { + addon.build_f32_region(buf, 1, 4, false); + assert.fail("misaligned region should be validated when instantiating"); + } catch (expected) {} + + try { + addon.build_f32_region(buf, 0, 20, false); + assert.fail("region overrun should be validated when instantiating"); + } catch (expected) {} + + try { + addon.build_f64_region(buf, 1, 4, true); + assert.fail("misaligned region should be validated when instantiating"); + } catch (expected) {} + + try { + addon.build_f64_region(buf, 0, 10, true); + assert.fail("region overrun should be validated when instantiating"); + } catch (expected) {} + }); +}); diff --git a/test/napi/src/js/functions.rs b/test/napi/src/js/functions.rs index f3422cfb7..0a099774a 100644 --- a/test/napi/src/js/functions.rs +++ b/test/napi/src/js/functions.rs @@ -160,7 +160,7 @@ pub fn num_arguments(mut cx: FunctionContext) -> JsResult { } pub fn return_this(mut cx: FunctionContext) -> JsResult { - Ok(cx.this()?) + cx.this() } pub fn require_object_this(mut cx: FunctionContext) -> JsResult { diff --git a/test/napi/src/js/futures.rs b/test/napi/src/js/futures.rs index 73b93ce5f..5d2168674 100644 --- a/test/napi/src/js/futures.rs +++ b/test/napi/src/js/futures.rs @@ -8,7 +8,7 @@ fn runtime<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static Runtime> { static RUNTIME: OnceCell = OnceCell::new(); RUNTIME - .get_or_try_init(|| Runtime::new()) + .get_or_try_init(Runtime::new) .or_else(|err| cx.throw_error(&err.to_string())) } @@ -58,7 +58,7 @@ pub fn lazy_async_sum(mut cx: FunctionContext) -> JsResult { let nums = nums .or_throw(&mut cx)? .downcast_or_throw::, _>(&mut cx)? - .as_slice(&mut cx) + .as_slice(&cx) .to_vec(); Ok(nums) diff --git a/test/napi/src/js/objects.rs b/test/napi/src/js/objects.rs index f1c9409c5..4c4f7f582 100644 --- a/test/napi/src/js/objects.rs +++ b/test/napi/src/js/objects.rs @@ -1,9 +1,6 @@ use std::borrow::Cow; -use neon::{ - prelude::*, - types::buffer::{BorrowError, TypedArray}, -}; +use neon::{prelude::*, types::buffer::TypedArray}; pub fn return_js_global_object(mut cx: FunctionContext) -> JsResult { Ok(cx.global()) @@ -52,164 +49,6 @@ pub fn seal_js_object(mut cx: FunctionContext) -> JsResult { } } -pub fn return_array_buffer(mut cx: FunctionContext) -> JsResult { - let b: Handle = cx.array_buffer(16)?; - Ok(b) -} - -pub fn read_array_buffer_with_lock(mut cx: FunctionContext) -> JsResult { - 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 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_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 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 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 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 = || -> Result<_, BorrowError> { - 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()) -} - -pub fn return_uninitialized_buffer(mut cx: FunctionContext) -> JsResult { - let b: Handle = unsafe { JsBuffer::uninitialized(&mut cx, 16)? }; - Ok(b) -} - -pub fn return_buffer(mut cx: FunctionContext) -> JsResult { - let b: Handle = cx.buffer(16)?; - Ok(b) -} - -pub fn return_external_buffer(mut cx: FunctionContext) -> JsResult { - let data = cx.argument::(0)?.value(&mut cx); - let buf = JsBuffer::external(&mut cx, data.into_bytes()); - - Ok(buf) -} - -pub fn return_external_array_buffer(mut cx: FunctionContext) -> JsResult { - let data = cx.argument::(0)?.value(&mut cx); - let buf = JsArrayBuffer::external(&mut cx, data.into_bytes()); - - Ok(buf) -} - -pub fn read_buffer_with_lock(mut cx: FunctionContext) -> JsResult { - let b: Handle = cx.argument(0)?; - 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 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_buffer_with_lock(mut cx: FunctionContext) -> JsResult { - let mut b: Handle = cx.argument(0)?; - 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 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()) -} - // Accepts either a `JsString` or `JsBuffer` and returns the contents as // as bytes; avoids copying. fn get_bytes<'cx, 'a, C>(cx: &'a mut C, v: Handle) -> NeonResult> diff --git a/test/napi/src/js/typedarrays.rs b/test/napi/src/js/typedarrays.rs new file mode 100644 index 000000000..572a4e7f2 --- /dev/null +++ b/test/napi/src/js/typedarrays.rs @@ -0,0 +1,361 @@ +use neon::{ + prelude::*, + types::buffer::{Binary, BorrowError, TypedArray}, +}; + +pub fn return_array_buffer(mut cx: FunctionContext) -> JsResult { + let b: Handle = cx.array_buffer(16)?; + Ok(b) +} + +pub fn read_array_buffer_with_lock(mut cx: FunctionContext) -> JsResult { + 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 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_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 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 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 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 = || -> Result<_, BorrowError> { + 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()) +} + +pub fn return_uninitialized_buffer(mut cx: FunctionContext) -> JsResult { + let b: Handle = unsafe { JsBuffer::uninitialized(&mut cx, 16)? }; + Ok(b) +} + +pub fn return_buffer(mut cx: FunctionContext) -> JsResult { + let b: Handle = cx.buffer(16)?; + Ok(b) +} + +pub fn return_external_buffer(mut cx: FunctionContext) -> JsResult { + let data = cx.argument::(0)?.value(&mut cx); + let buf = JsBuffer::external(&mut cx, data.into_bytes()); + + Ok(buf) +} + +pub fn return_external_array_buffer(mut cx: FunctionContext) -> JsResult { + let data = cx.argument::(0)?.value(&mut cx); + let buf = JsArrayBuffer::external(&mut cx, data.into_bytes()); + + Ok(buf) +} + +pub fn return_int8array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + JsInt8Array::from_buffer(&mut cx, buf) +} + +pub fn return_int16array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + JsInt16Array::from_buffer(&mut cx, buf) +} + +pub fn return_uint32array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + JsUint32Array::from_buffer(&mut cx, buf) +} + +pub fn return_float64array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + JsFloat64Array::from_buffer(&mut cx, buf) +} + +pub fn return_biguint64array_from_arraybuffer( + mut cx: FunctionContext, +) -> JsResult { + let buf = cx.argument::(0)?; + JsBigUint64Array::from_buffer(&mut cx, buf) +} + +pub fn return_new_int32array(mut cx: FunctionContext) -> JsResult { + let len = cx.argument::(0)?.value(&mut cx) as usize; + JsInt32Array::new(&mut cx, len) +} + +pub fn return_uint32array_from_arraybuffer_region( + mut cx: FunctionContext, +) -> JsResult { + let buf = cx.argument::(0)?; + let offset = cx.argument::(1)?; + let offset = offset.value(&mut cx); + let len = cx.argument::(2)?; + let len = len.value(&mut cx); + JsUint32Array::from_region(&mut cx, &buf.region(offset as usize, len as usize)) +} + +pub fn get_arraybuffer_byte_length(mut cx: FunctionContext) -> JsResult { + let buf = cx.argument::(0)?; + let size = buf.size(&mut cx); + let n = cx.number(size as u32); + Ok(n) +} + +fn typed_array_info<'cx, C, T: Binary>( + cx: &mut C, + a: Handle<'cx, JsTypedArray>, +) -> JsResult<'cx, JsObject> +where + C: Context<'cx>, +{ + let offset = a.offset(cx); + let offset = cx.number(offset as u32); + + let len = a.len(cx); + let len = cx.number(len as u32); + + let size = a.size(cx); + let size = cx.number(size as u32); + + let buffer = a.buffer(cx); + + let obj = cx.empty_object(); + + obj.set(cx, "byteOffset", offset)?; + obj.set(cx, "length", len)?; + obj.set(cx, "byteLength", size)?; + obj.set(cx, "buffer", buffer)?; + + Ok(obj) +} + +fn detach_and_then<'cx, F>(mut cx: FunctionContext<'cx>, f: F) -> JsResult +where + F: FnOnce( + &mut FunctionContext<'cx>, + Handle<'cx, JsUint32Array>, + ) -> NeonResult>>, +{ + let mut a = cx.argument::(0)?; + let detach = cx.argument::(1)?; + + let before = typed_array_info(&mut cx, a)?; + + detach.call_with(&cx).arg(a).exec(&mut cx)?; + + if let Some(new_array) = f(&mut cx, a)? { + a = new_array; + } + + let after = typed_array_info(&mut cx, a)?; + + let result = cx.empty_object(); + + result.set(&mut cx, "before", before)?; + result.set(&mut cx, "after", after)?; + + Ok(result) +} + +pub fn detach_same_handle(cx: FunctionContext) -> JsResult { + detach_and_then(cx, |_, _| Ok(None)) +} + +pub fn detach_and_escape(cx: FunctionContext) -> JsResult { + detach_and_then(cx, |cx, a| { + let a = cx.compute_scoped(|_| Ok(a))?; + Ok(Some(a)) + }) +} + +pub fn detach_and_cast(cx: FunctionContext) -> JsResult { + detach_and_then(cx, |cx, a| { + let v = a.upcast::(); + let a = v.downcast_or_throw::(cx)?; + Ok(Some(a)) + }) +} + +pub fn detach_and_unroot(cx: FunctionContext) -> JsResult { + detach_and_then(cx, |cx, a| { + let root = a.root(cx); + let a = root.into_inner(cx); + Ok(Some(a)) + }) +} + +pub fn get_typed_array_info(mut cx: FunctionContext) -> JsResult { + let x = cx.argument::(0)?; + + if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else if let Ok(a) = x.downcast::, _>(&mut cx) { + typed_array_info(&mut cx, a) + } else { + cx.throw_type_error("expected a typed array") + } +} + +pub fn build_f32_region(mut cx: FunctionContext) -> JsResult { + let buf: Handle = cx.argument(0)?; + let offset: Handle = cx.argument(1)?; + let offset: usize = offset.value(&mut cx) as u32 as usize; + let len: Handle = cx.argument(2)?; + let len: usize = len.value(&mut cx) as u32 as usize; + let convert: Handle = cx.argument(3)?; + let convert: bool = convert.value(&mut cx); + + let region = buf.region::(offset, len); + + if convert { + let arr = region.to_typed_array(&mut cx)?; + Ok(arr.upcast()) + } else { + Ok(cx.undefined().upcast()) + } +} + +pub fn build_f64_region(mut cx: FunctionContext) -> JsResult { + let buf: Handle = cx.argument(0)?; + let offset: Handle = cx.argument(1)?; + let offset: usize = offset.value(&mut cx) as u32 as usize; + let len: Handle = cx.argument(2)?; + let len: usize = len.value(&mut cx) as u32 as usize; + let convert: Handle = cx.argument(3)?; + let convert: bool = convert.value(&mut cx); + + let region = buf.region::(offset, len); + + if convert { + let arr = region.to_typed_array(&mut cx)?; + Ok(arr.upcast()) + } else { + Ok(cx.undefined().upcast()) + } +} + +pub fn read_buffer_with_lock(mut cx: FunctionContext) -> JsResult { + let b: Handle = cx.argument(0)?; + 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 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_buffer_with_lock(mut cx: FunctionContext) -> JsResult { + let mut b: Handle = cx.argument(0)?; + 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 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()) +} diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index e35315fb0..607baa48a 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -2,7 +2,7 @@ use neon::prelude::*; use crate::js::{ arrays::*, boxed::*, coercions::*, date::*, errors::*, functions::*, numbers::*, objects::*, - strings::*, threads::*, types::*, + strings::*, threads::*, typedarrays::*, types::*, }; mod js { @@ -17,6 +17,7 @@ mod js { pub mod objects; pub mod strings; pub mod threads; + pub mod typedarrays; pub mod types; pub mod workers; } @@ -36,8 +37,8 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { let b_true = cx.boolean(true); let b_false = cx.boolean(false); - assert_eq!(b_true.value(&mut cx), true); - assert_eq!(b_false.value(&mut cx), false); + assert!(b_true.value(&mut cx)); + assert!(!b_false.value(&mut cx)); cx.export_value("undefined", undefined)?; cx.export_value("null", null)?; @@ -81,13 +82,10 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { .abs() < f64::EPSILON ); - assert_eq!( - { - let v: Handle = rust_created.get(&mut cx, "whatever")?; - v.value(&mut cx) - }, - true - ); + assert!({ + let v: Handle = rust_created.get(&mut cx, "whatever")?; + v.value(&mut cx) + }); let property_names = rust_created .get_own_property_names(&mut cx)? @@ -235,6 +233,39 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { 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( + "return_int8array_from_arraybuffer", + return_int8array_from_arraybuffer, + )?; + cx.export_function( + "return_int16array_from_arraybuffer", + return_int16array_from_arraybuffer, + )?; + cx.export_function( + "return_uint32array_from_arraybuffer", + return_uint32array_from_arraybuffer, + )?; + cx.export_function( + "return_float64array_from_arraybuffer", + return_float64array_from_arraybuffer, + )?; + cx.export_function( + "return_biguint64array_from_arraybuffer", + return_biguint64array_from_arraybuffer, + )?; + cx.export_function("return_new_int32array", return_new_int32array)?; + cx.export_function( + "return_uint32array_from_arraybuffer_region", + return_uint32array_from_arraybuffer_region, + )?; + cx.export_function("get_arraybuffer_byte_length", get_arraybuffer_byte_length)?; + cx.export_function("detach_same_handle", detach_same_handle)?; + cx.export_function("detach_and_escape", detach_and_escape)?; + cx.export_function("detach_and_cast", detach_and_cast)?; + cx.export_function("detach_and_unroot", detach_and_unroot)?; + cx.export_function("get_typed_array_info", get_typed_array_info)?; + cx.export_function("build_f32_region", build_f32_region)?; + cx.export_function("build_f64_region", build_f64_region)?; 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("write_buffer_with_lock", write_buffer_with_lock)?;