From aee742f1854d0dd72fab834492cda870e3580b5b Mon Sep 17 00:00:00 2001 From: Victor Teo Date: Tue, 21 Mar 2023 11:22:07 +0800 Subject: [PATCH] feat(napi): property getter and setter with closure (#1526) * getter with closure with segment fault * fix getter closure pointer * add setter * Cleanup API * Add test for `create_function_from_closure` * Fix compile error * Fix flaky test title --------- Co-authored-by: LongYinan --- crates/napi/src/env.rs | 267 ++++++++++++------ crates/napi/src/js_values/mod.rs | 50 +++- crates/napi/src/js_values/object_property.rs | 63 +++++ .../__test__/function.spec.ts | 11 + examples/napi-compat-mode/src/function.rs | 17 ++ examples/napi/__test__/typegen.spec.ts.md | 3 + examples/napi/__test__/typegen.spec.ts.snap | Bin 3828 -> 3842 bytes examples/napi/__test__/values.spec.ts | 14 +- examples/napi/index.d.ts | 3 + examples/napi/src/class.rs | 22 +- 10 files changed, 348 insertions(+), 102 deletions(-) diff --git a/crates/napi/src/env.rs b/crates/napi/src/env.rs index e076d43b..d3d0e037 100644 --- a/crates/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -569,9 +569,7 @@ impl Env { F: 'static + Fn(crate::CallContext<'_>) -> Result, R: ToNapiValue, { - use crate::CallContext; - let boxed_callback = Box::new(callback); - let closure_data_ptr: *mut F = Box::into_raw(boxed_callback); + let closure_data_ptr = Box::into_raw(Box::new(callback)); let mut raw_result = ptr::null_mut(); let len = name.len(); @@ -581,85 +579,7 @@ impl Env { self.0, name.as_ptr(), len, - Some({ - unsafe extern "C" fn trampoline) -> Result>( - raw_env: sys::napi_env, - cb_info: sys::napi_callback_info, - ) -> sys::napi_value { - use ::std::panic::{self, AssertUnwindSafe}; - panic::catch_unwind(AssertUnwindSafe(|| { - let (raw_this, ref raw_args, closure_data_ptr) = { - let argc = { - let mut argc = 0; - let status = unsafe { - sys::napi_get_cb_info( - raw_env, - cb_info, - &mut argc, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ) - }; - debug_assert!( - Status::from(status) == Status::Ok, - "napi_get_cb_info failed" - ); - argc - }; - let mut raw_args = vec![ptr::null_mut(); argc]; - let mut raw_this = ptr::null_mut(); - let mut closure_data_ptr = ptr::null_mut(); - - let status = unsafe { - sys::napi_get_cb_info( - raw_env, - cb_info, - &mut { argc }, - raw_args.as_mut_ptr(), - &mut raw_this, - &mut closure_data_ptr, - ) - }; - debug_assert!( - Status::from(status) == Status::Ok, - "napi_get_cb_info failed" - ); - (raw_this, raw_args, closure_data_ptr) - }; - - let closure: &F = unsafe { - closure_data_ptr - .cast::() - .as_ref() - .expect("`napi_get_cb_info` should have yielded non-`NULL` assoc data") - }; - let env = &mut unsafe { Env::from_raw(raw_env) }; - let ctx = CallContext::new(env, cb_info, raw_this, raw_args, raw_args.len()); - closure(ctx) - .and_then(|ret: R| unsafe { ::to_napi_value(env.0, ret) }) - })) - .map_err(|e| { - Error::from_reason(format!( - "panic from Rust code: {}", - if let Some(s) = e.downcast_ref::() { - s - } else if let Some(s) = e.downcast_ref::<&str>() { - s - } else { - "" - }, - )) - }) - .and_then(|v| v) - .unwrap_or_else(|e| { - unsafe { JsError::from(e).throw_into(raw_env) }; - ptr::null_mut() - }) - } - - trampoline:: - }), + Some(trampoline::), closure_data_ptr.cast(), // We let it borrow the data here &mut raw_result, ) @@ -678,17 +598,7 @@ impl Env { self.0, raw_result, closure_data_ptr.cast(), - Some({ - unsafe extern "C" fn finalize_box_trampoline( - _raw_env: sys::napi_env, - closure_data_ptr: *mut c_void, - _finalize_hint: *mut c_void, - ) { - drop(unsafe { Box::::from_raw(closure_data_ptr.cast()) }) - } - - finalize_box_trampoline:: - }), + Some(finalize_box_trampoline::), ptr::null_mut(), ptr::null_mut(), ) @@ -1506,3 +1416,174 @@ unsafe extern "C" fn async_finalize( ); } } + +#[cfg(feature = "napi5")] +pub(crate) unsafe extern "C" fn trampoline< + R: ToNapiValue, + F: Fn(crate::CallContext) -> Result, +>( + raw_env: sys::napi_env, + cb_info: sys::napi_callback_info, +) -> sys::napi_value { + use crate::CallContext; + + let (raw_this, raw_args, closure_data_ptr, argc) = { + // Fast path for 4 arguments or less. + let mut argc = 4; + let mut raw_args = Vec::with_capacity(4); + let mut raw_this = ptr::null_mut(); + let mut closure_data_ptr = ptr::null_mut(); + + let status = unsafe { + sys::napi_get_cb_info( + raw_env, + cb_info, + &mut argc, + raw_args.as_mut_ptr(), + &mut raw_this, + &mut closure_data_ptr, + ) + }; + debug_assert!( + Status::from(status) == Status::Ok, + "napi_get_cb_info failed" + ); + + // Arguments length greater than 4, resize the vector. + if argc > 4 { + raw_args = vec![ptr::null_mut(); argc]; + let status = unsafe { + sys::napi_get_cb_info( + raw_env, + cb_info, + &mut argc, + raw_args.as_mut_ptr(), + &mut raw_this, + &mut closure_data_ptr, + ) + }; + debug_assert!( + Status::from(status) == Status::Ok, + "napi_get_cb_info failed" + ); + } else { + unsafe { raw_args.set_len(argc) }; + } + + (raw_this, raw_args, closure_data_ptr, argc) + }; + + let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) }); + let mut env = unsafe { Env::from_raw(raw_env) }; + let call_context = CallContext::new(&mut env, cb_info, raw_this, raw_args.as_slice(), argc); + closure(call_context) + .and_then(|ret: R| unsafe { ::to_napi_value(env.0, ret) }) + .unwrap_or_else(|e| { + unsafe { JsError::from(e).throw_into(raw_env) }; + ptr::null_mut() + }) +} + +#[cfg(feature = "napi5")] +pub(crate) unsafe extern "C" fn trampoline_setter< + V: FromNapiValue, + F: Fn(Env, crate::bindgen_runtime::Object, V) -> Result<()>, +>( + raw_env: sys::napi_env, + cb_info: sys::napi_callback_info, +) -> sys::napi_value { + use crate::bindgen_runtime::Object; + + let (raw_args, raw_this, closure_data_ptr) = { + let mut argc = 1; + let mut raw_args = vec![ptr::null_mut(); 1]; + let mut raw_this = ptr::null_mut(); + let mut closure_data_ptr = ptr::null_mut(); + + let status = unsafe { + sys::napi_get_cb_info( + raw_env, + cb_info, + &mut argc, + raw_args.as_mut_ptr(), + &mut raw_this, + &mut closure_data_ptr, + ) + }; + unsafe { raw_args.set_len(argc) }; + debug_assert!( + Status::from(status) == Status::Ok, + "napi_get_cb_info failed" + ); + (raw_args, raw_this, closure_data_ptr) + }; + + let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) }); + let env = unsafe { Env::from_raw(raw_env) }; + raw_args + .get(0) + .ok_or_else(|| Error::new(Status::InvalidArg, "Missing argument in property setter")) + .and_then(|value| unsafe { V::from_napi_value(raw_env, *value) }) + .and_then(|value| { + closure( + env, + unsafe { Object::from_raw_unchecked(raw_env, raw_this) }, + value, + ) + }) + .map(|_| std::ptr::null_mut()) + .unwrap_or_else(|e| { + unsafe { JsError::from(e).throw_into(raw_env) }; + ptr::null_mut() + }) +} + +#[cfg(feature = "napi5")] +pub(crate) unsafe extern "C" fn trampoline_getter< + R: ToNapiValue, + F: Fn(Env, crate::bindgen_runtime::This) -> Result, +>( + raw_env: sys::napi_env, + cb_info: sys::napi_callback_info, +) -> sys::napi_value { + let (raw_this, closure_data_ptr) = { + let mut raw_this = ptr::null_mut(); + let mut closure_data_ptr = ptr::null_mut(); + + let status = unsafe { + sys::napi_get_cb_info( + raw_env, + cb_info, + &mut 0, + ptr::null_mut(), + &mut raw_this, + &mut closure_data_ptr, + ) + }; + debug_assert!( + Status::from(status) == Status::Ok, + "napi_get_cb_info failed" + ); + (raw_this, closure_data_ptr) + }; + + let closure: &F = Box::leak(unsafe { Box::from_raw(closure_data_ptr.cast()) }); + let env = unsafe { Env::from_raw(raw_env) }; + closure(env, unsafe { + crate::bindgen_runtime::Object::from_raw_unchecked(raw_env, raw_this) + }) + .and_then(|ret: R| unsafe { ::to_napi_value(env.0, ret) }) + .unwrap_or_else(|e| { + unsafe { JsError::from(e).throw_into(raw_env) }; + ptr::null_mut() + }) +} + +#[cfg(feature = "napi5")] +pub(crate) unsafe extern "C" fn finalize_box_trampoline( + _raw_env: sys::napi_env, + closure_data_ptr: *mut c_void, + _finalize_hint: *mut c_void, +) { + drop(unsafe { Box::::from_raw(closure_data_ptr.cast()) }) +} diff --git a/crates/napi/src/js_values/mod.rs b/crates/napi/src/js_values/mod.rs index ec958bd3..29aa4a19 100644 --- a/crates/napi/src/js_values/mod.rs +++ b/crates/napi/src/js_values/mod.rs @@ -1,9 +1,11 @@ use std::convert::TryFrom; +#[cfg(feature = "napi5")] +use std::ffi::c_void; use std::ffi::CString; use std::ptr; use crate::{ - bindgen_runtime::{FromNapiValue, TypeName, ValidateNapiValue}, + bindgen_runtime::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue}, check_status, sys, type_of, Callback, Error, Result, Status, ValueType, }; @@ -308,11 +310,16 @@ macro_rules! impl_object_methods { pub fn set_named_property(&mut self, name: &str, value: T) -> Result<()> where - T: NapiRaw, + T: ToNapiValue, { let key = CString::new(name)?; check_status!(unsafe { - sys::napi_set_named_property(self.0.env, self.0.value, key.as_ptr(), value.raw()) + sys::napi_set_named_property( + self.0.env, + self.0.value, + key.as_ptr(), + T::to_napi_value(self.0.env, value)?, + ) }) } @@ -347,7 +354,7 @@ macro_rules! impl_object_methods { unsafe { ::from_napi_value(self.0.env, raw_value) } } - pub fn get_named_property_unchecked(&self, name: &str) -> Result + pub fn get_named_property_unchecked(&self, name: &str) -> Result where T: FromNapiValue, { @@ -539,14 +546,33 @@ macro_rules! impl_object_methods { /// This method allows the efficient definition of multiple properties on a given object. pub fn define_properties(&mut self, properties: &[Property]) -> Result<()> { + let properties_iter = properties.iter().map(|property| property.raw()); + #[cfg(feature = "napi5")] + { + let mut closures = properties_iter + .clone() + .map(|p| p.data) + .filter(|data| !data.is_null()) + .collect::>(); + let len = Box::into_raw(Box::new(closures.len())); + check_status!(unsafe { + sys::napi_add_finalizer( + self.0.env, + self.0.value, + closures.as_mut_ptr().cast(), + Some(finalize_closures), + len.cast(), + ptr::null_mut(), + ) + })?; + std::mem::forget(closures); + } check_status!(unsafe { sys::napi_define_properties( self.0.env, self.0.value, properties.len(), - properties - .iter() - .map(|property| property.raw()) + properties_iter .collect::>() .as_ptr(), ) @@ -697,3 +723,13 @@ impl JsUnknown { unsafe { V::from_raw_unchecked(self.0.env, self.0.value) } } } + +#[cfg(feature = "napi5")] +unsafe extern "C" fn finalize_closures(_env: sys::napi_env, data: *mut c_void, len: *mut c_void) { + let length: usize = *unsafe { Box::from_raw(len.cast()) }; + let closures: Vec<*mut PropertyClosures> = + unsafe { Vec::from_raw_parts(data.cast(), length, length) }; + for closure in closures.into_iter() { + drop(unsafe { Box::from_raw(closure) }); + } +} diff --git a/crates/napi/src/js_values/object_property.rs b/crates/napi/src/js_values/object_property.rs index b558d409..88e73516 100644 --- a/crates/napi/src/js_values/object_property.rs +++ b/crates/napi/src/js_values/object_property.rs @@ -1,11 +1,35 @@ use std::convert::From; +#[cfg(feature = "napi5")] +use std::ffi::c_void; use std::ffi::CString; use std::ptr; use bitflags::bitflags; +#[cfg(feature = "napi5")] +use crate::{ + bindgen_runtime::{FromNapiValue, This, ToNapiValue}, + Env, +}; use crate::{sys, Callback, NapiRaw, Result}; +#[cfg(feature = "napi5")] +#[derive(Copy, Clone)] +pub struct PropertyClosures { + setter_closure: *mut c_void, + getter_closure: *mut c_void, +} + +#[cfg(feature = "napi5")] +impl Default for PropertyClosures { + fn default() -> Self { + Self { + setter_closure: ptr::null_mut(), + getter_closure: ptr::null_mut(), + } + } +} + #[derive(Clone)] pub struct Property { pub name: CString, @@ -15,6 +39,8 @@ pub struct Property { attrs: PropertyAttributes, value: sys::napi_value, pub(crate) is_ctor: bool, + #[cfg(feature = "napi5")] + pub(crate) closures: PropertyClosures, } impl Default for Property { @@ -27,6 +53,8 @@ impl Default for Property { attrs: Default::default(), value: ptr::null_mut(), is_ctor: Default::default(), + #[cfg(feature = "napi5")] + closures: PropertyClosures::default(), } } } @@ -77,11 +105,41 @@ impl Property { self } + #[cfg(feature = "napi5")] + pub fn with_getter_closure(mut self, callback: F) -> Self + where + F: 'static + Fn(Env, This) -> Result, + R: ToNapiValue, + { + let boxed_callback = Box::new(callback); + let closure_data_ptr: *mut F = Box::into_raw(boxed_callback); + self.closures.getter_closure = closure_data_ptr.cast(); + + let fun = crate::trampoline_getter::; + self.getter = Some(fun); + self + } + pub fn with_setter(mut self, callback: Callback) -> Self { self.setter = Some(callback); self } + #[cfg(feature = "napi5")] + pub fn with_setter_closure(mut self, callback: F) -> Self + where + F: 'static + Fn(crate::Env, This, V) -> Result<()>, + V: FromNapiValue, + { + let boxed_callback = Box::new(callback); + let closure_data_ptr: *mut F = Box::into_raw(boxed_callback); + self.closures.setter_closure = closure_data_ptr.cast(); + + let fun = crate::trampoline_setter::; + self.setter = Some(fun); + self + } + pub fn with_property_attributes(mut self, attributes: PropertyAttributes) -> Self { self.attrs = attributes; self @@ -93,6 +151,8 @@ impl Property { } pub(crate) fn raw(&self) -> sys::napi_property_descriptor { + #[cfg(feature = "napi5")] + let closures = Box::into_raw(Box::new(self.closures)); sys::napi_property_descriptor { utf8name: self.name.as_ptr(), name: ptr::null_mut(), @@ -101,7 +161,10 @@ impl Property { setter: self.setter, value: self.value, attributes: self.attrs.into(), + #[cfg(not(feature = "napi5"))] data: ptr::null_mut(), + #[cfg(feature = "napi5")] + data: closures.cast(), } } diff --git a/examples/napi-compat-mode/__test__/function.spec.ts b/examples/napi-compat-mode/__test__/function.spec.ts index 5dcfd346..0c8e30a5 100644 --- a/examples/napi-compat-mode/__test__/function.spec.ts +++ b/examples/napi-compat-mode/__test__/function.spec.ts @@ -31,3 +31,14 @@ test('should handle errors', (t) => { }, ) }) + +test('should be able to create function from closure', (t) => { + for (let i = 0; i < 100; i++) { + t.is( + bindings.testCreateFunctionFromClosure()( + ...Array.from({ length: i }).map((_, i) => i), + ), + `arguments length: ${i}`, + ) + } +}) diff --git a/examples/napi-compat-mode/src/function.rs b/examples/napi-compat-mode/src/function.rs index ff04aac1..c51bc9f5 100644 --- a/examples/napi-compat-mode/src/function.rs +++ b/examples/napi-compat-mode/src/function.rs @@ -43,6 +43,19 @@ pub fn call_function_error(ctx: CallContext) -> Result { } } +#[js_function(0)] +pub fn test_create_function_from_closure(ctx: CallContext) -> Result { + ctx + .env + .create_function_from_closure("functionFromClosure", move |ctx| { + if ctx.length != 0 { + let max: u32 = ctx.get(ctx.length - 1)?; + assert_eq!(max, ctx.length as u32 - 1); + } + Ok(format!("arguments length: {}", ctx.length)) + }) +} + pub fn register_js(exports: &mut JsObject) -> Result<()> { exports.create_named_method("testCallFunction", call_function)?; exports.create_named_method( @@ -51,5 +64,9 @@ pub fn register_js(exports: &mut JsObject) -> Result<()> { )?; exports.create_named_method("testCallFunctionWithThis", call_function_with_this)?; exports.create_named_method("testCallFunctionError", call_function_error)?; + exports.create_named_method( + "testCreateFunctionFromClosure", + test_create_function_from_closure, + )?; Ok(()) } diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 849279e2..1a513ae4 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -325,6 +325,9 @@ Generated by [AVA](https://avajs.dev). value: number␊ constructor(value: number)␊ }␊ + export class GetterSetterWithClosures {␊ + constructor()␊ + }␊ export class ClassWithFactory {␊ name: string␊ static withName(name: string): ClassWithFactory␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 7ab511221fef7467a82a3e1e1e95c7356724d78d..85740da669453c12d40db35743e45032bcf0d46c 100644 GIT binary patch delta 3416 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 diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index 40b807a0..b5bb7847 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -25,6 +25,7 @@ import { ClassWithFactory, CustomNumEnum, Context, + GetterSetterWithClosures, enumToI32, listObjKeys, createObj, @@ -329,7 +330,7 @@ test('return function', (t) => { }) }) -test('function return Promise', async (t) => { +test('callback function return Promise', async (t) => { const cbSpy = spy() await callbackReturnPromise(() => '1', spy) t.is(cbSpy.callCount, 0) @@ -919,3 +920,14 @@ Napi5Test('Date to chrono test', (t) => { new Date(fixture.getTime() + 60 * 1000), ) }) + +Napi5Test('Class with getter setter closures', (t) => { + const instance = new GetterSetterWithClosures() + // @ts-expect-error + instance.name = 'Allie' + t.pass() + // @ts-expect-error + t.is(instance.name, `I'm Allie`) + // @ts-expect-error + t.is(instance.age, 0.3) +}) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index ce0d13ad..3dc1da08 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -315,6 +315,9 @@ export class Width { value: number constructor(value: number) } +export class GetterSetterWithClosures { + constructor() +} export class ClassWithFactory { name: string static withName(name: string): ClassWithFactory diff --git a/examples/napi/src/class.rs b/examples/napi/src/class.rs index 29a2b4b3..230fcdcb 100644 --- a/examples/napi/src/class.rs +++ b/examples/napi/src/class.rs @@ -1,6 +1,6 @@ use napi::{ bindgen_prelude::{Buffer, ClassInstance, ObjectFinalize, This, Uint8Array, Unknown}, - Env, Result, + Env, Property, Result, }; use crate::r#enum::Kind; @@ -411,3 +411,23 @@ pub struct Width { pub fn plus_one(this: This<&Width>) -> i32 { this.value + 1 } + +#[napi] +pub struct GetterSetterWithClosures {} + +#[napi] +impl GetterSetterWithClosures { + #[napi(constructor)] + pub fn new(mut this: This) -> Result { + this.define_properties(&[ + Property::new("name")? + .with_setter_closure(move |_env, mut this, value: String| { + this.set_named_property("_name", format!("I'm {}", value))?; + Ok(()) + }) + .with_getter_closure(|_env, this| this.get_named_property_unchecked::("_name")), + Property::new("age")?.with_getter_closure(|_env, _this| Ok(0.3)), + ])?; + Ok(Self {}) + } +}