Compare commits

..

2 Commits

Author SHA1 Message Date
autofix-ci[bot]
50c7c9d994 [autofix.ci] apply automated fixes 2026-02-26 10:03:38 +00:00
Sosuke Suzuki
2742418ea1 fix: add missing return after throwException in console.Console getter
The getConsoleConstructor custom getter calls profiledCall to execute
createConsoleConstructor. When this call throws (e.g. stack overflow),
throwException was called but execution continued to putDirect with an
invalid result, caching undefined as the Console property permanently.

Add return {} after throwException to properly propagate the exception.
2026-02-26 19:00:30 +09:00
4 changed files with 53 additions and 2 deletions

View File

@@ -154,7 +154,7 @@ NodeVMModule::NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String i
: Base(vm, structure)
, m_identifier(WTF::move(identifier))
, m_context(context && context.isObject() ? asObject(context) : nullptr, JSC::WriteBarrierEarlyInit)
, m_moduleWrapper(moduleWrapper, JSC::WriteBarrierEarlyInit)
, m_moduleWrapper(vm, this, moduleWrapper)
{
}

View File

@@ -54,7 +54,7 @@ private:
NodeVMSyntheticModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier, JSValue context, JSValue moduleWrapper, WTF::HashSet<String> exportNames, JSValue syntheticEvaluationSteps)
: Base(vm, structure, WTF::move(identifier), context, moduleWrapper)
, m_exportNames(WTF::move(exportNames))
, m_syntheticEvaluationSteps(syntheticEvaluationSteps, JSC::WriteBarrierEarlyInit)
, m_syntheticEvaluationSteps(vm, this, syntheticEvaluationSteps)
{
}

View File

@@ -2450,6 +2450,7 @@ JSC_DEFINE_CUSTOM_GETTER(getConsoleConstructor, (JSGlobalObject * globalObject,
if (returnedException) {
auto scope = DECLARE_THROW_SCOPE(vm);
throwException(globalObject, scope, returnedException.get());
return {};
}
console->putDirect(vm, property, result, 0);
return JSValue::encode(result);

View File

@@ -0,0 +1,50 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
describe("console.Console getter", () => {
test("handles exception from internal call without crashing", async () => {
// When the stack is nearly exhausted, accessing console.Console triggers
// profiledCall to createConsoleConstructor, which throws StackOverflow.
// The C++ getter must return early after throwException, not continue
// to putDirect with an invalid result.
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
// Exhaust most of the stack, then try to access console.Console
// createConsoleConstructor needs significant stack space (require calls etc.)
function exhaust() {
try {
exhaust();
} catch (e) {
// Near the stack limit - this access should throw cleanly, not crash
try {
void console.Console;
} catch (e2) {
// Expected: stack overflow, not a crash
}
}
}
exhaust();
// After stack recovery, console.Console should still work
const C = console.Console;
if (typeof C !== "function") {
process.exit(1);
}
console.log("OK");
`,
],
env: bunEnv,
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).not.toContain("panic");
expect(stderr).not.toContain("Segmentation fault");
expect(stdout.trim()).toBe("OK");
expect(exitCode).toBe(0);
});
});