Pre-existing bug uncovered while regression-testing PR #9521's exception.rs reorder (a forced minor GC reproduces it; not fixed there).
The asymmetry
register_error_code_pub / error_code_for_message key their side tables on the message StringHeader address. The GC's GcMoveHookKind::ErrorSideTables rekey fires for the error object's address. Nothing rekeys the message-keyed entries — so the moment an error's message string is relocated by an evacuating scavenge, its ERR_* code, syscall, errno, path, dest and hostname are silently dropped.
User-visible shape: an fs/net error that was constructed with a full node-style code loses it before it is reported — err.code === undefined where node has "ENOENT", and the uncaught-error report prints without the code. Timing-dependent (needs a scavenge between construction and read), which is exactly the class of bug this campaign keeps finding behind "flaky" symptoms.
Fix shape
Either key the side tables by the error object's address (so the existing ErrorSideTables rekey covers them — check why the message was chosen; there may be construction-order reasons), or add the message key to the rekey hook. The first is structurally better: one rekey path instead of two that can drift.
Verification bar
Reproduced already with an explicit gc_collect_minor() between construction and read — that harness shape (from PR #9521's test work) is the starting point, with the positive control that the string actually moved (assert_ne!(before, after)); a test whose forced collection moves nothing passes on broken code, which was demonstrated on this exact machinery. Cover: err.code read after GC, the uncaught-error report path, and a node-shaped fs error (ENOENT with path) round-tripping through util.inspect.
Evidence and the two documented dead-end harness attempts: PR #9521's uncaught_native_error_report test comments.
Pre-existing bug uncovered while regression-testing PR #9521's exception.rs reorder (a forced minor GC reproduces it; not fixed there).
The asymmetry
register_error_code_pub/error_code_for_messagekey their side tables on the messageStringHeaderaddress. The GC'sGcMoveHookKind::ErrorSideTablesrekey fires for the error object's address. Nothing rekeys the message-keyed entries — so the moment an error's message string is relocated by an evacuating scavenge, itsERR_*code,syscall,errno,path,destandhostnameare silently dropped.User-visible shape: an fs/net error that was constructed with a full node-style code loses it before it is reported —
err.code === undefinedwhere node has"ENOENT", and the uncaught-error report prints without the code. Timing-dependent (needs a scavenge between construction and read), which is exactly the class of bug this campaign keeps finding behind "flaky" symptoms.Fix shape
Either key the side tables by the error object's address (so the existing
ErrorSideTablesrekey covers them — check why the message was chosen; there may be construction-order reasons), or add the message key to the rekey hook. The first is structurally better: one rekey path instead of two that can drift.Verification bar
Reproduced already with an explicit
gc_collect_minor()between construction and read — that harness shape (from PR #9521's test work) is the starting point, with the positive control that the string actually moved (assert_ne!(before, after)); a test whose forced collection moves nothing passes on broken code, which was demonstrated on this exact machinery. Cover:err.coderead after GC, the uncaught-error report path, and a node-shaped fs error (ENOENTwithpath) round-tripping throughutil.inspect.Evidence and the two documented dead-end harness attempts: PR #9521's
uncaught_native_error_reporttest comments.