fix(napi): External should impl FromNapiRef rather than FromNapiValue (#2013)

- Close https://github.com/napi-rs/napi-rs/issues/1994
This commit is contained in:
LongYinan 2024-03-25 15:11:11 +08:00 committed by GitHub
parent be610c9353
commit 0550c56fcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 78 additions and 57 deletions

View file

@ -69,7 +69,7 @@ fn test_class_constructor(ctx: CallContext) -> Result<JsUndefined> {
line_join: LineJoin::Miter, line_join: LineJoin::Miter,
}; };
let mut this = ctx.this_unchecked::<JsObject>(); let mut this = ctx.this_unchecked::<JsObject>();
ctx.env.wrap(&mut this, native)?; ctx.env.wrap(&mut this, native, None)?;
ctx.env.get_undefined() ctx.env.get_undefined()
} }

View file

@ -3,11 +3,13 @@ use std::{
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
}; };
use super::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue}; use super::{FromNapiMutRef, FromNapiRef, ToNapiValue, TypeName, ValidateNapiValue};
use crate::{check_status, sys, Error, Status, TaggedObject}; use crate::{check_status, sys, Error, Status};
#[repr(C)]
pub struct External<T: 'static> { pub struct External<T: 'static> {
obj: *mut TaggedObject<T>, type_id: TypeId,
obj: T,
size_hint: usize, size_hint: usize,
pub adjusted_size: i64, pub adjusted_size: i64,
} }
@ -15,7 +17,7 @@ pub struct External<T: 'static> {
unsafe impl<T: 'static + Send> Send for External<T> {} unsafe impl<T: 'static + Send> Send for External<T> {}
unsafe impl<T: 'static + Sync> Sync for External<T> {} unsafe impl<T: 'static + Sync> Sync for External<T> {}
impl<T: 'static> TypeName for External<T> { impl<T: 'static> TypeName for &External<T> {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"External" "External"
} }
@ -31,12 +33,13 @@ impl<T: 'static> From<T> for External<T> {
} }
} }
impl<T: 'static> ValidateNapiValue for External<T> {} impl<T: 'static> ValidateNapiValue for &External<T> {}
impl<T: 'static> External<T> { impl<T: 'static> External<T> {
pub fn new(value: T) -> Self { pub fn new(value: T) -> Self {
Self { Self {
obj: Box::into_raw(Box::new(TaggedObject::new(value))), type_id: TypeId::of::<T>(),
obj: value,
size_hint: 0, size_hint: 0,
adjusted_size: 0, adjusted_size: 0,
} }
@ -49,15 +52,19 @@ impl<T: 'static> External<T> {
/// If your `External` object is not effect to GC, you can use `External::new` instead. /// If your `External` object is not effect to GC, you can use `External::new` instead.
pub fn new_with_size_hint(value: T, size_hint: usize) -> Self { pub fn new_with_size_hint(value: T, size_hint: usize) -> Self {
Self { Self {
obj: Box::into_raw(Box::new(TaggedObject::new(value))), type_id: TypeId::of::<T>(),
obj: value,
size_hint, size_hint,
adjusted_size: 0, adjusted_size: 0,
} }
} }
} }
impl<T: 'static> FromNapiValue for External<T> { impl<T: 'static> FromNapiMutRef for External<T> {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> { unsafe fn from_napi_mut_ref(
env: sys::napi_env,
napi_val: sys::napi_value,
) -> crate::Result<&'static mut Self> {
let mut unknown_tagged_object = std::ptr::null_mut(); let mut unknown_tagged_object = std::ptr::null_mut();
check_status!( check_status!(
unsafe { sys::napi_get_value_external(env, napi_val, &mut unknown_tagged_object) }, unsafe { sys::napi_get_value_external(env, napi_val, &mut unknown_tagged_object) },
@ -66,30 +73,38 @@ impl<T: 'static> FromNapiValue for External<T> {
let type_id = unknown_tagged_object as *const TypeId; let type_id = unknown_tagged_object as *const TypeId;
if unsafe { *type_id } == TypeId::of::<T>() { if unsafe { *type_id } == TypeId::of::<T>() {
let tagged_object = unknown_tagged_object as *mut TaggedObject<T>; let tagged_object = unknown_tagged_object as *mut External<T>;
Ok(Self { Ok(Box::leak(unsafe { Box::from_raw(tagged_object) }))
obj: tagged_object,
size_hint: 0,
adjusted_size: 0,
})
} else { } else {
Err(Error::new( Err(Error::new(
Status::InvalidArg, Status::InvalidArg,
"T on `get_value_external` is not the type of wrapped object".to_owned(), format!(
"<{}> on `External` is not the type of wrapped object",
std::any::type_name::<T>()
),
)) ))
} }
} }
} }
impl<T: 'static> FromNapiRef for External<T> {
unsafe fn from_napi_ref(
env: sys::napi_env,
napi_val: sys::napi_value,
) -> crate::Result<&'static Self> {
unsafe { Self::from_napi_mut_ref(env, napi_val) }.map(|v| v as &Self)
}
}
impl<T: 'static> AsRef<T> for External<T> { impl<T: 'static> AsRef<T> for External<T> {
fn as_ref(&self) -> &T { fn as_ref(&self) -> &T {
unsafe { Box::leak(Box::from_raw(self.obj)).object.as_ref().unwrap() } &self.obj
} }
} }
impl<T: 'static> AsMut<T> for External<T> { impl<T: 'static> AsMut<T> for External<T> {
fn as_mut(&mut self) -> &mut T { fn as_mut(&mut self) -> &mut T {
unsafe { Box::leak(Box::from_raw(self.obj)).object.as_mut().unwrap() } &mut self.obj
} }
} }
@ -108,15 +123,18 @@ impl<T: 'static> DerefMut for External<T> {
} }
impl<T: 'static> ToNapiValue for External<T> { impl<T: 'static> ToNapiValue for External<T> {
unsafe fn to_napi_value(env: sys::napi_env, mut val: Self) -> crate::Result<sys::napi_value> { unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> crate::Result<sys::napi_value> {
let mut napi_value = std::ptr::null_mut(); let mut napi_value = std::ptr::null_mut();
let size_hint = val.size_hint as i64;
let size_hint_ptr = Box::into_raw(Box::new(size_hint));
let obj_ptr = Box::into_raw(Box::new(val));
check_status!( check_status!(
unsafe { unsafe {
sys::napi_create_external( sys::napi_create_external(
env, env,
val.obj as *mut _, obj_ptr.cast(),
Some(crate::raw_finalize::<T>), Some(crate::raw_finalize::<External<T>>),
Box::into_raw(Box::new(Some(val.size_hint as i64))) as *mut _, size_hint_ptr.cast(),
&mut napi_value, &mut napi_value,
) )
}, },
@ -125,12 +143,12 @@ impl<T: 'static> ToNapiValue for External<T> {
let mut adjusted_external_memory_size = std::mem::MaybeUninit::new(0); let mut adjusted_external_memory_size = std::mem::MaybeUninit::new(0);
if val.size_hint != 0 { if size_hint != 0 {
check_status!( check_status!(
unsafe { unsafe {
sys::napi_adjust_external_memory( sys::napi_adjust_external_memory(
env, env,
val.size_hint as i64, size_hint,
adjusted_external_memory_size.as_mut_ptr(), adjusted_external_memory_size.as_mut_ptr(),
) )
}, },
@ -138,7 +156,8 @@ impl<T: 'static> ToNapiValue for External<T> {
)?; )?;
}; };
val.adjusted_size = unsafe { adjusted_external_memory_size.assume_init() }; (Box::leak(unsafe { Box::from_raw(obj_ptr) })).adjusted_size =
unsafe { adjusted_external_memory_size.assume_init() };
Ok(napi_value) Ok(napi_value)
} }

View file

@ -772,14 +772,19 @@ impl Env {
} }
#[allow(clippy::needless_pass_by_ref_mut)] #[allow(clippy::needless_pass_by_ref_mut)]
pub fn wrap<T: 'static>(&self, js_object: &mut JsObject, native_object: T) -> Result<()> { pub fn wrap<T: 'static>(
&self,
js_object: &mut JsObject,
native_object: T,
size_hint: Option<usize>,
) -> Result<()> {
check_status!(unsafe { check_status!(unsafe {
sys::napi_wrap( sys::napi_wrap(
self.0, self.0,
js_object.0.value, js_object.0.value,
Box::into_raw(Box::new(TaggedObject::new(native_object))).cast(), Box::into_raw(Box::new(TaggedObject::new(native_object))).cast(),
Some(raw_finalize::<T>), Some(raw_finalize::<TaggedObject<T>>),
ptr::null_mut(), Box::into_raw(Box::new(size_hint.unwrap_or(0) as i64)).cast(),
ptr::null_mut(), ptr::null_mut(),
) )
}) })
@ -904,6 +909,7 @@ impl Env {
Ok(unsafe { T::from_raw_unchecked(self.0, js_value) }) Ok(unsafe { T::from_raw_unchecked(self.0, js_value) })
} }
#[deprecated(since = "3.0.0", note = "Please use `External::new` instead")]
/// If `size_hint` provided, `Env::adjust_external_memory` will be called under the hood. /// If `size_hint` provided, `Env::adjust_external_memory` will be called under the hood.
/// ///
/// If no `size_hint` provided, global garbage collections will be triggered less times than expected. /// If no `size_hint` provided, global garbage collections will be triggered less times than expected.
@ -919,8 +925,8 @@ impl Env {
sys::napi_create_external( sys::napi_create_external(
self.0, self.0,
Box::into_raw(Box::new(TaggedObject::new(native_object))).cast(), Box::into_raw(Box::new(TaggedObject::new(native_object))).cast(),
Some(raw_finalize::<T>), Some(raw_finalize::<TaggedObject<T>>),
Box::into_raw(Box::new(size_hint)).cast(), Box::into_raw(Box::new(size_hint.unwrap_or(0))).cast(),
&mut object_value, &mut object_value,
) )
})?; })?;
@ -935,6 +941,7 @@ impl Env {
Ok(unsafe { JsExternal::from_raw_unchecked(self.0, object_value) }) Ok(unsafe { JsExternal::from_raw_unchecked(self.0, object_value) })
} }
#[deprecated(since = "3.0.0", note = "Please use `&External` instead")]
pub fn get_value_external<T: 'static>(&self, js_external: &JsExternal) -> Result<&mut T> { pub fn get_value_external<T: 'static>(&self, js_external: &JsExternal) -> Result<&mut T> {
unsafe { unsafe {
let mut unknown_tagged_object = ptr::null_mut(); let mut unknown_tagged_object = ptr::null_mut();
@ -1382,21 +1389,19 @@ pub(crate) unsafe extern "C" fn raw_finalize<T>(
finalize_data: *mut c_void, finalize_data: *mut c_void,
finalize_hint: *mut c_void, finalize_hint: *mut c_void,
) { ) {
let tagged_object = finalize_data as *mut TaggedObject<T>; let tagged_object = finalize_data as *mut T;
drop(unsafe { Box::from_raw(tagged_object) }); drop(unsafe { Box::from_raw(tagged_object) });
if !finalize_hint.is_null() { if !finalize_hint.is_null() {
let size_hint = unsafe { *Box::from_raw(finalize_hint as *mut Option<i64>) }; let size_hint = unsafe { *Box::from_raw(finalize_hint as *mut i64) };
if let Some(changed) = size_hint { if size_hint != 0 {
if changed != 0 { let mut adjusted = 0i64;
let mut adjusted = 0i64; let status = unsafe { sys::napi_adjust_external_memory(env, -size_hint, &mut adjusted) };
let status = unsafe { sys::napi_adjust_external_memory(env, -changed, &mut adjusted) }; debug_assert!(
debug_assert!( status == sys::Status::napi_ok,
status == sys::Status::napi_ok, "Calling napi_adjust_external_memory failed"
"Calling napi_adjust_external_memory failed" );
); }
} };
};
}
} }
#[cfg(feature = "napi6")] #[cfg(feature = "napi6")]

View file

@ -27,7 +27,7 @@ fn test_class_constructor(ctx: CallContext) -> Result<JsUndefined> {
let mut this: JsObject = ctx.this_unchecked(); let mut this: JsObject = ctx.this_unchecked();
ctx ctx
.env .env
.wrap(&mut this, NativeClass { value: count + 100 })?; .wrap(&mut this, NativeClass { value: count + 100 }, None)?;
this.set_named_property("count", ctx.env.create_int32(count)?)?; this.set_named_property("count", ctx.env.create_int32(count)?)?;
ctx.env.get_undefined() ctx.env.get_undefined()
} }
@ -54,7 +54,7 @@ fn add_native_count(ctx: CallContext) -> Result<JsNumber> {
fn renew_wrapped(ctx: CallContext) -> Result<JsUndefined> { fn renew_wrapped(ctx: CallContext) -> Result<JsUndefined> {
let mut this: JsObject = ctx.this_unchecked(); let mut this: JsObject = ctx.this_unchecked();
ctx.env.drop_wrapped::<NativeClass>(&this)?; ctx.env.drop_wrapped::<NativeClass>(&this)?;
ctx.env.wrap(&mut this, NativeClass { value: 42 })?; ctx.env.wrap(&mut this, NativeClass { value: 42 }, None)?;
ctx.env.get_undefined() ctx.env.get_undefined()
} }

View file

@ -26,7 +26,7 @@ pub fn constructor(ctx: CallContext) -> napi::Result<JsUndefined> {
let mut this: JsObject = ctx.this_unchecked(); let mut this: JsObject = ctx.this_unchecked();
let obj = A { cb }; let obj = A { cb };
ctx.env.wrap(&mut this, obj)?; ctx.env.wrap(&mut this, obj, None)?;
ctx.env.get_undefined() ctx.env.get_undefined()
} }

View file

@ -893,10 +893,7 @@ test('external', (t) => {
const ext2 = createExternalString('wtf') const ext2 = createExternalString('wtf')
// @ts-expect-error // @ts-expect-error
const e = t.throws(() => getExternal(ext2)) const e = t.throws(() => getExternal(ext2))
t.is( t.is(e?.message, '<u32> on `External` is not the type of wrapped object')
e?.message,
'T on `get_value_external` is not the type of wrapped object',
)
}) })
test('should be able to run script', async (t) => { test('should be able to run script', async (t) => {

View file

@ -11,11 +11,11 @@ pub fn create_external_string(content: String) -> External<String> {
} }
#[napi] #[napi]
pub fn get_external(external: External<u32>) -> u32 { pub fn get_external(external: &External<u32>) -> u32 {
*external **external
} }
#[napi] #[napi]
pub fn mutate_external(mut external: External<u32>, new_val: u32) { pub fn mutate_external(external: &mut External<u32>, new_val: u32) {
*external = new_val; **external = new_val;
} }

View file

@ -54,8 +54,8 @@ fn validate_date_time(_d: DateTime<Utc>) -> i64 {
} }
#[napi(strict)] #[napi(strict)]
fn validate_external(e: External<u32>) -> u32 { fn validate_external(e: &External<u32>) -> u32 {
*e **e
} }
#[napi(strict, ts_args_type = "cb: () => number")] #[napi(strict, ts_args_type = "cb: () => number")]