From 7182db3a81a44497e01bb6b9f59cb4165430b6dc Mon Sep 17 00:00:00 2001 From: LongYinan Date: Mon, 19 May 2025 19:52:21 +0800 Subject: [PATCH] feat(napi): EscapableHandleScope API (#2652) --- .github/workflows/test-release.yaml | 12 +++ .../src/bindgen_runtime/js_values/array.rs | 1 + .../src/bindgen_runtime/js_values/class.rs | 19 +---- .../src/bindgen_runtime/js_values/function.rs | 1 + .../bindgen_runtime/js_values/promise_raw.rs | 4 +- .../src/bindgen_runtime/js_values/scope.rs | 77 +++++++++++++++++- crates/napi/src/env.rs | 6 +- crates/napi/src/js_values/buffer.rs | 12 ++- crates/napi/src/js_values/date.rs | 5 +- crates/napi/src/js_values/global.rs | 13 +++ crates/napi/src/js_values/mod.rs | 4 +- crates/napi/src/js_values/value.rs | 19 ++++- .../__tests__/__snapshots__/values.spec.ts.md | 2 + .../__snapshots__/values.spec.ts.snap | Bin 6527 -> 6547 bytes examples/napi/__tests__/values.spec.ts | 17 ++++ examples/napi/example.wasi-browser.js | 1 + examples/napi/example.wasi.cjs | 1 + examples/napi/index.cjs | 1 + examples/napi/index.d.cts | 2 + examples/napi/src/scope.rs | 57 +++++++++++-- 20 files changed, 215 insertions(+), 39 deletions(-) diff --git a/.github/workflows/test-release.yaml b/.github/workflows/test-release.yaml index 47f3657c..e3c6e093 100644 --- a/.github/workflows/test-release.yaml +++ b/.github/workflows/test-release.yaml @@ -602,6 +602,18 @@ jobs: settings: - features: 'napi1,napi2,napi3,napi4,napi5,napi6,napi7,napi8,napi9,experimental,async,chrono_date,latin1,full' package: 'napi' + - features: 'napi3' + package: 'napi' + - features: 'napi3,compat-mode' + package: 'napi' + - features: 'napi9' + package: 'napi' + - features: 'napi3,serde-json' + package: 'napi' + - features: 'napi9,serde-json' + package: 'napi' + - features: 'async,compat-mode' + package: 'napi' - features: 'compat-mode,strict,type-def,noop,full,default' package: 'napi-derive' - features: 'noop' diff --git a/crates/napi/src/bindgen_runtime/js_values/array.rs b/crates/napi/src/bindgen_runtime/js_values/array.rs index b9974827..176cafb2 100644 --- a/crates/napi/src/bindgen_runtime/js_values/array.rs +++ b/crates/napi/src/bindgen_runtime/js_values/array.rs @@ -2,6 +2,7 @@ use std::{marker::PhantomData, ptr}; use crate::{bindgen_prelude::*, check_status, Value}; +#[derive(Clone, Copy)] pub struct Array<'env> { pub(crate) env: sys::napi_env, pub(crate) inner: sys::napi_value, diff --git a/crates/napi/src/bindgen_runtime/js_values/class.rs b/crates/napi/src/bindgen_runtime/js_values/class.rs index cd52a6d7..944d47b1 100644 --- a/crates/napi/src/bindgen_runtime/js_values/class.rs +++ b/crates/napi/src/bindgen_runtime/js_values/class.rs @@ -12,6 +12,7 @@ use crate::{ check_status, sys, Env, JsValue, Property, PropertyAttributes, Value, ValueType, }; +#[derive(Clone, Copy)] pub struct This<'env, T = Object<'env>> { pub object: T, _phantom: &'env PhantomData<()>, @@ -46,12 +47,6 @@ impl<'env, T: JsValue<'env>> JsValue<'env> for This<'_, T> { } } -impl<'env, T: JsValue<'env>> JsValue<'env> for &This<'_, T> { - fn value(&self) -> Value { - self.object.value() - } -} - impl FromNapiValue for This<'_, T> { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { Ok(Self { @@ -146,7 +141,7 @@ impl<'env, T: 'env> ClassInstance<'env, T> { U: FromNapiValue + JsValue<'this>, { let property = Property::new(name)? - .with_value(&self) + .with_value(self) .with_property_attributes(attributes); check_status!( @@ -171,16 +166,6 @@ impl<'env, T: 'env> ClassInstance<'env, T> { } } -impl<'env, T: 'env> JsValue<'env> for &ClassInstance<'env, T> { - fn value(&self) -> Value { - Value { - env: self.env, - value: self.value, - value_type: ValueType::Object, - } - } -} - impl<'env, T: 'env> TypeName for ClassInstance<'env, T> where &'env T: TypeName, diff --git a/crates/napi/src/bindgen_runtime/js_values/function.rs b/crates/napi/src/bindgen_runtime/js_values/function.rs index 524315f4..62fb979e 100644 --- a/crates/napi/src/bindgen_runtime/js_values/function.rs +++ b/crates/napi/src/bindgen_runtime/js_values/function.rs @@ -105,6 +105,7 @@ impl_tuple_conversion!( A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z ); +#[derive(Clone, Copy)] /// A JavaScript function. /// It can only live in the scope of a function call. /// If you want to use it outside the scope of a function call, you can turn it into a reference. diff --git a/crates/napi/src/bindgen_runtime/js_values/promise_raw.rs b/crates/napi/src/bindgen_runtime/js_values/promise_raw.rs index 84806da6..0de84900 100644 --- a/crates/napi/src/bindgen_runtime/js_values/promise_raw.rs +++ b/crates/napi/src/bindgen_runtime/js_values/promise_raw.rs @@ -18,7 +18,7 @@ pub struct PromiseRaw<'env, T> { _phantom: &'env PhantomData, } -impl<'env, T> JsValue<'env> for PromiseRaw<'env, T> { +impl<'env, T: FromNapiValue> JsValue<'env> for PromiseRaw<'env, T> { fn value(&self) -> Value { Value { env: self.env, @@ -28,7 +28,7 @@ impl<'env, T> JsValue<'env> for PromiseRaw<'env, T> { } } -impl<'env, T> JsObjectValue<'env> for PromiseRaw<'env, T> {} +impl<'env, T: FromNapiValue> JsObjectValue<'env> for PromiseRaw<'env, T> {} impl TypeName for PromiseRaw<'_, T> { fn type_name() -> &'static str { diff --git a/crates/napi/src/bindgen_runtime/js_values/scope.rs b/crates/napi/src/bindgen_runtime/js_values/scope.rs index 034927fe..74a9bb7a 100644 --- a/crates/napi/src/bindgen_runtime/js_values/scope.rs +++ b/crates/napi/src/bindgen_runtime/js_values/scope.rs @@ -1,6 +1,6 @@ use std::ptr; -use crate::{check_status, sys, Env, JsValue, Result}; +use crate::{bindgen_runtime::FromNapiValue, check_status, sys, Env, JsValue, Result}; pub struct HandleScope { pub(crate) scope: sys::napi_handle_scope, @@ -16,7 +16,29 @@ impl HandleScope { Ok(Self { scope }) } - pub fn run(self, arg: A, f: impl FnOnce(A) -> Result) -> Result + /// # Safety + /// + /// This function is unsafe because it will invalidate the JsValue created within the HandleScope. + /// + /// For example: + /// + /// ```no_run + /// #[napi] + /// pub fn shorter_scope(env: &Env, arr: Array) -> Result> { + /// let len = arr.len(); + /// let mut result = Vec::with_capacity(len as usize); + /// for i in 0..len { + /// let scope = HandleScope::create(env)?; + /// let value: Unknown = arr.get_element(i)?; + /// ^^^ this will be invalidated after the scope is closed + /// let len = unsafe { scope.close(value, |v| match v.get_type()? { + /// ValueType::String => Ok(v.utf8_len()? as u32), + /// _ => Ok(0), + /// })? }; + /// } + /// } + /// ``` + pub unsafe fn close(self, arg: A, f: impl FnOnce(A) -> Result) -> Result where A: JsValuesTuple, { @@ -30,6 +52,57 @@ impl HandleScope { } } +pub struct EscapableHandleScope<'env> { + pub(crate) scope: sys::napi_escapable_handle_scope, + pub(crate) env: sys::napi_env, + pub(crate) phantom: std::marker::PhantomData<&'env ()>, +} + +impl<'env, 'scope: 'env> EscapableHandleScope<'scope> { + pub fn with< + T, + Args: JsValuesTuple, + F: 'env + FnOnce(EscapableHandleScope<'env>, Args) -> Result, + >( + env: &'env Env, + args: Args, + scope_fn: F, + ) -> Result { + let mut scope = ptr::null_mut(); + check_status!( + unsafe { sys::napi_open_escapable_handle_scope(env.0, &mut scope) }, + "Failed to open handle scope" + )?; + let scope: EscapableHandleScope<'env> = Self { + scope, + env: env.0, + phantom: std::marker::PhantomData, + }; + scope_fn(scope, args) + } + + pub fn escape + FromNapiValue>(&self, value: V) -> Result { + let mut result = ptr::null_mut(); + check_status!( + unsafe { sys::napi_escape_handle(self.env, self.scope, value.raw(), &mut result) }, + "Failed to escape handle" + )?; + unsafe { V::from_napi_value(self.env, result) } + } +} + +impl Drop for EscapableHandleScope<'_> { + fn drop(&mut self) { + let status = unsafe { sys::napi_close_escapable_handle_scope(self.env, self.scope) }; + if status != sys::Status::napi_ok { + panic!( + "Failed to close handle scope: {}", + crate::Status::from(status) + ); + } + } +} + pub trait JsValuesTuple { fn env(&self) -> sys::napi_env; } diff --git a/crates/napi/src/env.rs b/crates/napi/src/env.rs index 7942e1a2..a42e2653 100644 --- a/crates/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -26,9 +26,9 @@ use crate::bindgen_runtime::FinalizeContext; use crate::bindgen_runtime::FunctionCallContext; #[cfg(all(feature = "tokio_rt", feature = "napi4"))] use crate::bindgen_runtime::PromiseRaw; -#[cfg(feature = "napi4")] -use crate::bindgen_runtime::ToNapiValue; -use crate::bindgen_runtime::{FromNapiValue, Function, JsValuesTupleIntoVec, Object, Unknown}; +use crate::bindgen_runtime::{ + FromNapiValue, Function, JsValuesTupleIntoVec, Object, ToNapiValue, Unknown, +}; #[cfg(feature = "napi3")] use crate::cleanup_env::{CleanupEnvHook, CleanupEnvHookData}; #[cfg(feature = "serde-json")] diff --git a/crates/napi/src/js_values/buffer.rs b/crates/napi/src/js_values/buffer.rs index ccbd35e2..9bb21e44 100644 --- a/crates/napi/src/js_values/buffer.rs +++ b/crates/napi/src/js_values/buffer.rs @@ -3,7 +3,7 @@ use std::ops::{Deref, DerefMut}; use std::ptr; use crate::{ - bindgen_runtime::{TypeName, ValidateNapiValue}, + bindgen_runtime::{FromNapiValue, TypeName, ValidateNapiValue}, check_status, sys, Env, Error, JsValue, Ref, Result, Status, Unknown, Value, ValueType, }; @@ -34,6 +34,16 @@ impl ValidateNapiValue for JsBuffer { } } +impl FromNapiValue for JsBuffer { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + Ok(JsBuffer(Value { + env, + value: napi_val, + value_type: ValueType::Object, + })) + } +} + impl JsValue<'_> for JsBuffer { fn value(&self) -> Value { self.0 diff --git a/crates/napi/src/js_values/date.rs b/crates/napi/src/js_values/date.rs index b2d2579a..6edf5203 100644 --- a/crates/napi/src/js_values/date.rs +++ b/crates/napi/src/js_values/date.rs @@ -1,10 +1,9 @@ use std::marker::PhantomData; use std::ptr; -use super::{check_status, JsObjectValue}; use crate::{ - bindgen_runtime::{FromNapiValue, TypeName, ValidateNapiValue}, - sys, Error, JsValue, Result, Status, Value, ValueType, + bindgen_runtime::{FromNapiValue, JsObjectValue, TypeName, ValidateNapiValue}, + check_status, sys, Error, JsValue, Result, Status, Value, ValueType, }; #[derive(Clone, Copy)] diff --git a/crates/napi/src/js_values/global.rs b/crates/napi/src/js_values/global.rs index ffd829fd..6d1d7d05 100644 --- a/crates/napi/src/js_values/global.rs +++ b/crates/napi/src/js_values/global.rs @@ -6,6 +6,19 @@ pub struct JsGlobal<'env>( pub(crate) std::marker::PhantomData<&'env ()>, ); +impl FromNapiValue for JsGlobal<'_> { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + Ok(JsGlobal( + Value { + env, + value: napi_val, + value_type: ValueType::Object, + }, + std::marker::PhantomData, + )) + } +} + impl<'env> JsValue<'env> for JsGlobal<'env> { fn value(&self) -> Value { self.0 diff --git a/crates/napi/src/js_values/mod.rs b/crates/napi/src/js_values/mod.rs index ec3333d9..588ffc40 100644 --- a/crates/napi/src/js_values/mod.rs +++ b/crates/napi/src/js_values/mod.rs @@ -12,11 +12,11 @@ use crate::bindgen_runtime::finalize_closures; #[cfg(feature = "compat-mode")] use crate::{ bindgen_runtime::{FromNapiValue, ValidateNapiValue}, - type_of, Callback, Error, Status, + check_status, type_of, Callback, Error, Status, }; use crate::{ bindgen_runtime::{JsObjectValue, ToNapiValue}, - check_status, sys, Result, ValueType, + sys, Result, ValueType, }; #[cfg(feature = "compat-mode")] diff --git a/crates/napi/src/js_values/value.rs b/crates/napi/src/js_values/value.rs index 5b5fa523..6e5672f8 100644 --- a/crates/napi/src/js_values/value.rs +++ b/crates/napi/src/js_values/value.rs @@ -1,6 +1,7 @@ use std::fmt::{self, Display}; use std::ptr; +use crate::bindgen_runtime::EscapableHandleScope; use crate::{ bindgen_runtime::{FromNapiValue, Object, Unknown}, {check_status, sys, JsNumber, JsString, Result, ValueType}, @@ -19,7 +20,7 @@ impl Display for Value { } } -pub trait JsValue<'env>: Sized { +pub trait JsValue<'env>: Sized + FromNapiValue { fn value(&self) -> Value; fn raw(&self) -> sys::napi_value { @@ -157,4 +158,20 @@ pub trait JsValue<'env>: Sized { })?; Ok(result) } + + fn escape<'scope, E: JsValue<'scope> + FromNapiValue>( + &self, + escapable_handle_scope: EscapableHandleScope<'scope>, + ) -> Result { + let mut result = ptr::null_mut(); + unsafe { + sys::napi_escape_handle( + escapable_handle_scope.env, + escapable_handle_scope.scope, + self.raw(), + &mut result, + ) + }; + unsafe { ::from_napi_value(self.value().env, result) } + } } diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.md b/examples/napi/__tests__/__snapshots__/values.spec.ts.md index 80be2855..d5b20a61 100644 --- a/examples/napi/__tests__/__snapshots__/values.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/values.spec.ts.md @@ -824,6 +824,8 @@ Generated by [AVA](https://avajs.dev). value: number␊ }␊ ␊ + export declare function shorterEscapableScope(createString: () => string | null): string␊ + ␊ export declare function shorterScope(arr: unknown[]): Array␊ ␊ export declare function shutdownRuntime(): void␊ diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap index e68529266037344b965d37a7eac3258954a43f7f..d51e433ca05fa3cdf7f898c63b7b7ff4a4bb8e49 100644 GIT binary patch literal 6547 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 F001&yu=fA} literal 6527 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 diff --git a/examples/napi/__tests__/values.spec.ts b/examples/napi/__tests__/values.spec.ts index a00a5d60..4d03cd6b 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -233,6 +233,7 @@ import { shutdownRuntime, callAsyncWithUnknownReturnValue, shorterScope, + shorterEscapableScope, } from '../index.cjs' // import other stuff in `#[napi(module_exports)]` import nativeAddon from '../index.cjs' @@ -1666,3 +1667,19 @@ test('shorter scope', (t) => { const result = shorterScope(['hello', { foo: 'bar' }, 'world', true]) t.deepEqual(result, [5, 1, 5, 0]) }) + +test('escapable handle scope', (t) => { + function makeIterFunction() { + let i = 0 + return () => { + if (i >= 10_000) { + return null + } + i++ + return Math.random().toString().repeat(100) + } + } + t.notThrows(() => { + shorterEscapableScope(makeIterFunction()) + }) +}) diff --git a/examples/napi/example.wasi-browser.js b/examples/napi/example.wasi-browser.js index c42fba5b..0f423926 100644 --- a/examples/napi/example.wasi-browser.js +++ b/examples/napi/example.wasi-browser.js @@ -292,6 +292,7 @@ export const roundtripStr = __napiModule.exports.roundtripStr export const runScript = __napiModule.exports.runScript export const setNullByteProperty = __napiModule.exports.setNullByteProperty export const setSymbolInObj = __napiModule.exports.setSymbolInObj +export const shorterEscapableScope = __napiModule.exports.shorterEscapableScope export const shorterScope = __napiModule.exports.shorterScope export const shutdownRuntime = __napiModule.exports.shutdownRuntime export const spawnThreadInThread = __napiModule.exports.spawnThreadInThread diff --git a/examples/napi/example.wasi.cjs b/examples/napi/example.wasi.cjs index 53280515..6ea843a4 100644 --- a/examples/napi/example.wasi.cjs +++ b/examples/napi/example.wasi.cjs @@ -316,6 +316,7 @@ module.exports.roundtripStr = __napiModule.exports.roundtripStr module.exports.runScript = __napiModule.exports.runScript module.exports.setNullByteProperty = __napiModule.exports.setNullByteProperty module.exports.setSymbolInObj = __napiModule.exports.setSymbolInObj +module.exports.shorterEscapableScope = __napiModule.exports.shorterEscapableScope module.exports.shorterScope = __napiModule.exports.shorterScope module.exports.shutdownRuntime = __napiModule.exports.shutdownRuntime module.exports.spawnThreadInThread = __napiModule.exports.spawnThreadInThread diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index b400ec22..d1313462 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -606,6 +606,7 @@ module.exports.roundtripStr = nativeBinding.roundtripStr module.exports.runScript = nativeBinding.runScript module.exports.setNullByteProperty = nativeBinding.setNullByteProperty module.exports.setSymbolInObj = nativeBinding.setSymbolInObj +module.exports.shorterEscapableScope = nativeBinding.shorterEscapableScope module.exports.shorterScope = nativeBinding.shorterScope module.exports.shutdownRuntime = nativeBinding.shutdownRuntime module.exports.spawnThreadInThread = nativeBinding.spawnThreadInThread diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index 6163d2fc..74e81c19 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -786,6 +786,8 @@ export interface Shared { value: number } +export declare function shorterEscapableScope(createString: () => string | null): string + export declare function shorterScope(arr: unknown[]): Array export declare function shutdownRuntime(): void diff --git a/examples/napi/src/scope.rs b/examples/napi/src/scope.rs index 0fa8fd07..25ed429b 100644 --- a/examples/napi/src/scope.rs +++ b/examples/napi/src/scope.rs @@ -7,15 +7,56 @@ pub fn shorter_scope(env: &Env, arr: Array) -> Result> { for i in 0..len { let scope = HandleScope::create(env)?; let value: Unknown = arr.get_element(i)?; - let len = scope.run(value, |v| match v.get_type()? { - ValueType::String => { - let string = unsafe { v.cast::() }?; - Ok(string.utf8_len()? as u32) - } - ValueType::Object => Ok(1), - _ => Ok(0), - })?; + let len = unsafe { + scope.close(value, |v| match v.get_type()? { + ValueType::String => { + let string = v.cast::()?; + Ok(string.utf8_len()? as u32) + } + ValueType::Object => Ok(1), + _ => Ok(0), + })? + }; result.push(len); } Ok(result) } + +#[napi] +pub fn shorter_escapable_scope<'env>( + env: &'env Env, + create_string: Function<(), Option>, +) -> Result> { + let mut longest_string = env.create_string("")?; + let mut prev_len = 0; + loop { + if let Some(maybe_longest) = EscapableHandleScope::with( + env, + (create_string, longest_string), + move |scope, (create_string, prev)| { + let elem = create_string.call(())?; + if let Some(string) = elem { + let len = string.utf8_len()?; + if len > prev.utf8_len()? { + return Ok(Some(Either::A(string.escape::(scope)?))); + } + } else { + return Ok(Some(Either::B(()))); + } + Ok(None) + }, + )? { + match maybe_longest { + Either::A(longest) => { + if longest.utf8_len()? == prev_len { + break; + } + prev_len = longest.utf8_len()?; + longest_string = longest; + } + Either::B(_) => break, + } + } + } + Ok(longest_string) +}