ReviewOS

home-lang/home

JSTranspiler: failed transform poisons later worker transform

#567
Closed chris opened this 9 days ago ·
9 days ago

Follow-up to #66 and discovered while implementing the native task ownership work in #465.

At pinned Bun baseline 4982b91e3702094330f3be3883354c52b8c01323, a failed and caught main-VM Bun.Transpiler.transform() does not affect a later worker transform. Home's debug runtime instead deterministically crashes in the worker printer with the allocator poison address 0xaaaaaaaaaaaaaaaa.

Reproduction:

import { once } from 'node:events'
import { Worker } from 'node:worker_threads'

try {
  await new Bun.Transpiler().transform('const =')
} catch {}

const worker = new Worker(`
  const { parentPort } = require('node:worker_threads');
  const source = 'export default "' + 'x'.repeat(16 * 1024 * 1024) + '"';
  void new Bun.Transpiler().transform(source);
  parentPort.postMessage('armed');
`, { eval: true })

console.log(await once(worker, 'message'))
console.log(await worker.terminate())

Pinned Bun release output:

[ "armed" ]
0

Home debug exits 134:

Segmentation fault at address 0xaaaaaaaaaaaaaaaa
compiler_rt/memcpy.zig:170 copyFixedLength
packages/runtime/src/js_printer/js_printer.zig:6162 printAst
packages/runtime/src/bundler/transpiler.zig:635 printWithSourceMapMaybe
packages/runtime/src/bundler/transpiler.zig:683 print
packages/runtime/src/runtime/api/JSTranspiler.zig:547 TransformTask.run
packages/runtime/src/jsc/ConcurrentPromiseTask.zig:44 runFromThreadPool

A valid main transform (const value = 1) followed by the same worker transform passes. The failure happens while TransformTask.run is executing, before its completion is queued or shutdown cancellation runs, so it is separate from #465's quiescence/cancellation protocol.

Audit failed-transform cleanup and any mutable parser/transpiler state shared or copied across VMs/threads. Add a bounded regression matching the sequence above and keep the pinned Bun result as the behavioral control.

9 days ago

Resolved on main by f8d70bcd2.

Root cause was not worker allocator corruption: parser recovery returned a diagnostic-bearing already_bundled placeholder whose AST storage is deliberately poisoned in the debug allocator. The async transform path handed that placeholder to the printer before the existing JS-thread completion path could reject the Promise. The fix stops immediately when the task log contains diagnostics, matching the synchronous transform contract; ordinary valid transforms still print normally.

Verification on Darwin:

  • the original invalid async transform followed by a 16 MiB worker transform passes;
  • the same regression passes eight consecutive runs;
  • invalid transform rejection followed by a valid transform in the same VM passes;
  • pinned transpiler API directory: 46 pass / 0 fail / 72 assertions;
  • full native runtime regression batch: 59 pass / 0 fail;
  • debug build, focused Pickier, Zig formatting, and whitespace checks pass.

The separate parser stack-exhaustion crash found during the broader unchanged corpus run is tracked in #574. Full parity remains under #66.

Sign in to comment on this issue.