BBS: TELESC.NET.BR Assunto: get_all_msg_headers(): cold *_NULL fields read undefined via dot-acces De: Rob Swindell Data: Fri, 22 May 2026 13:36:47 -0700 ----------------------------------------------------------- https://gitlab.synchro.net/main/sbbs/-/issues/1143#note_9008 **Root cause is TraceMonkey (`JSOPTION_JIT` / `JS_TRACER`), not the interpreter PropertyCache and that resolves the FreeBSD/Linux divergence** @Deuce your FreeBSD non-repro and your source-trace were both correct. The interpreter PropertyCache *can't* mis-serve here, exactly as you traced. The misprediction is one layer up, in the **trace JIT**, which your build doesn't compile and mine does. Answering your three questions in order, then the decisive A/B. ## Q1 platform / build of the probe runs Not Windows (the commit author tag misled us). The repro is on **Linux**: - `Linux 6.1.0-37-amd64 x86_64`, **gcc (Debian) 14.2.0**, **debug** build of this tree. - jsexec: built from HEAD with both fix commits reverted (local branch = two `git revert`s on master), in an isolated worktree. mozjs is **statically linked into `libsbbs.so`** (no separate .so). - Invocation: `jsexec -c /sbbs/ctrl` against a **read-only copy** of my `data/mail.*` opened by path same method you used. ## Q2 mozjs build flags I'm linking `js-confdefs.h` is **build-generated** here (not tracked in git), and on this Linux/gcc build it contains: ``` #define JS_METHODJIT 1 #define JS_MONOIC 1 #define JS_POLYIC 1 #define JS_POLYIC_TYPED_ARRAY 1 #define JS_PUNBOX64 1 #define JS_THREADSAFE 1 #define JS_TRACER 1 <-- present here; you reported it ABSENT on your tree ``` The linked `libmozjs185-1.0.a` contains the TraceMonkey objects (`TraceRecorder`, `MonitorLoopEdge`, `js::InitJIT`, ...). So **TraceMonkey is compiled in on my build and gone on yours** the single difference that matters. At runtime, `js.options` reads **`0x810`** = `JSOPTION_JIT (0x800) | JSOPTION_COMPILE_N_GO (0x10)`. That's the active default for non-JSDOOR jsexec (`jsexec.cpp:63`, `JAVASCRIPT_OPTIONS` in `sbbsdefs.h:69`) you had this right. Note bit 11 = `JSOPTION_JIT` = **TraceMonkey**; `JSOPTION_METHODJIT` (bit 14, 0x4000) is **not** set. So the active JIT in the repro is TraceMonkey alone. ## Q3 does it reproduce on a fresh local copy opened by path? **Yes.** Verbatim read-only copy of `data/mail.*` `/tmp/maildup/mail.*`, opened with `new MsgBase("/tmp/maildup/mail", true /* is_path */)`. Pre-fix binary: total=7522, genuinely-NULL `to_ext`=215, **cold `hdr.to_ext` undefined=7483, BUG mismatches=7268, defined-but-wrong=0.** So **the SMB-mounted-share layer is ruled out** it's purely the binary/build, against identical bytes. ## The decisive A/B (same binary build procedure, only the JIT bit changes) I rebuilt jsexec twice, flipping only `JSOPTION_JIT` in the compiled default, and confirmed the active options via `js.options` each time: | `js.options` | TraceMonkey | total | cold `to_ext` undef | genuinely-NULL | **BUG mismatches** | defined-but-wrong | |---|---|---|---|---|---|---| | `0x810` (stock) | **on** | 7522 | 7483 | 215 | **7268** | 0 | | `0x10` (`COMPILE_N_GO` only) | **off** | 7522 | **215** | 215 | **0** | 0 | With the tracer off, cold `to_ext` undefined collapses to exactly the genuinely-NULL count (215) zero spurious undefineds. **TraceMonkey is necessary and sufficient for the symptom.** (Methodological note that explains why this took a second pass: my first "JIT off" rebuild edited the `#ifdef JSDOOR` branch at `jsexec.cpp:66/68`, which is dead code for a normal jsexec the active default is `jsexec.cpp:63`. `js.options` still read `0x810`, i.e. JIT was still on. Reading `js.options` at runtime is what caught it; the table above is from editing line 63 and confirming `js.options=0x10`.) ## What this means for the diagnosis and the fix - The earlier "interpreter `JSOP_GETPROP` per-(shape,pc) PropertyCache mispredict" writeup (and `666ff71ce`'s commit message) named the **wrong cache**. The mechanism is the **same shape** bulk headers share one shape lineage but diverge in whether a `*_NULL` field is an own property but it's the **trace recorder's** shape-guarded GETPROP on the hot `for..in` dot-access loop that records a read of an absent slot and replays `undefined`, not the interpreter cache. - Consistent with the prior evidence: **heap-invariant** (not allocation/#1144), **GETELEM-immune** (`JSOP_GETELEM` traces differently from `JSOP_GETPROP`), **defined-but-wrong = 0** (the guard passes on shape, the slot read just yields a hole). The `for..in` loop is exactly the hot path the tracer targets, which is why priming with a non-NULL field first dodges it. - Why `666ff71ce` (eager full-resolve) works: every populated field becomes a real own property at construction, so there's no "present on some headers, absent on others" divergence under a shared shape for the tracer to misrecord. Cost is the loss of laziness, as discussed. ## Candidate directions (updated) 1. **`JSCLASS_NEW_RESOLVE`** still the SM-sanctioned lazy route; would need to be proven against the live base **with TraceMonkey on**, not just in the interpreter. 2. **Keep eager-resolve** (`666ff71ce`) and **correct its commit message** to say "trace-JIT GETPROP misrecord across shared-shape headers with conditional `*_NULL` resolve," not "interpreter property cache." 3. **Disable TraceMonkey** for these workloads `JS_TRACER` is dead upstream (gone from your tree already); turning bit 11 off (or not compiling it) sidesteps the whole class. Worth deciding whether SBBS should still be shipping `JSOPTION_JIT` at all on builds where `JS_TRACER` is compiled in. Happy to run any of these against the copy here (Linux/gcc, tracer on) to validate before you commit to one. I can also bisect the exact trace-recorder GETPROP path under gdb if you want the precise misrecord site. Reproducer used: `probe_to_ext.js` with `new MsgBase("mail")` swapped for `new MsgBase("/tmp/maildup/mail", true)`; everything else byte-identical to the attached scripts. n --- mSynchronetn hgVertrauen n hHome of Synchronet n gh[vert/cvs/bbs].synchro.net ----------------------------------------------------------- [Voltar]