From 3a511baceebe60f2e00a064eda54c635fcaddebb Mon Sep 17 00:00:00 2001 From: LongYinan Date: Sun, 7 Jul 2024 20:42:17 +0800 Subject: [PATCH] fix(napi): Promise callbacks should require static lifetime (#2172) --- .../src/bindgen_runtime/js_values/promise.rs | 12 +- .../bindgen_runtime/js_values/promise_raw.rs | 226 ++++++++++++++---- .../__snapshots__/typegen.spec.ts.md | 2 + .../__snapshots__/typegen.spec.ts.snap | Bin 5121 -> 5142 bytes examples/napi/__tests__/values.spec.ts | 6 +- examples/napi/index.cjs | 1 + examples/napi/index.d.cts | 2 + examples/napi/src/promise.rs | 15 +- 8 files changed, 208 insertions(+), 56 deletions(-) diff --git a/crates/napi/src/bindgen_runtime/js_values/promise.rs b/crates/napi/src/bindgen_runtime/js_values/promise.rs index 3df56b1f..874a1663 100644 --- a/crates/napi/src/bindgen_runtime/js_values/promise.rs +++ b/crates/napi/src/bindgen_runtime/js_values/promise.rs @@ -9,7 +9,7 @@ use tokio::sync::oneshot::{channel, Receiver}; use crate::{sys, Error, Result, Status}; -use super::{FromNapiValue, PromiseRaw, TypeName, Unknown, ValidateNapiValue}; +use super::{CallbackContext, FromNapiValue, PromiseRaw, TypeName, Unknown, ValidateNapiValue}; /// The JavaScript Promise object representation /// @@ -29,7 +29,7 @@ use super::{FromNapiValue, PromiseRaw, TypeName, Unknown, ValidateNapiValue}; /// /// But this `Promise` can not be pass back to `JavaScript`. /// If you want to use raw JavaScript `Promise` API, you can use the [`PromiseRaw`](./PromiseRaw) instead. -pub struct Promise { +pub struct Promise { value: Pin>>>, } @@ -63,17 +63,17 @@ impl FromNapiValue for Promise { let tx_box = Arc::new(Cell::new(Some(tx))); let tx_in_catch = tx_box.clone(); promise_object - .then(move |value| { + .then(move |ctx| { if let Some(sender) = tx_box.replace(None) { // no need to handle the send error here, the receiver has been dropped - let _ = sender.send(Ok(value)); + let _ = sender.send(Ok(ctx.value)); } Ok(()) })? - .catch(move |err: Unknown| { + .catch(move |ctx: CallbackContext| { if let Some(sender) = tx_in_catch.replace(None) { // no need to handle the send error here, the receiver has been dropped - let _ = sender.send(Err(err.into())); + let _ = sender.send(Err(ctx.value.into())); } Ok(()) })?; 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 a6c52c9f..1c9d042a 100644 --- a/crates/napi/src/bindgen_runtime/js_values/promise_raw.rs +++ b/crates/napi/src/bindgen_runtime/js_values/promise_raw.rs @@ -4,11 +4,11 @@ use std::ptr; #[cfg(all(feature = "napi4", feature = "tokio_rt"))] use crate::bindgen_runtime::Promise; -use crate::NapiRaw; use crate::{ bindgen_prelude::{FromNapiValue, Result, ToNapiValue, TypeName, ValidateNapiValue}, check_status, sys, }; +use crate::{Env, Error, NapiRaw, NapiValue, Status}; pub struct PromiseRaw { pub(crate) inner: sys::napi_value, @@ -31,12 +31,12 @@ impl PromiseRaw { pub fn then(&mut self, cb: Callback) -> Result> where U: ToNapiValue, - Callback: FnOnce(T) -> Result, + Callback: 'static + FnOnce(CallbackContext) -> Result, { let mut then_fn = ptr::null_mut(); - let then_c_string = unsafe { CStr::from_bytes_with_nul_unchecked(b"then\0") }; + const THEN: &[u8; 5] = b"then\0"; check_status!(unsafe { - sys::napi_get_named_property(self.env, self.inner, then_c_string.as_ptr(), &mut then_fn) + sys::napi_get_named_property(self.env, self.inner, THEN.as_ptr().cast(), &mut then_fn) })?; let mut then_callback = ptr::null_mut(); let rust_cb = Box::into_raw(Box::new(cb)); @@ -44,7 +44,7 @@ impl PromiseRaw { unsafe { sys::napi_create_function( self.env, - then_c_string.as_ptr(), + THEN.as_ptr().cast(), 4, Some(raw_promise_then_callback::), rust_cb.cast(), @@ -80,23 +80,19 @@ impl PromiseRaw { where E: FromNapiValue, U: ToNapiValue, - Callback: FnOnce(E) -> Result, + Callback: 'static + FnOnce(CallbackContext) -> Result, { let mut catch_fn = ptr::null_mut(); + const CATCH: &[u8; 6] = b"catch\0"; check_status!(unsafe { - sys::napi_get_named_property( - self.env, - self.inner, - "catch\0".as_ptr().cast(), - &mut catch_fn, - ) + sys::napi_get_named_property(self.env, self.inner, CATCH.as_ptr().cast(), &mut catch_fn) })?; let mut catch_callback = ptr::null_mut(); let rust_cb = Box::into_raw(Box::new(cb)); check_status!(unsafe { sys::napi_create_function( self.env, - "catch\0".as_ptr().cast(), + CATCH.as_ptr().cast(), 5, Some(raw_promise_catch_callback::), rust_cb.cast(), @@ -122,6 +118,55 @@ impl PromiseRaw { }) } + /// Promise.finally method + pub fn finally(&mut self, cb: Callback) -> Result> + where + U: ToNapiValue, + Callback: 'static + FnOnce(Env) -> Result, + { + let mut then_fn = ptr::null_mut(); + const FINALLY: &[u8; 8] = b"finally\0"; + + check_status!(unsafe { + sys::napi_get_named_property(self.env, self.inner, FINALLY.as_ptr().cast(), &mut then_fn) + })?; + let mut then_callback = ptr::null_mut(); + let rust_cb = Box::into_raw(Box::new(cb)); + check_status!( + unsafe { + sys::napi_create_function( + self.env, + FINALLY.as_ptr().cast(), + 7, + Some(raw_promise_finally_callback::), + rust_cb.cast(), + &mut then_callback, + ) + }, + "Create then function for PromiseRaw failed" + )?; + let mut new_promise = ptr::null_mut(); + check_status!( + unsafe { + sys::napi_call_function( + self.env, + self.inner, + then_fn, + 1, + [then_callback].as_ptr(), + &mut new_promise, + ) + }, + "Call then callback on PromiseRaw failed" + )?; + + Ok(Self { + env: self.env, + inner: new_promise, + _phantom: PhantomData, + }) + } + #[cfg(all(feature = "napi4", feature = "tokio_rt"))] /// Convert `PromiseRaw` to `Promise` /// @@ -156,6 +201,28 @@ impl NapiRaw for PromiseRaw { } } +impl NapiValue for PromiseRaw { + unsafe fn from_raw(env: napi_sys::napi_env, value: napi_sys::napi_value) -> Result { + let mut is_promise = false; + check_status!(unsafe { sys::napi_is_promise(env, value, &mut is_promise) })?; + is_promise + .then_some(Self { + env, + inner: value, + _phantom: PhantomData, + }) + .ok_or_else(|| Error::new(Status::InvalidArg, "JavaScript value is not Promise")) + } + + unsafe fn from_raw_unchecked(env: napi_sys::napi_env, value: napi_sys::napi_value) -> Self { + Self { + env, + inner: value, + _phantom: PhantomData, + } + } +} + pub(crate) fn validate_promise( env: napi_sys::napi_env, napi_val: napi_sys::napi_value, @@ -210,16 +277,6 @@ pub(crate) fn validate_promise( Ok(ptr::null_mut()) } -impl FromNapiValue for PromiseRaw { - unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result { - Ok(PromiseRaw { - inner: napi_val, - env, - _phantom: PhantomData, - }) - } -} - unsafe extern "C" fn raw_promise_then_callback( env: sys::napi_env, cbinfo: sys::napi_callback_info, @@ -227,19 +284,13 @@ unsafe extern "C" fn raw_promise_then_callback( where T: FromNapiValue, U: ToNapiValue, - Cb: FnOnce(T) -> Result, + Cb: FnOnce(CallbackContext) -> Result, { - match handle_then_callback::(env, cbinfo) { - Ok(v) => v, - Err(err) => { - let code = CString::new(err.status.as_ref()).unwrap(); - let msg = CString::new(err.reason).unwrap(); - unsafe { sys::napi_throw_error(env, code.as_ptr(), msg.as_ptr()) }; - ptr::null_mut() - } - } + handle_then_callback::(env, cbinfo) + .unwrap_or_else(|err| throw_error(env, err, "Error in Promise.then")) } +#[inline(always)] fn handle_then_callback( env: sys::napi_env, cbinfo: sys::napi_callback_info, @@ -247,7 +298,7 @@ fn handle_then_callback( where T: FromNapiValue, U: ToNapiValue, - Cb: FnOnce(T) -> Result, + Cb: FnOnce(CallbackContext) -> Result, { let mut callback_values = [ptr::null_mut()]; let mut rust_cb = ptr::null_mut(); @@ -267,7 +318,15 @@ where let then_value: T = unsafe { FromNapiValue::from_napi_value(env, callback_values[0]) }?; let cb: Box = unsafe { Box::from_raw(rust_cb.cast()) }; - unsafe { U::to_napi_value(env, cb(then_value)?) } + unsafe { + U::to_napi_value( + env, + cb(CallbackContext { + env: Env(env), + value: then_value, + })?, + ) + } } unsafe extern "C" fn raw_promise_catch_callback( @@ -277,19 +336,13 @@ unsafe extern "C" fn raw_promise_catch_callback( where E: FromNapiValue, U: ToNapiValue, - Cb: FnOnce(E) -> Result, + Cb: FnOnce(CallbackContext) -> Result, { - match handle_catch_callback::(env, cbinfo) { - Ok(v) => v, - Err(err) => { - let code = CString::new(err.status.as_ref()).unwrap(); - let msg = CString::new(err.reason).unwrap(); - unsafe { sys::napi_throw_error(env, code.as_ptr(), msg.as_ptr()) }; - ptr::null_mut() - } - } + handle_catch_callback::(env, cbinfo) + .unwrap_or_else(|err| throw_error(env, err, "Error in Promise.catch")) } +#[inline(always)] fn handle_catch_callback( env: sys::napi_env, cbinfo: sys::napi_callback_info, @@ -297,7 +350,7 @@ fn handle_catch_callback( where E: FromNapiValue, U: ToNapiValue, - Cb: FnOnce(E) -> Result, + Cb: FnOnce(CallbackContext) -> Result, { let mut callback_values = [ptr::null_mut(); 1]; let mut rust_cb = ptr::null_mut(); @@ -317,5 +370,84 @@ where let catch_value: E = unsafe { FromNapiValue::from_napi_value(env, callback_values[0]) }?; let cb: Box = unsafe { Box::from_raw(rust_cb.cast()) }; - unsafe { U::to_napi_value(env, cb(catch_value)?) } + unsafe { + U::to_napi_value( + env, + cb(CallbackContext { + env: Env(env), + value: catch_value, + })?, + ) + } +} + +unsafe extern "C" fn raw_promise_finally_callback( + env: sys::napi_env, + cbinfo: sys::napi_callback_info, +) -> sys::napi_value +where + U: ToNapiValue, + Cb: FnOnce(Env) -> Result, +{ + handle_finally_callback::(env, cbinfo) + .unwrap_or_else(|err| throw_error(env, err, "Error in Promise.finally")) +} + +#[inline(always)] +fn handle_finally_callback( + env: sys::napi_env, + cbinfo: sys::napi_callback_info, +) -> Result +where + U: ToNapiValue, + Cb: FnOnce(Env) -> Result, +{ + let mut rust_cb = ptr::null_mut(); + check_status!( + unsafe { + sys::napi_get_cb_info( + env, + cbinfo, + &mut 0, + ptr::null_mut(), + ptr::null_mut(), + &mut rust_cb, + ) + }, + "Get callback info from finally callback failed" + )?; + let cb: Box = unsafe { Box::from_raw(rust_cb.cast()) }; + + unsafe { U::to_napi_value(env, cb(Env(env))?) } +} + +pub struct CallbackContext { + pub env: Env, + pub value: T, +} + +impl ToNapiValue for CallbackContext { + unsafe fn to_napi_value(env: napi_sys::napi_env, val: Self) -> Result { + T::to_napi_value(env, val.value) + } +} + +#[inline(never)] +fn throw_error(env: sys::napi_env, err: Error, default_msg: &str) -> sys::napi_value { + let code = if err.status.as_ref().is_empty() { + CString::new(Status::GenericFailure.as_ref()) + } else { + CString::new(err.status.as_ref()) + } + .map(|s| s.as_ptr()) + .unwrap_or(ptr::null_mut()); + let msg = if err.reason.is_empty() { + CString::new(default_msg) + } else { + CString::new(err.reason) + } + .map(|s| s.as_ptr()) + .unwrap_or(ptr::null_mut()); + unsafe { sys::napi_throw_error(env, code, msg) }; + ptr::null_mut() } diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md index 4ee610be..0670e58f 100644 --- a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md @@ -340,6 +340,8 @@ Generated by [AVA](https://avajs.dev). ␊ export declare function callCatchOnPromise(input: PromiseRaw): PromiseRaw␊ ␊ + export declare function callFinallyOnPromise(input: PromiseRaw, onFinally: () => void): PromiseRaw␊ + ␊ export declare function callFunction(cb: () => number): number␊ ␊ export declare function callFunctionWithArg(cb: (arg0: number, arg1: number) => number, arg0: number, arg1: number): number␊ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap index 469bb6c94bda315b73f63485bca269d6d69f1204..c1b1ff729d066eaf0587f39747f2eab7d850dce9 100644 GIT binary patch delta 5069 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 { t.is(res, '1') const cat = await callCatchOnPromise(Promise.reject('cat')) t.is(cat, 'cat') + const spy = Sinon.spy() + await callFinallyOnPromise(Promise.resolve(1), spy) + t.true(spy.calledOnce) }) test('object', (t) => { diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index 88cf408d..0d71884b 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -435,6 +435,7 @@ module.exports.call2 = nativeBinding.call2 module.exports.callbackReturnPromise = nativeBinding.callbackReturnPromise module.exports.callbackReturnPromiseAndSpawn = nativeBinding.callbackReturnPromiseAndSpawn module.exports.callCatchOnPromise = nativeBinding.callCatchOnPromise +module.exports.callFinallyOnPromise = nativeBinding.callFinallyOnPromise module.exports.callFunction = nativeBinding.callFunction module.exports.callFunctionWithArg = nativeBinding.callFunctionWithArg module.exports.callFunctionWithArgAndCtx = nativeBinding.callFunctionWithArgAndCtx diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index 637d9c2e..51f21488 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -330,6 +330,8 @@ export declare function callbackReturnPromiseAndSpawn(jsFunc: (arg0: string) => export declare function callCatchOnPromise(input: PromiseRaw): PromiseRaw +export declare function callFinallyOnPromise(input: PromiseRaw, onFinally: () => void): PromiseRaw + export declare function callFunction(cb: () => number): number export declare function callFunctionWithArg(cb: (arg0: number, arg1: number) => number, arg0: number, arg1: number): number diff --git a/examples/napi/src/promise.rs b/examples/napi/src/promise.rs index 69c9ac82..47e1e6af 100644 --- a/examples/napi/src/promise.rs +++ b/examples/napi/src/promise.rs @@ -8,10 +8,21 @@ pub async fn async_plus_100(p: Promise) -> Result { #[napi] pub fn call_then_on_promise(mut input: PromiseRaw) -> Result> { - input.then(|v| Ok(format!("{}", v))) + input.then(|v| Ok(format!("{}", v.value))) } #[napi] pub fn call_catch_on_promise(mut input: PromiseRaw) -> Result> { - input.catch(|e: String| Ok(e)) + input.catch(|e: CallbackContext| Ok(e.value)) +} + +#[napi] +pub fn call_finally_on_promise( + mut input: PromiseRaw, + on_finally: FunctionRef<(), ()>, +) -> Result> { + input.finally(move |env| { + on_finally.borrow_back(&env)?.call(())?; + Ok(()) + }) }