You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
GC: explicit gc() (full collect, default auto stack-scan) reclaims live top-level locals — string fields read back as garbage #4977
constkeep={nested: {deep: "leaf-string-4916"}};gc();console.log(keep.nested.deep.length);// garbage, e.g. 3833593936console.log(keep.nested.deep);// [invalid utf8]
Same corruption with a class instance (class Widget { x = 1; constructor(public name: string) {} }) reading w.name after gc(). Reproduces in both auto-optimize and PERRY_NO_AUTO_OPTIMIZE=1 builds (v0.5.1155, macOS arm64).
Bisection
PERRY_CONSERVATIVE_STACK_SCAN=full → correct (prints 16 / the string).
PERRY_GEN_GC=0 (legacy full mark-sweep) → correct.
Default (auto scan mode) → live objects reclaimed; later field reads return dangling-pointer garbage.
So in the default auto mode (gc/roots.rs::conservative_stack_scan_decision_for maps Auto → SkipDisabled), a full collection skips the native stack scan, and module-init/top-level locals like keep are evidently not covered by the precise shadow-stack roots or the module-var scanners at the gc() callsite — the whole object graph is treated as dead. The auto skip was motivated by copied-minor eligibility ("copied-minor eligibility only depends on exact mutable roots"), but it also applies to explicit full collections where these locals are live.
Impact
Any program calling the documented explicit gc() (or any API that forces a full collection) with live state held in top-level locals can have that state reclaimed and read back as garbage — silent memory corruption, no crash.
v8.getHeapSnapshot()/writeHeapSnapshot() (#4916) force a full collection like Node does; they sidestep this by setting the per-thread set_conservative_stack_scan_override(Full) around their collect. Explicit gc() likely wants the same treatment (or shadow-stack coverage for module-init locals) — the override mechanism already exists for the GC unit tests.
Found while validating the real heap-snapshot graph for #4916.
Activity
feat(diagnostics): real v8 heap snapshot from GC heap walk + inspecto…
fix(gc): explicit gc() forces the conservative native-stack scan (#4977…
fix(gc): close the #5029 conservative-scan x evacuation corruption — …
test(gc-ratchet): classify a retention breach instead of guessing at …
test(gc-ratchet): classify a retention breach instead of guessing at …