Repository navigation
closure body elided + js_box_get null-box for class method via async () => driver.find(...) against real @perryts/mongodb #531
Description
Summary
Sibling to #528 (which v0.5.609 closed for its stated standalone repro). Different symptom, different signal — filing separately.
In the @perryts/mongodb benchmark cell on Perry-AOT, the find_movies_year_gte_2010_sort_limit_50 op fingerprints count=1 instead of 50. Instrumenting the adapter shows that PerryTSDriver.find's body never executes at all under perry — not even the first console.log line on entry — even though the closure async () => driver.find(...) is invoked hundreds of times via the bench's runOp wrapper.
The other 8 driver methods on the same class (findOne, distinct, aggregate, insertMany, updateMany, deleteMany, …) all run their bodies correctly under the same closure-through-runOp pattern. Only driver.find is silently elided.
This is a distinct bug class from #528:
- async class method: this.method1().method2() chain returns undefined receiver #528's failure was body-runs-but-
cur-is-undefined from a chainthis.col().find(). v0.5.609'srecv_is_classchain-fold gate fixed that. Verified on the standalone repro. - This failure is body-doesn't-run-at-all. async class method: this.method1().method2() chain returns undefined receiver #528's two-locals workaround does not apply (tested). Renaming
find→findMany(with a delegatingfindstub) also doesn't fix it.
Diagnostic
Instrumented bench/src/adapters/perryts.ts:
async find(db, col, filter, opts): Promise<Doc[]> {
console.log('[adapter.find] entered'); // ← never fires
const findOpts: Record<string, unknown> = {};
if (opts.sort !== undefined) findOpts.sort = opts.sort;
// ...
const cur = this.col(db, col).find(filter as Document, findOpts);
console.log('[adapter.find] cur typeof=' + typeof cur); // ← never fires
const arr = await cur.toArray();
console.log('[adapter.find] arr len=' + ...); // ← never fires
return arr as Doc[];
}Across ~100+ invocations from the bench's runOp closure (async () => driver.find(...)), zero [adapter.find] log lines fire. The closure resolves to undefined.
During the 3 warmup iterations of find_movies, perry-stdlib emits:
[PERRY WARN] js_box_get: invalid box pointer 0x0 #0
[PERRY WARN] js_box_get: invalid box pointer 0x0 #1
[PERRY WARN] js_box_get: invalid box pointer 0x0 #2
That telemetry is from crates/perry-runtime/src/box.rs:45 — it fires when a closure prologue passes a null pointer to js_box_get for a heap-boxed captured mutable variable. This is the smoking gun: the closure for driver.find is being constructed with a missing capture slot. When the closure is invoked, the box-get returns f64::NAN for whatever was supposed to be captured (likely driver or this), and the eventual method dispatch silently no-ops on the null receiver.
Reduction attempts (all passed on v0.5.609)
Could not reduce to a driver-free repro after four attempts:
Driverclass withthis.col().find({})chain — works (5)- Same with closure invoked via runOp HOF — works (5)
- Cross-module split (
Driverexported from another file) — works (5) - 3-deep chain mirroring
this.client.db(d).collection(c).find()— works (5)
So the bug needs the real @perryts/mongodb MongoClient / Db / Collection / FindCursor class hierarchy to manifest. The narrowing factor is presumably one of:
- A specific shape of
this-capture from the bench's runOp wrapper that only the deeper class hierarchy hits - A method-name collision (
PerryTSDriver.findandCollection.find) confusing closure-table dispatch - Cross-module class method dispatch where the boxing of
this(ordriver) is dropped only when the path crosses ≥3 module boundaries
Repro instructions
- Clone
@perryts/mongodb - Build perry from current
main(≥ v0.5.610) - Start a local
mongod perry run bench/run-perryts.ts- Observe
find_movies_year_gte_2010_sort_limit_50 fp=count (1)(expected 50) - Observe 3×
[PERRY WARN] js_box_get: invalid box pointer 0x0warnings on stderr during warmup
Asks
- A driver-free reduction (a synthetic class hierarchy that forces the same
js_box_get: invalid box pointer 0x0shape would be a huge unlock — happy to try more shapes if there's a hypothesis) - Or guidance on what closure-prologue / capture-table state to dump from the failing
driver.findsite to make the box-null capture slot identifiable
Environment
- perry v0.5.610 (HEAD =
30055e2c) - macOS 26.4 (arm64)
- Reference: bun 1.3.12, node 25.8.0 + tsx — both produce the correct
count=50fingerprint
Activity