Fix crash initializing process stdio streams while process is overridden (#19978)

This commit is contained in:
190n
2025-05-28 22:57:59 -07:00
committed by GitHub
parent 24b3de1bc3
commit 2bb36ca6b4
3 changed files with 56 additions and 15 deletions

View File

@@ -2030,13 +2030,14 @@ enum class BunProcessStdinFdType : int32_t {
extern "C" BunProcessStdinFdType Bun__Process__getStdinFdType(void*, int fd);
extern "C" void Bun__ForceFileSinkToBeSynchronousForProcessObjectStdio(JSC::JSGlobalObject*, JSC::EncodedJSValue);
static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, int fd)
static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, JSC::JSObject* processObject, int fd)
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSC::JSFunction* getStdioWriteStream = JSC::JSFunction::create(vm, globalObject, processObjectInternalsGetStdioWriteStreamCodeGenerator(vm), globalObject);
JSC::MarkedArgumentBuffer args;
args.append(processObject);
args.append(JSC::jsNumber(fd));
args.append(jsBoolean(bun_stdio_tty[fd]));
BunProcessStdinFdType fdType = Bun__Process__getStdinFdType(Bun::vm(vm), fd);
@@ -2045,8 +2046,11 @@ static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, int
JSC::CallData callData = JSC::getCallData(getStdioWriteStream);
auto result = JSC::profiledCall(globalObject, ProfilingReason::API, getStdioWriteStream, callData, globalObject->globalThis(), args);
scope.assertNoExceptionExceptTermination();
CLEAR_AND_RETURN_IF_EXCEPTION(scope, jsUndefined());
if (auto* exception = scope.exception()) {
Zig::GlobalObject::reportUncaughtExceptionAtEventLoop(globalObject, exception);
scope.clearException();
return jsUndefined();
}
ASSERT_WITH_MESSAGE(JSC::isJSArray(result), "Expected an array from getStdioWriteStream");
JSC::JSArray* resultObject = JSC::jsCast<JSC::JSArray*>(result);
@@ -2077,12 +2081,12 @@ static JSValue constructStdioWriteStream(JSC::JSGlobalObject* globalObject, int
static JSValue constructStdout(VM& vm, JSObject* processObject)
{
return constructStdioWriteStream(processObject->globalObject(), 1);
return constructStdioWriteStream(processObject->globalObject(), processObject, 1);
}
static JSValue constructStderr(VM& vm, JSObject* processObject)
{
return constructStdioWriteStream(processObject->globalObject(), 2);
return constructStdioWriteStream(processObject->globalObject(), processObject, 2);
}
#if OS(WINDOWS)
@@ -2092,17 +2096,22 @@ static JSValue constructStderr(VM& vm, JSObject* processObject)
static JSValue constructStdin(VM& vm, JSObject* processObject)
{
auto* globalObject = processObject->globalObject();
auto scope = DECLARE_THROW_SCOPE(vm);
JSC::JSFunction* getStdioWriteStream = JSC::JSFunction::create(vm, globalObject, processObjectInternalsGetStdinStreamCodeGenerator(vm), globalObject);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSC::JSFunction* getStdinStream = JSC::JSFunction::create(vm, globalObject, processObjectInternalsGetStdinStreamCodeGenerator(vm), globalObject);
JSC::MarkedArgumentBuffer args;
args.append(processObject);
args.append(JSC::jsNumber(STDIN_FILENO));
args.append(jsBoolean(bun_stdio_tty[STDIN_FILENO]));
BunProcessStdinFdType fdType = Bun__Process__getStdinFdType(Bun::vm(vm), STDIN_FILENO);
args.append(jsNumber(static_cast<int32_t>(fdType)));
JSC::CallData callData = JSC::getCallData(getStdioWriteStream);
JSC::CallData callData = JSC::getCallData(getStdinStream);
auto result = JSC::profiledCall(globalObject, ProfilingReason::API, getStdioWriteStream, callData, globalObject, args);
RETURN_IF_EXCEPTION(scope, {});
auto result = JSC::profiledCall(globalObject, ProfilingReason::API, getStdinStream, callData, globalObject, args);
if (auto* exception = scope.exception()) {
Zig::GlobalObject::reportUncaughtExceptionAtEventLoop(globalObject, exception);
scope.clearException();
return jsUndefined();
}
return result;
}

View File

@@ -30,8 +30,13 @@ const enum BunProcessStdinFdType {
socket = 2,
}
export function getStdioWriteStream(fd, isTTY: boolean, _fdType: BunProcessStdinFdType) {
$assert(typeof fd === "number", `Expected fd to be a number, got ${typeof fd}`);
export function getStdioWriteStream(
process: typeof globalThis.process,
fd: number,
isTTY: boolean,
_fdType: BunProcessStdinFdType,
) {
$assert(fd === 1 || fd === 2, `Expected fd to be 1 or 2, got ${fd}`);
let stream;
if (isTTY) {
@@ -74,9 +79,14 @@ export function getStdioWriteStream(fd, isTTY: boolean, _fdType: BunProcessStdin
return [stream, underlyingSink];
}
export function getStdinStream(fd, isTTY: boolean, fdType: BunProcessStdinFdType) {
export function getStdinStream(
process: typeof globalThis.process,
fd: number,
isTTY: boolean,
fdType: BunProcessStdinFdType,
) {
$assert(fd === 0);
const native = Bun.stdin.stream();
// @ts-expect-error
const source = native.$bunNativePtr;
var reader: ReadableStreamDefaultReader<Uint8Array> | undefined;
@@ -246,7 +256,12 @@ export function getStdinStream(fd, isTTY: boolean, fdType: BunProcessStdinFdType
return stream;
}
export function initializeNextTickQueue(process, nextTickQueue, drainMicrotasksFn, reportUncaughtExceptionFn) {
export function initializeNextTickQueue(
process: typeof globalThis.process,
nextTickQueue,
drainMicrotasksFn,
reportUncaughtExceptionFn,
) {
var queue;
var process;
var nextTickQueue = nextTickQueue;

View File

@@ -1114,3 +1114,20 @@ it("should handle user assigned `default` properties", async () => {
await promise;
});
it.each(["stdin", "stdout", "stderr"])("%s stream accessor should handle exceptions without crashing", stream => {
expect([
/* js */ `
const old = process;
process = null;
try {
old.${stream};
} catch {}
if (typeof old.${stream} !== "undefined") {
console.log("wrong");
}
`,
"",
1,
]).toRunInlineFixture();
});