fix(napi): memory leak while using Reference (#1954)
- Close https://github.com/napi-rs/napi-rs/issues/1952
This commit is contained in:
parent
09efd416e5
commit
c49309fdc2
4 changed files with 57 additions and 31 deletions
|
@ -267,11 +267,16 @@ impl NapiFn {
|
|||
{
|
||||
if let Some(p) = path.path.segments.first() {
|
||||
if p.ident == *self.parent.as_ref().unwrap() {
|
||||
args.push(
|
||||
quote! { napi::bindgen_prelude::Reference::from_value_ptr(this_ptr.cast(), env)? },
|
||||
);
|
||||
args.push(quote! {
|
||||
napi::bindgen_prelude::Reference::from_value_ptr(this_ptr.cast(), env)?
|
||||
});
|
||||
skipped_arg_count += 1;
|
||||
continue;
|
||||
} else {
|
||||
bail_span!(
|
||||
p,
|
||||
"The `T` of Reference<T> type must be the same as the class type"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -551,16 +556,20 @@ impl NapiFn {
|
|||
let ty_string = ty.into_token_stream().to_string();
|
||||
let is_return_self = ty_string == "& Self" || ty_string == "&mut Self";
|
||||
if self.kind == FnKind::Constructor {
|
||||
let parent = self
|
||||
.parent
|
||||
.as_ref()
|
||||
.expect("Parent must exist for constructor");
|
||||
if self.is_ret_result {
|
||||
if self.parent_is_generator {
|
||||
quote! { cb.construct_generator(#js_name, #ret?) }
|
||||
quote! { cb.construct_generator::<false, #parent>(#js_name, #ret?) }
|
||||
} else {
|
||||
quote! { cb.construct(#js_name, #ret?) }
|
||||
quote! { cb.construct::<false, #parent>(#js_name, #ret?) }
|
||||
}
|
||||
} else if self.parent_is_generator {
|
||||
quote! { cb.construct_generator(#js_name, #ret) }
|
||||
quote! { cb.construct_generator::<false, #parent>(#js_name, #ret) }
|
||||
} else {
|
||||
quote! { cb.construct(#js_name, #ret) }
|
||||
quote! { cb.construct::<false, #parent>(#js_name, #ret) }
|
||||
}
|
||||
} else if self.kind == FnKind::Factory {
|
||||
if self.is_ret_result {
|
||||
|
|
|
@ -227,10 +227,12 @@ impl NapiStruct {
|
|||
quote! { #name {#(#fields),*} }
|
||||
};
|
||||
|
||||
let is_empty_struct_hint = fields_len == 0;
|
||||
|
||||
let constructor = if self.implement_iterator {
|
||||
quote! { unsafe { cb.construct_generator(#js_name_str, #construct) } }
|
||||
quote! { unsafe { cb.construct_generator::<#is_empty_struct_hint, #name>(#js_name_str, #construct) } }
|
||||
} else {
|
||||
quote! { unsafe { cb.construct(#js_name_str, #construct) } }
|
||||
quote! { unsafe { cb.construct::<#is_empty_struct_hint, #name>(#js_name_str, #construct) } }
|
||||
};
|
||||
|
||||
quote! {
|
||||
|
@ -280,7 +282,7 @@ impl NapiStruct {
|
|||
) -> napi::Result<napi::bindgen_prelude::sys::napi_value> {
|
||||
if let Some(ctor_ref) = napi::__private::get_class_constructor(#js_name_str) {
|
||||
let wrapped_value = Box::into_raw(Box::new(val));
|
||||
let instance_value = #name::new_instance(env, wrapped_value as *mut std::ffi::c_void, ctor_ref)?;
|
||||
let instance_value = #name::new_instance(env, wrapped_value.cast(), ctor_ref)?;
|
||||
#iterator_implementation
|
||||
Ok(instance_value)
|
||||
} else {
|
||||
|
@ -298,12 +300,12 @@ impl NapiStruct {
|
|||
if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) {
|
||||
unsafe {
|
||||
let wrapped_value = Box::into_raw(Box::new(val));
|
||||
let instance_value = #name::new_instance(env.raw(), wrapped_value as *mut std::ffi::c_void, ctor_ref)?;
|
||||
let instance_value = #name::new_instance(env.raw(), wrapped_value.cast(), ctor_ref)?;
|
||||
{
|
||||
let env = env.raw();
|
||||
#iterator_implementation
|
||||
}
|
||||
napi::bindgen_prelude::Reference::<#name>::from_value_ptr(wrapped_value as *mut std::ffi::c_void, env.raw())
|
||||
napi::bindgen_prelude::Reference::<#name>::from_value_ptr(wrapped_value.cast(), env.raw())
|
||||
}
|
||||
} else {
|
||||
Err(napi::bindgen_prelude::Error::new(
|
||||
|
|
|
@ -12,6 +12,10 @@ thread_local! {
|
|||
pub static ___CALL_FROM_FACTORY: AtomicBool = AtomicBool::new(false);
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
struct EmptyStructPlaceholder(u8);
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct CallbackInfo<const N: usize> {
|
||||
env: sys::napi_env,
|
||||
pub this: sys::napi_value,
|
||||
|
@ -84,14 +88,19 @@ impl<const N: usize> CallbackInfo<N> {
|
|||
self.this
|
||||
}
|
||||
|
||||
fn _construct<T: ObjectFinalize + 'static>(
|
||||
fn _construct<const IsEmptyStructHint: bool, T: ObjectFinalize + 'static>(
|
||||
&self,
|
||||
js_name: &str,
|
||||
obj: T,
|
||||
) -> Result<(sys::napi_value, *mut T)> {
|
||||
let obj = Box::new(obj);
|
||||
let this = self.this();
|
||||
let value_ref = Box::into_raw(obj);
|
||||
let mut value_ref = Box::into_raw(obj);
|
||||
// for empty struct like `#[napi] struct A;`, the `value_ref` will be `0x1`
|
||||
// and it will be overwritten by the others instance of the same class
|
||||
if IsEmptyStructHint || value_ref as usize == 0x1 {
|
||||
value_ref = Box::into_raw(Box::new(EmptyStructPlaceholder(0))).cast();
|
||||
}
|
||||
let mut object_ref = ptr::null_mut();
|
||||
let initial_finalize: Box<dyn FnOnce()> = Box::new(|| {});
|
||||
let finalize_callbacks_ptr = Rc::into_raw(Rc::new(Cell::new(Box::into_raw(initial_finalize))));
|
||||
|
@ -118,20 +127,25 @@ impl<const N: usize> CallbackInfo<N> {
|
|||
Ok((this, value_ref))
|
||||
}
|
||||
|
||||
pub fn construct<T: ObjectFinalize + 'static>(
|
||||
pub fn construct<const IsEmptyStructHint: bool, T: ObjectFinalize + 'static>(
|
||||
&self,
|
||||
js_name: &str,
|
||||
obj: T,
|
||||
) -> Result<sys::napi_value> {
|
||||
self._construct(js_name, obj).map(|(v, _)| v)
|
||||
self
|
||||
._construct::<IsEmptyStructHint, T>(js_name, obj)
|
||||
.map(|(v, _)| v)
|
||||
}
|
||||
|
||||
pub fn construct_generator<T: Generator + ObjectFinalize + 'static>(
|
||||
pub fn construct_generator<
|
||||
const IsEmptyStructHint: bool,
|
||||
T: Generator + ObjectFinalize + 'static,
|
||||
>(
|
||||
&self,
|
||||
js_name: &str,
|
||||
obj: T,
|
||||
) -> Result<sys::napi_value> {
|
||||
let (instance, generator_ptr) = self._construct(js_name, obj)?;
|
||||
let (instance, generator_ptr) = self._construct::<IsEmptyStructHint, T>(js_name, obj)?;
|
||||
crate::__private::create_iterator(self.env, instance, generator_ptr);
|
||||
Ok(instance)
|
||||
}
|
||||
|
@ -187,7 +201,13 @@ impl<const N: usize> CallbackInfo<N> {
|
|||
let initial_finalize: Box<dyn FnOnce()> = Box::new(|| {});
|
||||
let finalize_callbacks_ptr = Rc::into_raw(Rc::new(Cell::new(Box::into_raw(initial_finalize))));
|
||||
let mut object_ref = ptr::null_mut();
|
||||
let value_ref = Box::into_raw(obj);
|
||||
let mut value_ref = Box::into_raw(obj);
|
||||
|
||||
// for empty struct like `#[napi] struct A;`, the `value_ref` will be `0x1`
|
||||
// and it will be overwritten by the others instance of the same class
|
||||
if value_ref as usize == 0x1 {
|
||||
value_ref = Box::into_raw(Box::new(EmptyStructPlaceholder(0))).cast();
|
||||
}
|
||||
check_status!(
|
||||
unsafe {
|
||||
sys::napi_wrap(
|
||||
|
|
|
@ -7,9 +7,9 @@ use std::rc::{Rc, Weak};
|
|||
use crate::{bindgen_runtime::ToNapiValue, check_status, Env, Error, Result, Status};
|
||||
|
||||
type RefInformation = (
|
||||
*mut c_void,
|
||||
crate::sys::napi_ref,
|
||||
*const Cell<*mut dyn FnOnce()>,
|
||||
/* wrapped_value */ *mut c_void,
|
||||
/* napi_ref */ crate::sys::napi_ref,
|
||||
/* finalize_callback */ *const Cell<*mut dyn FnOnce()>,
|
||||
);
|
||||
|
||||
thread_local! {
|
||||
|
@ -27,9 +27,6 @@ pub struct Reference<T: 'static> {
|
|||
finalize_callbacks: Rc<Cell<*mut dyn FnOnce()>>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send for Reference<T> {}
|
||||
unsafe impl<T: Sync> Sync for Reference<T> {}
|
||||
|
||||
impl<T> Drop for Reference<T> {
|
||||
fn drop(&mut self) {
|
||||
let rc_strong_count = Rc::strong_count(&self.finalize_callbacks);
|
||||
|
@ -71,8 +68,9 @@ impl<T: 'static> Reference<T> {
|
|||
if let Some((wrapped_value, napi_ref, finalize_callbacks_ptr)) =
|
||||
REFERENCE_MAP.with(|map| map.borrow().get(&t).cloned())
|
||||
{
|
||||
let mut ref_count = 0;
|
||||
check_status!(
|
||||
unsafe { crate::sys::napi_reference_ref(env, napi_ref, &mut 0) },
|
||||
unsafe { crate::sys::napi_reference_ref(env, napi_ref, &mut ref_count) },
|
||||
"Failed to ref napi reference"
|
||||
)?;
|
||||
let finalize_callbacks_raw = unsafe { Rc::from_raw(finalize_callbacks_ptr) };
|
||||
|
@ -80,9 +78,9 @@ impl<T: 'static> Reference<T> {
|
|||
// Leak the raw finalize callbacks
|
||||
Rc::into_raw(finalize_callbacks_raw);
|
||||
Ok(Self {
|
||||
raw: wrapped_value as *mut T,
|
||||
raw: wrapped_value.cast(),
|
||||
napi_ref,
|
||||
env: env as *mut c_void,
|
||||
env: env.cast(),
|
||||
finalize_callbacks,
|
||||
})
|
||||
} else {
|
||||
|
@ -243,9 +241,6 @@ pub struct SharedReference<T: 'static, S: 'static> {
|
|||
owner: Reference<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send, S: Send> Send for SharedReference<T, S> {}
|
||||
unsafe impl<T: Sync, S: Sync> Sync for SharedReference<T, S> {}
|
||||
|
||||
impl<T: 'static, S: 'static> SharedReference<T, S> {
|
||||
pub fn clone(&self, env: Env) -> Result<Self> {
|
||||
Ok(SharedReference {
|
||||
|
|
Loading…
Reference in a new issue