Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/js_parser/scan/scan_side_effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,13 @@ impl SideEffects {
Self::find_identifiers(binding, &mut decls);
}

// Drop the statement entirely when the destructuring pattern binds no
// identifiers — e.g. `if (0) var []`. An empty `var;` is invalid syntax,
// and the printer asserts it never sees an empty decl list.
if decls.is_empty() {
return false;
}

local.decls = G::DeclList::move_from_list(decls);
true
}
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/wtf-bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ extern "C" int uv_tty_reset_mode(void)

static void uv__tty_make_raw(struct termios* tio)
{
assert(tio != NULL);
ASSERT(tio != NULL);

#if defined __sun || defined __MVS__
/*
Expand Down
70 changes: 70 additions & 0 deletions test/regression/issue/31002.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// https://github.com/oven-sh/bun/issues/31002
//
// `if (0) var []` (a destructuring `var` with zero bindings inside a
// statically-dead branch) panicked the printer with
// `panic: internal error: entered unreachable code` at
// `src/js_printer/lib.rs:2308`. The printer asserts that a `var` decl list
// is never empty ("var;" is invalid syntax). The dead-code elimination
// pass in `scan_side_effects.rs::should_keep_stmt_in_dead_control_flow`
// hoists identifiers out of destructuring patterns to preserve `var`
// hoisting semantics; when the pattern binds no identifiers (`var []`,
// `var {}`) the list collapsed to empty but the statement was still
// kept, tripping the printer invariant in the next pass.
//
// The fix: when the hoisted identifiers vector is empty, drop the whole
// statement. There is nothing to hoist and no valid `var` output exists.
//
// Run in a subprocess so a reintroduction (panic in the child) doesn't
// tear down the in-process test runner.

import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";

test.concurrent.each([
// Bare `var []` / `var {}` as the only statement in a dead `if` body.
// These were the exact forms in the bug report; without the fix, each
// one crashed the printer.
"if(0)var[]",
"if(0)var{}",
// Same empty destructuring inside a function body — the DCE path runs
// per-scope and must not crash regardless of enclosing scope.
"function f(){if(0)var[]}",
])("`%s` does not crash the printer (dead-branch destructuring var with no bindings)", async source => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", source],
env: bunEnv,
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
timeout: 10_000,
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

// Before the fix: stderr carried `panic: internal error: entered
// unreachable code` and the process exited with a crash signal.
// After the fix: the dead branch is dropped and the program exits 0
// with no output.
expect(stdout).toBe("");
expect(stderr).toBe("");
expect(proc.signalCode).toBeNull();
expect(exitCode).toBe(0);
});

test.concurrent("dead-branch destructuring with real identifiers still hoists them as `undefined`", async () => {
// Regression guard: the fix must not drop destructuring `var`s that do
// introduce bindings — those still need to be hoisted so that reads
// after the dead `if` see `undefined` (not ReferenceError).
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", "if(0) var [a, b] = []; console.log(typeof a, typeof b);"],
env: bunEnv,
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
timeout: 10_000,
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stdout).toBe("undefined undefined\n");
expect(stderr).toBe("");
expect(exitCode).toBe(0);
});
Loading