Repository: wasm-bindgen/wasm-bindgen (Rust)

Issue:
FinalizationRegistry.register() call missing in exported rust types that define a constructor
### Describe the Bug

`FinalizationRegistry.register()` call missing in exported rust types that define a constructor, preventing cleanup.

### Steps to Reproduce

1. Add this rust code to one of the wasm-bindgen tests, e.g. `test/api.rs`:

```rust
#[wasm_bindgen]
pub struct ObjectWithConstructor {}

#[wasm_bindgen]
impl ObjectWithConstructor {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        Self {}
    }
}
```

2. Run tests to generate JS code

3. View generated JS code:

```javascript
const ObjectWithConstructorFinalization = (typeof FinalizationRegistry === 'undefined')
    ? { register: () => {}, unregister: () => {} }
    : new FinalizationRegistry(ptr => wasm.__wbg_objectwithconstructor_free(ptr >>> 0));
/**
*/
class ObjectWithConstructor {

    __destroy_into_raw() {
        const ptr = this.__wbg_ptr;
        this.__wbg_ptr = 0;
        ObjectWithConstructorFinalization.unregister(this);
        return ptr;
    }

    free() {
        const ptr = this.__destroy_into_raw();
        wasm.__wbg_objectwithconstructor_free(ptr);
    }
    /**
    */
    constructor() {
        const ret = wasm.objectwithconstructor_new();
        this.__wbg_ptr = ret >>> 0;
        return this;
    }
}
module.exports.ObjectWithConstructor = ObjectWithConstructor;
```

### Expected Behavior

I expect there to be a line in the constructor to register, e.g. `ObjectWithConstructorFinalization.register(this, this.__wbg_ptr, this);`

### Actual Behavior

In the `ObjectWithConstructor` code, there is never a call to register the object destruction callback. 

If we compare to this object, which does not define a constructor:

```rust
#[wasm_bindgen]
pub struct ObjectNoConstructor {}

#[wasm_bindgen]
impl ObjectNoConstructor {
    pub fn new() -> Self {
        Self {}
    }
}
```

It has a `register` call in the `__wrap` function:

```javascript
const ObjectNoConstructorFinalization = (typeof FinalizationRegistry === 'undefined')
    ? { register: () => {}, unregister: () => {} }
    : new FinalizationRegistry(ptr => wasm.__wbg_objectnoconstructor_free(ptr >>> 0));
/**
*/
class ObjectNoConstructor {

    constructor() {
        throw new Error('cannot invoke `new` directly');
    }

    static __wrap(ptr) {
        ptr = ptr >>> 0;
        const obj = Object.create(ObjectNoConstructor.prototype);
        obj.__wbg_ptr = ptr;
        ObjectNoConstructorFinalization.register(obj, obj.__wbg_ptr, obj);
        return obj;
    }

    __destroy_into_raw() {
        const ptr = this.__wbg_ptr;
        this.__wbg_ptr = 0;
        ObjectNoConstructorFinalization.unregister(this);
        return ptr;
    }

    free() {
        const ptr = this.__destroy_into_raw();
        wasm.__wbg_objectnoconstructor_free(ptr);
    }
    /**
    * @returns {ObjectNoConstructor}
    */
    static new() {
        const ret = wasm.objectnoconstructor_new();
        return ObjectNoConstructor.__wrap(ret);
    }
}
module.exports.ObjectNoConstructor = ObjectNoConstructor;
```

### Additional Context

Should the register call be generated [here](https://github.com/rustwasm/wasm-bindgen/blob/0c09e154cf37b7e7f2308787d5186056cf85b533/crates/cli-support/src/js/binding.rs#L1021)?



Discussion: I'm not familiar with this part of the code, but whats missing is a call to [`Context::require_class_wrap()`](https://github.com/rustwasm/wasm-bindgen/blob/0c09e154cf37b7e7f2308787d5186056cf85b533/crates/cli-support/src/js/mod.rs#L2224-L2226).

Happy to dig into it if a PR is provided.



Fix (patch):
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e18609b7d7..2b528788265 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -55,6 +55,12 @@
 * Fix missing target features in module when enabling reference types or multi-value transformation.
   [#3967](https://github.com/rustwasm/wasm-bindgen/pull/3967)
 
+* Fixed Rust values getting GC'd while still borrowed.
+  [#3940](https://github.com/rustwasm/wasm-bindgen/pull/3940)
+
+* Fixed Rust values not getting GC'd if they were created via. a constructor.
+  [#3940](https://github.com/rustwasm/wasm-bindgen/pull/3940)
+
 --------------------------------------------------------------------------------
 
 ## [0.2.92](https://github.com/rustwasm/wasm-bindgen/compare/0.2.91...0.2.92)
diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs
index 123c4ea9036..91aa23040e6 100644
--- a/crates/backend/src/ast.rs
+++ b/crates/backend/src/ast.rs
@@ -108,7 +108,7 @@ pub struct Export {
 
 /// The 3 types variations of `self`.
 #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
-#[derive(Clone)]
+#[derive(Copy, Clone)]
 pub enum MethodSelf {
     /// `self`
     ByValue,
diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs
index 330ffba6626..268d74fc42c 100644
--- a/crates/backend/src/codegen.rs
+++ b/crates/backend/src/codegen.rs
@@ -239,9 +239,9 @@ impl ToTokens for ast::Struct {
                 type Abi = u32;
 
                 fn into_abi(self) -> u32 {
-                    use #wasm_bindgen::__rt::std::boxed::Box;
+                    use #wasm_bindgen::__rt::std::rc::Rc;
                     use #wasm_bindgen::__rt::WasmRefCell;
-                    Box::into_raw(Box::new(WasmRefCell::new(self))) as u32
+                    Rc::into_raw(Rc::new(WasmRefCell::new(self))) as u32
                 }
             }
 
@@ -250,14 +250,19 @@ impl ToTokens for ast::Struct {
                 type Abi = u32;
 
                 unsafe fn from_abi(js: u32) -> Self {
-                    use #wasm_bindgen::__rt::std::boxed::Box;
+                    use #wasm_bindgen::__rt::std::rc::Rc;
+                    use #wasm_bindgen::__rt::std::result::Result::{Ok, Err};
                     use #wasm_bindgen::__rt::{assert_not_null, WasmRefCell};
 
                     let ptr = js as *mut WasmRefCell<#name>;
                     assert_not_null(ptr);
-                    let js = Box::from_raw(ptr);
-                    (*js).borrow_mut(); // make sure no one's borrowing
-                    js.into_inner()
+                    let rc = Rc::from_raw(ptr);
+                    match Rc::try_unwrap(rc) {
+                        Ok(cell) => cell.into_inner(),
+                        Err(_) => #wasm_bindgen::throw_str(
+                            "attempted to take ownership of Rust value while it was borrowed"
+                        ),
+                    }
                 }
             }
 
@@ -291,39 +296,62 @@ impl ToTokens for ast::Struct {
             const _: () = {
                 #[no_mangle]
                 #[doc(hidden)]
-                pub unsafe extern "C" fn #free_fn(ptr: u32) {
-                    let _ = <#name as #wasm_bindgen::convert::FromWasmAbi>::from_abi(ptr); //implicit `drop()`
+                // `allow_delayed` is whether it's ok to not actually free the `ptr` immediately
+                // if it's still borrowed.
+                pub unsafe extern "C" fn #free_fn(ptr: u32, allow_delayed: u32) {
+                    use #wasm_bindgen::__rt::std::rc::Rc;
+
+                    if allow_delayed != 0 {
+                        // Just drop the implicit `Rc` owned by JS, and then if the value is still
+                        // referenced it'll be kept alive by its other `Rc`s.
+                        let ptr = ptr as *mut #wasm_bindgen::__rt::WasmRefCell<#name>;
+                        #wasm_bindgen::__rt::assert_not_null(ptr);
+                        drop(Rc::from_raw(ptr));
+                    } else {
+                        // Claim ownership of the value, which will panic if it's borrowed.
+                        let _ = <#name as #wasm_bindgen::convert::FromWasmAbi>::from_abi(ptr);
+                    }
                 }
             };
 
             #[automatically_derived]
             impl #wasm_bindgen::convert::RefFromWasmAbi for #name {
                 type Abi = u32;
-                type Anchor = #wasm_bindgen::__rt::Ref<'static, #name>;
+                type Anchor = #wasm_bindgen::__rt::RcRef<#name>;
 
                 unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor {
+                    use #wasm_bindgen::__rt::std::rc::Rc;
+
                     let js = js as *mut #wasm_bindgen::__rt::WasmRefCell<#name>;
                     #wasm_bindgen::__rt::assert_not_null(js);
-                    (*js).borrow()
+
+                    Rc::increment_strong_count(js);
+                    let rc = Rc::from_raw(js);
+                    #wasm_bindgen::__rt::RcRef::new(rc)
                 }
             }
 
             #[automatically_derived]
             impl #wasm_bindgen::convert::RefMutFromWasmAbi for #name {
                 type Abi = u32;
-                type Anchor = #wasm_bindgen::__rt::RefMut<'static, #name>;
+                type Anchor = #wasm_bindgen::__rt::RcRefMut<#name>;
 
                 unsafe fn ref_mut_from_abi(js: Self::Abi) -> Self::Anchor {
+                    use #wasm_bindgen::__rt::std::rc::Rc;
+
                     let js = js as *mut #wasm_bindgen::__rt::WasmRefCell<#name>;
                     #wasm_bindgen::__rt::assert_not_null(js);
-                    (*js).borrow_mut()
+
+                    Rc::increment_strong_count(js);
+                    let rc = Rc::from_raw(js);
+                    #wasm_bindgen::__rt::RcRefMut::new(rc)
                 }
             }
 
             #[automatically_derived]
             impl #wasm_bindgen::convert::LongRefFromWasmAbi for #name {
                 type Abi = u32;
-                type Anchor = #wasm_bindgen::__rt::Ref<'static, #name>;
+                type Anchor = #wasm_bindgen::__rt::RcRef<#name>;
 
                 unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor {
                     <Self as #wasm_bindgen::convert::RefFromWasmAbi>::ref_from_abi(js)
@@ -562,12 +590,24 @@ impl TryToTokens for ast::Export {
             }
             Some(ast::MethodSelf::RefShared) => {
                 let class = self.rust_class.as_ref().unwrap();
+                let (trait_, func, borrow) = if self.function.r#async {
+                    (
+                        quote!(LongRefFromWasmAbi),
+                        quote!(long_ref_from_abi),
+                        quote!(
+                            <<#class as #wasm_bindgen::convert::LongRefFromWasmAbi>
+                                ::Anchor as #wasm_bindgen::__rt::std::borrow::Borrow<#class>>
+                                ::borrow(&me)
+                        ),
+                    )
+                } else {
+                    (quote!(RefFromWasmAbi), quote!(ref_from_abi), quote!(&*me))
+                };
                 arg_conversions.push(quote! {
                     let me = unsafe {
-                        <#class as #wasm_bindgen::convert::RefFromWasmAbi>
-                            ::ref_from_abi(me)
+                        <#class as #wasm_bindgen::convert::#trait_>::#func(me)
                     };
-                    let me = &*me;
+                    let me = #borrow;
                 });
                 quote! { me.#name }
             }
diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs
index 31863da50ab..dfbd28b8b2f 100644
--- a/crates/cli-support/src/js/binding.rs
+++ b/crates/cli-support/src/js/binding.rs
@@ -1129,7 +1129,12 @@ fn instruction(
             let val = js.pop();
             match constructor {
                 Some(name) if name == class => {
-                    js.prelude(&format!("this.__wbg_ptr = {} >>> 0;", val));
+                    js.prelude(&format!(
+                        "
+                        this.__wbg_ptr = {val} >>> 0;
+                        {name}Finalization.register(this, this.__wbg_ptr, this);
+                        "
+                    ));
                     js.push(String::from("this"));
                 }
                 Some(_) | None => {
diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs
index 8c270186bf7..97a7cbd8044 100644
--- a/crates/cli-support/src/js/mod.rs
+++ b/crates/cli-support/src/js/mod.rs
@@ -948,7 +948,7 @@ impl<'a> Context<'a> {
             "
             const {name}Finalization = (typeof FinalizationRegistry === 'undefined')
                 ? {{ register: () => {{}}, unregister: () => {{}} }}
-                : new FinalizationRegistry(ptr => wasm.{}(ptr >>> 0));",
+                : new FinalizationRegistry(ptr => wasm.{}(ptr >>> 0, 1));",
             wasm_bindgen_shared::free_function(name),
         ));
 
@@ -1021,7 +1021,7 @@ impl<'a> Context<'a> {
 
             free() {{
                 const ptr = this.__destroy_into_raw();
-                wasm.{}(ptr);
+                wasm.{}(ptr, 0);
             }}
             ",
             wasm_bindgen_shared::free_function(name),
diff --git a/src/lib.rs b/src/lib.rs
index f09bec2d213..d0cee09c6b0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1610,6 +1610,98 @@ pub mod __rt {
         );
     }
 
+    if_std! {
+        use std::rc::Rc;
+
+        /// A type that encapsulates an `Rc<WasmRefCell<T>>` as well as a `Ref`
+        /// to the contents of that `WasmRefCell`.
+        ///
+        /// The `'static` requirement is an unfortunate consequence of how this
+        /// is implemented.
+        pub struct RcRef<T: ?Sized + 'static> {
+            // The 'static is a lie.
+            //
+            // We could get away without storing this, since we're in the same module as
+            // `WasmRefCell` and can directly manipulate its `borrow`, but I'm considering
+            // turning it into a wrapper around `std`'s `RefCell` to reduce `unsafe` in
+            // which case that would stop working. This also requires less `unsafe` as is.
+            //
+            // It's important that this goes before `Rc` so that it gets dropped first.
+            ref_: Ref<'static, T>,
+            _rc: Rc<WasmRefCell<T>>,
+        }
+
+        impl<T: ?Sized> RcRef<T> {
+            pub fn new(rc: Rc<WasmRefCell<T>>) -> Self {
+                let ref_ = unsafe { (*Rc::as_ptr(&rc)).borrow() };
+                Self { _rc: rc, ref_ }
+            }
+        }
+
+        impl<T: ?Sized> Deref for RcRef<T> {
+            type Target = T;
+
+            #[inline]
+            fn deref(&self) -> &T {
+                &self.ref_
+            }
+        }
+
+        impl<T: ?Sized> Borrow<T> for RcRef<T> {
+            #[inline]
+            fn borrow(&self) -> &T {
+                &self.ref_
+            }
+        }
+
+        /// A type that encapsulates an `Rc<WasmRefCell<T>>` as well as a
+        /// `RefMut` to the contents of that `WasmRefCell`.
+        ///
+        /// The `'static` requirement is an unfortunate consequence of how this
+        /// is implemented.
+        pub struct RcRefMut<T: ?Sized + 'static> {
+            ref_: RefMut<'static, T>,
+            _rc: Rc<WasmRefCell<T>>,
+        }
+
+        impl<T: ?Sized> RcRefMut<T> {
+            pub fn new(rc: Rc<WasmRefCell<T>>) -> Self {
+                let ref_ = unsafe { (*Rc::as_ptr(&rc)).borrow_mut() };
+                Self { _rc: rc, ref_ }
+            }
+        }
+
+        impl<T: ?Sized> Deref for RcRefMut<T> {
+            type Target = T;
+
+            #[inline]
+            fn deref(&self) -> &T {
+                &self.ref_
+            }
+        }
+
+        impl<T: ?Sized> DerefMut for RcRefMut<T> {
+            #[inline]
+            fn deref_mut(&mut self) -> &mut T {
+                &mut self.ref_
+            }
+        }
+
+        impl<T: ?Sized> Borrow<T> for RcRefMut<T> {
+            #[inline]
+            fn borrow(&self) -> &T {
+                &self.ref_
+            }
+        }
+
+        impl<T: ?Sized> BorrowMut<T> for RcRefMut<T> {
+            #[inline]
+            fn borrow_mut(&mut self) -> &mut T {
+                &mut self.ref_
+            }
+        }
+    }
+
     if_std! {
         use std::alloc::{alloc, dealloc, realloc, Layout};