From 624ae11c26dcf44854c705f3a4b7cc1a0c9f6d9a Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 14 Sep 2022 18:01:16 +0800 Subject: [PATCH] fix(napi): propagation error in function call --- crates/backend/src/codegen/fn.rs | 6 +-- crates/backend/src/typegen.rs | 4 ++ crates/napi/src/error.rs | 48 ++++++++++++++++++-- crates/napi/src/lib.rs | 5 +- examples/napi/__test__/typegen.spec.ts.md | 1 + examples/napi/__test__/typegen.spec.ts.snap | Bin 3583 -> 3603 bytes examples/napi/__test__/values.spec.ts | 10 ++++ examples/napi/index.d.ts | 1 + examples/napi/src/callback.rs | 12 +++++ 9 files changed, 79 insertions(+), 8 deletions(-) diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index 7fbb03444a..43d7e274e0 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -343,7 +343,8 @@ impl NapiFn { let mut ret_ptr = std::ptr::null_mut(); - napi::bindgen_prelude::check_status!( + napi::bindgen_prelude::check_pending_exception!( + env, napi::bindgen_prelude::sys::napi_call_function( env, cb.this(), @@ -351,8 +352,7 @@ impl NapiFn { args.len(), args.as_ptr(), &mut ret_ptr - ), - "Failed to call napi callback", + ) )?; #ret diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index ead99fac7b..3ca88a0682 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -148,6 +148,10 @@ static KNOWN_TYPES: Lazy> = Lazy::new(|| { ("Buffer", "Buffer"), ("Vec", "Array<{}>"), ("Result", "Error | {}"), + ("Error", "Error"), + ("JsError", "Error"), + ("JsTypeError", "TypeError"), + ("JsRangeError", "RangeError"), ("ClassInstance", "{}"), ("Either", "{} | {}"), ("Either3", "{} | {} | {}"), diff --git a/crates/napi/src/error.rs b/crates/napi/src/error.rs index de37b6efd3..019e3822e9 100644 --- a/crates/napi/src/error.rs +++ b/crates/napi/src/error.rs @@ -12,6 +12,7 @@ use serde::{de, ser}; #[cfg(feature = "serde-json")] use serde_json::Error as SerdeJSONError; +use crate::bindgen_runtime::ToNapiValue; use crate::{check_status, sys, Env, JsUnknown, NapiValue, Status}; pub type Result = std::result::Result; @@ -28,6 +29,21 @@ pub struct Error { maybe_env: sys::napi_env, } +impl ToNapiValue for Error { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + if val.maybe_raw.is_null() { + let err = unsafe { JsError::from(val).into_value(env) }; + Ok(err) + } else { + let mut value = std::ptr::null_mut(); + check_status!(unsafe { + sys::napi_get_reference_value(val.maybe_env, val.maybe_raw, &mut value) + })?; + Ok(value) + } + } +} + unsafe impl Send for Error {} unsafe impl Sync for Error {} @@ -294,6 +310,12 @@ macro_rules! impl_object_methods { Self(err) } } + + impl crate::bindgen_prelude::ToNapiValue for $js_value { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + unsafe { ToNapiValue::to_napi_value(env, val.0) } + } + } }; } @@ -334,7 +356,8 @@ macro_rules! check_status { #[doc(hidden)] #[macro_export] macro_rules! check_pending_exception { - ($env: expr, $code:expr) => {{ + ($env:expr, $code:expr) => {{ + use $crate::NapiValue; let c = $code; match c { $crate::sys::Status::napi_ok => Ok(()), @@ -344,11 +367,30 @@ macro_rules! check_pending_exception { unsafe { $crate::sys::napi_get_and_clear_last_exception($env, &mut error_result) }, $crate::sys::Status::napi_ok ); - return Err(Error::from(unsafe { - JsUnknown::from_raw_unchecked($env, error_result) + return Err($crate::Error::from(unsafe { + $crate::bindgen_prelude::Unknown::from_raw_unchecked($env, error_result) })); } _ => Err($crate::Error::new($crate::Status::from(c), "".to_owned())), } }}; + + ($env:expr, $code:expr, $($msg:tt)*) => {{ + use $crate::NapiValue; + let c = $code; + match c { + $crate::sys::Status::napi_ok => Ok(()), + $crate::sys::Status::napi_pending_exception => { + let mut error_result = std::ptr::null_mut(); + assert_eq!( + unsafe { $crate::sys::napi_get_and_clear_last_exception($env, &mut error_result) }, + $crate::sys::Status::napi_ok + ); + return Err($crate::Error::from(unsafe { + $crate::bindgen_prelude::Unknown::from_raw_unchecked($env, error_result) + })); + } + _ => Err($crate::Error::new($crate::Status::from(c), format!($($msg)*))), + } + }}; } diff --git a/crates/napi/src/lib.rs b/crates/napi/src/lib.rs index 642d504092..002e6d5a77 100644 --- a/crates/napi/src/lib.rs +++ b/crates/napi/src/lib.rs @@ -160,8 +160,9 @@ pub mod bindgen_prelude { #[cfg(feature = "tokio_rt")] pub use crate::tokio_runtime::*; pub use crate::{ - assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys, - type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType, + assert_type_of, bindgen_runtime::*, check_pending_exception, check_status, + check_status_or_throw, error, error::*, sys, type_of, JsError, Property, PropertyAttributes, + Result, Status, Task, ValueType, }; // This function's signature must be kept in sync with the one in tokio_runtime.rs, otherwise napi diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index bcf1735e9a..ce2521b9ea 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -43,6 +43,7 @@ Generated by [AVA](https://avajs.dev). export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊ export function returnJsFunction(): (...args: any[]) => any␊ export function callbackReturnPromise(functionInput: () => T | Promise, callback: (err: Error | null, result: T) => void): T | Promise␊ + export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void␊ export interface ObjectFieldClassInstance {␊ bird: Bird␊ }␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 6091812b16d5a18d472aa412528843047abafbf3..88f7a84c7d53fcdb2b078003448de159b0fd5d1a 100644 GIT binary patch literal 3603 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 Zu;SvdTc+Qvysf>t^FJS`^WBm&002c8-@E_- literal 3583 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 F002??;#>d# diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index bf351b0b94..d4f7fe514a 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -112,6 +112,7 @@ import { CustomFinalize, plusOne, Width, + captureErrorInCallback, } from '../' test('export const', (t) => { @@ -290,6 +291,15 @@ test('callback', (t) => { t.is(err, undefined) t.is(content, 'hello world') }) + + captureErrorInCallback( + () => { + throw new Error('Testing') + }, + (err) => { + t.is((err as Error).message, 'Testing') + }, + ) }) test('return function', (t) => { diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index e7c74e2bb0..67d8b6c70b 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -33,6 +33,7 @@ export function optionOnly(callback: (arg0?: string | undefined | null) => void) export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void export function returnJsFunction(): (...args: any[]) => any export function callbackReturnPromise(functionInput: () => T | Promise, callback: (err: Error | null, result: T) => void): T | Promise +export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void export interface ObjectFieldClassInstance { bird: Bird } diff --git a/examples/napi/src/callback.rs b/examples/napi/src/callback.rs index f92e01ef93..069ceb2a79 100644 --- a/examples/napi/src/callback.rs +++ b/examples/napi/src/callback.rs @@ -80,3 +80,15 @@ fn callback_return_promise Result>( Ok(ret) } } + +#[napi] +pub fn capture_error_in_callback Result<()>, E: Fn(Error) -> Result<()>>( + cb1: C, + cb2: E, +) -> Result<()> { + if let Err(e) = cb1() { + cb2(e) + } else { + Ok(()) + } +}