Skip to content
Closed
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
1 change: 0 additions & 1 deletion mordant-baseline.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
"field_valid_only_when:src/runtime/shell/builtin/rm.rs" = 1
"parallel_vecs:src/runtime/api/html_rewriter.rs" = 1
"reimplemented_helper:src/runtime/api/bun/Terminal.rs" = 1
"reimplemented_helper:src/runtime/hw_exports.rs" = 1
"same_match_twice:src/runtime/api/bun/h2_frame_parser.rs" = 1
"same_match_twice:src/runtime/cli/pack_command.rs" = 1
"same_match_twice:src/runtime/cli/update_interactive_command.rs" = 2
Expand Down
34 changes: 12 additions & 22 deletions src/runtime/hw_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,11 +304,8 @@ mod sql_hooks {

// ─── entry-point promise reactions (used by `--print`) ───────────────────────

// HOST_EXPORT(Bun__onResolveEntryPointResult)
pub fn on_resolve_entry_point_result(
global: &JSGlobalObject,
callframe: &CallFrame,
) -> bun_jsc::JsResult<JSValue> {
// A fulfilled value and a rejection reason are printed the same way, as `run_command.rs` does.
fn print_entry_point_result_and_exit(global: &JSGlobalObject, callframe: &CallFrame) -> ! {
let result = callframe.argument(0);
// SAFETY: `vals[..len]` is the single stack `result`; `ctype` is ignored by
// `message_with_type_and_level` (it always resolves the per-VM console via
Expand All @@ -324,30 +321,23 @@ pub fn on_resolve_entry_point_result(
);
}
// SAFETY: bun_vm() never null for a Bun-owned global.
bun_core::Global::exit(u32::from(global.bun_vm().as_mut().exit_handler.exit_code));
bun_core::Global::exit(u32::from(global.bun_vm().as_mut().exit_handler.exit_code))
}

// HOST_EXPORT(Bun__onResolveEntryPointResult)
pub fn on_resolve_entry_point_result(
global: &JSGlobalObject,
callframe: &CallFrame,
) -> bun_jsc::JsResult<JSValue> {
print_entry_point_result_and_exit(global, callframe)
}

// HOST_EXPORT(Bun__onRejectEntryPointResult)
pub fn on_reject_entry_point_result(
global: &JSGlobalObject,
callframe: &CallFrame,
) -> bun_jsc::JsResult<JSValue> {
let result = callframe.argument(0);
// SAFETY: `vals[..len]` is the single stack `result`; `ctype` is ignored by
// `message_with_type_and_level` (it always resolves the per-VM console via
// `vm_console(global)`), so null is fine.
unsafe {
bun_jsc::ConsoleObject::message_with_type_and_level(
core::ptr::null_mut(),
bun_jsc::ConsoleObject::MessageType::Log,
bun_jsc::ConsoleObject::MessageLevel::Log,
global,
&raw const result,
1,
);
}
// SAFETY: bun_vm() never null for a Bun-owned global.
bun_core::Global::exit(u32::from(global.bun_vm().as_mut().exit_handler.exit_code));
print_entry_point_result_and_exit(global, callframe)
}

// ─── bindgenv2 dispatch shims (`bindgen_*_dispatch*`) ────────────────────────
Expand Down
36 changes: 36 additions & 0 deletions test/cli/run/run-eval.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,42 @@ describe("--print for cjs/esm", () => {
});
});

// An uncaught exception stops the event loop while the printed promise is still
// pending, so --print attaches reactions to it and polls once more. The settling
// timer is created inside the throwing callback so it cannot fire in the same
// batch as the throw; the final poll then sleeps until it is due and the reactions
// print the value. Skipped on Windows, where the libuv poll can return before the
// timer is due and the still-pending promise gets printed instead.
describe.concurrent.skipIf(isWindows)("--print of a promise that settles after the event loop stopped", () => {
test.each([
{ settle: "resolve", printed: "settled late" },
{ settle: "reject", printed: "rejected late" },
])("$settle() prints the value", async ({ settle, printed }) => {
await using proc = Bun.spawn({
cmd: [
bunExe(),
"--print",
`new Promise((resolve, reject) => setTimeout(() => {
setTimeout(() => ${settle}(${JSON.stringify(printed)}), 1);
throw new Error("loop killer");
}, 1))`,
],
env: {
...bunEnv,
// detect_leaks=0: the reactions exit without tearing the VM down, so LSan
// reports its still-live state as leaks and aborts the exit.
ASAN_OPTIONS: [bunEnv.ASAN_OPTIONS, "detect_leaks=0"].filter(Boolean).join(":"),
},
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toBe(`${printed}\n`);
expect(stderr).toContain("error: loop killer");
expect(exitCode).toBe(1);
});
});

function group(run: (code: string) => SyncSubprocess<"pipe", "inherit">) {
test("it works", async () => {
const { stdout } = run('console.log("hello world")');
Expand Down
Loading