Skip to content

No public weak reference API for JsObject #5420

Description

@jerry4718

Summary

There is currently no way for an embedder to hold a weak reference to a JsObject.
The GC types WeakGc and Ephemeron are public, but they require &Gc<T> to construct,
and the only path from JsObject to its inner Gc<VTableObject<T>> — the inner() method — is pub(crate).
This leaves embedders without any mechanism to observe object liveness without keeping strong references that prevent collection.

The problem

Consider an embedder that maintains a Rust-side cache keyed by JS objects.
The cache should not keep JS objects alive on its own. When a JS object becomes unreachable and is collected by the GC, the corresponding cache entry should become invalid.

This requires a weak reference to JsObject: the ability to check whether the object is still alive, and to upgrade back to a strong JsObject when it is.

What is available

  • WeakGc<T> and Ephemeron<K, V> are pub in boa_gc.
  • WeakGc::upgrade() returns Option<Gc<T>> — exactly the right semantics.
  • WeakGc::new() requires &Gc<T>.

What is blocked

  • JsObject::inner() is pub(crate):
// core/engine/src/object/jsobject.rs:1041
pub(crate) fn inner(&self) -> &Gc<VTableObject<T>> {
    &self.inner
}
  • JsObject::from_inner() is also pub(crate):
// core/engine/src/object/jsobject.rs:1045
pub(crate) fn from_inner(inner: Gc<VTableObject<T>>) -> Self {
    Self { inner }
}
  • VTableObject itself is pub(crate).

So an external crate cannot write the type Gc<VTableObject<...>>, cannot call inner() to get it, and cannot call from_inner() to convert back. The entire WeakGc / Ephemeron API is unreachable for JsObject.

Where weak references are used today

FinalizationRegistry and WeakRef both use weak references inside boa_engine, where pub(crate) is accessible:

// finalization_registry/mod.rs
WeakGc::new(obj.inner())        // accessible within boa_engine
Ephemeron::new(target_obj.inner(), ...)  // accessible within boa_engine

This means the existing weak reference infrastructure is exercised internally, but is not currently reachable from external crates.

Why this matters

Without a weak reference API, embedders who need Rust-side structures keyed by JS objects have two options, neither of which is sound:

  1. Hold strong JsObject references. This prevents GC collection, leading to memory leaks.

  2. Store raw pointers via transmute or similar. This breaks GC safety: the pointer may dangle after collection, ref_count becomes inconsistent, and casting back to JsObject can trigger use-after-free or ref_count underflow.

Questions

  • Is the absence of a public weak reference API for JsObject an intentional design decision, or something that hasn't been needed yet?
  • If it is intentional, what approach would you recommend for embedders who need to observe object liveness without preventing GC collection?
  • Is there any thoughts on exposing this kind of functionality in the future, or should embedders plan to maintain a fork for now?

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-APIChanges related to public APIsA-EnhancementNew feature or requestE-EasyEasy difficulty problemR-TriagedIssue reviewed by the maintainer team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions