Skip to content

Rust JSG: jsg::Rc moved out of a traced field is neither strong nor traced #7976

Open

Description

@piscisaureus

A jsg::Rc that GC has visited inside a traced field gives up its
strong reference and relies on its parent's trace instead (its
strong flag is false). If Rust then moves it out of the field, for
example with Cell::take, it is neither strong nor traced. A GC can
then collect the resource's JS wrapper while Rust still holds the
Rc.

This is not a use-after-free: the Rc still keeps the Rust value and
the C++ Wrappable allocated. But the resource loses its JS identity
(a new wrapper is created the next time it is passed to JavaScript),
and nothing traces the resource's own fields any more, so a
TracedGlobal it holds can lose its value and as_local panics.

#[jsg_resource]
impl Scheduler {
    #[jsg_method]
    fn run(&self, lock: &mut jsg::Lock) {
        let task = self.pending.take().unwrap(); // moved out, not strong
        call_into_js(lock);                      // a GC here can collect task's wrapper
        task.callback(lock);
    }
}

C++ JSG doesn't have this problem: the jsg::Ref move constructor
makes a moved-from traced reference strong again
(src/workerd/jsg/jsg.h, Ref(Ref&& other) calls addStrongRef()
when other.strong is false). Rust has no hook on moves, which is the
same reason a weak TracedGlobal needed a stable boxed slot.

Workaround: clone the Rc after moving it out
(self.pending.take().unwrap().clone()); a clone always starts
strong.

Possible fixes: a jsg-provided field type whose take/replace
returns a strong Rc, or making traced fields hold a type that can't
be moved out without going through such an API. Reported by
@littledivy in review of #7966.

Metadata

Metadata

Assignees

No one assigned

    Labels

    jsgrustPull requests that update rust code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions