diff --git a/src/jsc/bindings/BunProcess.cpp b/src/jsc/bindings/BunProcess.cpp index 498fdcf9278f..18d5a3188dc3 100644 --- a/src/jsc/bindings/BunProcess.cpp +++ b/src/jsc/bindings/BunProcess.cpp @@ -1325,7 +1325,7 @@ extern "C" void Bun__promises__emitUnhandledRejectionWarning(JSC::JSGlobalObject if (is_errorlike) { reasonStack = JSValue::decode(reason).get(globalObject, vm.propertyNames->stack); CLEAR_IF_EXCEPTION(scope); - warning->putDirect(vm, vm.propertyNames->stack, reasonStack); + warning->putDirect(vm, vm.propertyNames->stack, reasonStack, JSC::PropertyAttribute::DontEnum | 0); } if (!reasonStack) { reasonStack = JSValue::decode(Bun__noSideEffectsToString(vm, globalObject, reason)); diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js index 9d686f89a812..4410a70ad7ce 100644 --- a/test/js/node/process/process.test.js +++ b/test/js/node/process/process.test.js @@ -1135,6 +1135,51 @@ describe.concurrent(() => { expect({ stdout: stdout.trim(), stderr, exitCode }).toEqual({ stdout: "ok", stderr: "", exitCode: 0 }); }); + it("UnhandledPromiseRejectionWarning installs .stack as non-enumerable", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "--unhandled-rejections=warn", + "-e", + ` + let result; + process.on("warning", w => { + if (w.name !== "UnhandledPromiseRejectionWarning") return; + if (!(w instanceof Error)) return; + const d = Object.getOwnPropertyDescriptor(w, "stack"); + if (!d || w.stack !== reason.stack) return; + let forInHasStack = false; + for (const k in w) if (k === "stack") forInHasStack = true; + result = { + keysIncludesStack: Object.keys(w).includes("stack"), + forInHasStack, + jsonHasStack: "stack" in JSON.parse(JSON.stringify(w)), + enumerable: d.enumerable, + configurable: d.configurable, + }; + }); + const reason = new Error("boom"); + Promise.reject(reason); + process.on("beforeExit", () => console.log(JSON.stringify(result ?? null))); + `, + ], + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect({ result: JSON.parse(stdout.trim() || "null"), exitCode }).toEqual({ + result: { + keysIncludesStack: false, + forInHasStack: false, + jsonHasStack: false, + enumerable: false, + configurable: true, + }, + exitCode: 0, + }); + }); + it("aborts when the uncaughtException handler throws", async () => { const proc = Bun.spawn([bunExe(), join(import.meta.dir, "process-onUncaughtExceptionAbort.js")], { stderr: "pipe",