Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions src/jsc/bindings/FormatStackTraceForJS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,12 @@ JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncAppendStackTrace, (JSC::JSGlobalObj
return {};
}

// Appending an error's stack trace to itself would make Vector::appendVector
// read from its own (possibly reallocated and freed) buffer.
if (source == destination) {
return JSC::JSValue::encode(jsUndefined());
}

if (!destination->stackTrace()) {
destination->captureStackTrace(vm, globalObject, 1);
}
Expand Down
49 changes: 49 additions & 0 deletions test/js/bun/util/error-append-stack-trace.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";

// Appending an error's stack trace to itself made Vector::appendVector read
// from its own freed buffer once the append grew past the vector's capacity.
// Malloc=1 routes WTF allocations through the system allocator so ASan builds
// can see the use-after-free; symbolize=0 keeps the failing child fast.
test("Error.appendStackTrace with the same error as source and destination", async () => {
const code = `
function f(n) {
if (n > 0) return f(n - 1) + 1;
try {
null();
} catch (e) {
Error.appendStackTrace(e, e);
}
return 0;
}
f(64);
console.log("ok");
`;
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", code],
env: {
...bunEnv,
Malloc: "1",
ASAN_OPTIONS: [bunEnv.ASAN_OPTIONS, "symbolize=0"].filter(Boolean).join(":"),
},
stdout: "pipe",
stderr: "pipe",
});
const [stdout, , exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toBe("ok\n");
expect(exitCode).toBe(0);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
});

test("Error.appendStackTrace moves the source stack trace into the destination", () => {
function inner() {
try {
null();
} catch (e) {
return e;
}
}
const src = inner();
const dst = new Error("dst");
(Error as any).appendStackTrace(src, dst);
expect(dst.stack).toContain("inner");
});
Loading