ReviewOS

home-lang/home

filesystem_router: MatchedRoute.deinit derefs an origin string with a zero refcount (SIGABRT)

#707
Closed glennmichael123 opened this 9 days ago ·
9 days ago

js/bun/util/filesystem_router.test.ts aborts. Pinned Bun scores 22 pass / 0 fail on the same file, so this is a Home gap.

thread N panic: reached unreachable code
  string/wtf.zig:92          deref     bun.assert(self.hasAtLeastOneRef())
  api/filesystem_router.zig  MatchedRoute.deinit  -> origin.deref()
  api/filesystem_router.zig  finalize
  .generated/ZigGeneratedClasses.zig  MatchedRouteClass__finalize
  runtime/timer/WTFTimer.zig  fire / runWithoutRemoving

The assert is hasAtLeastOneRef(), i.e. the RefString is already at zero when MatchedRoute.deinit drops its reference. It runs from the GC finalizer, after origin should be validated and the skipped handles filenames containing byte 0xFF.

Ruled out

  • Repeated deinit of one object. 445b0d5f3's follow-up made both FileSystemRouter.finalize and MatchedRoute.deinit clear each field before dropping it (mirroring the .take() in Bun's filesystem_router.rs:705-713 and :851-859). The abort is unchanged, same line, same assert.
  • A second construction path. grep 'MatchedRoute{' finds exactly one site, inside MatchedRoute.init, and that init refs all three strings (base_dir.ref(), o.ref(), prefix.ref()) exactly as Bun's does.

So the ref/deref accounting within filesystem_router.zig is symmetric.

Where I would look next

FileSystemRouter.fromJS builds the string at filesystem_router.zig:163:

.origin = if (origin_str.len > 0) vm.refCountedString(origin_str.slice(), null, true) else null,

Bun's equivalent is vm.ref_counted_string::<true>(origin_str.slice(), None) (filesystem_router.rs:332-334). The question is whether Home's refCountedString returns an owned reference (count 1, caller must deref) or a borrowed/interned one whose count is managed by the VM's string cache — and whether that matches what both finalize paths assume. If it hands back a deduped entry the VM also drops, the count reaches zero before the finalizer runs, which is exactly the symptom.

Worth checking the same contract for asset_prefix and base_dir, which are acquired the same way.

Refs #66

Sign in to comment on this issue.