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
25 changes: 25 additions & 0 deletions packages/compiler/src/frontend/lowering/builtins/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,28 @@ const ASSERTION_ERROR_PROPERTIES: ReadonlySet<string> = new Set([
* declaration is not stdlib). Reads only: writes keep their fence (no
* compiled program constructs an errno error). Null for non-error
* receivers and non-stdlib members, so the chain keeps trying. */
/** True for child_process.ExecException, exec's callback error. Its
* `code` is the child's numeric exit status (null after a signal), which
* the runtime error's string code slot does not hold, so reads, `in` tests
* and typeof tests of `code` refuse instead of answering from the slot. */
export function isExecExceptionType(lowerer: Lowerer, t: ts.Type): boolean {
const sym = t.getSymbol();
return (
sym?.name === "ExecException" &&
lowerer.checker
.declarationsOf(sym)
.some((d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()))
);
}

export function refuseExecExceptionCode(lowerer: Lowerer, node: ts.Node): never {
return lowerer.noLowering(
"ExecException.code",
node,
"spawn reports the exit status through its 'close' event",
);
}

export function lowerErrorCodeProperty(
lowerer: Lowerer,
expr: ts.PropertyAccessExpression,
Expand Down Expand Up @@ -122,6 +144,9 @@ export function lowerErrorCodeProperty(
!(recvT.className === "%Error" && lowerer.typeOf(expr.expression).isIntersectionType())
)
return null;
if (expr.name.text === "code" && isExecExceptionType(lowerer, lowerer.typeOf(expr.expression))) {
refuseExecExceptionCode(lowerer, expr);
}
const rawReceiver = lowerer.lowerExpr(expr.expression);
const receiver =
rawReceiver.type.kind === "dyn"
Expand Down
5 changes: 5 additions & 0 deletions packages/compiler/src/frontend/lowering/lower-exprs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { lowerWorkerMetadata } from "./builtins/workers.js";
import { lowerReadlineProperty } from "./builtins/readline.js";
import { lowerBlobInstanceof } from "./builtins/blob.js";
import { lowerKeyObjectInstanceof } from "./builtins/key-object.js";
import { isExecExceptionType, refuseExecExceptionCode } from "./builtins/errors.js";
import { STATS_TYPE_TESTS } from "./builtins/filesystem.js";
import { threadsImportOf } from "../threads-import.js";
import { experimentalImportOf, lifetimeRegionMemberOf } from "../experimental-import.js";
Expand Down Expand Up @@ -11118,6 +11119,7 @@ function lowerErrorCodeTypeofTest(
recv = recv.expression;
const errT = errorRootedObjectOf(lowerer, lowerer.typeOf(recv));
if (!errT) continue;
if (isExecExceptionType(lowerer, lowerer.typeOf(recv))) refuseExecExceptionCode(lowerer, prop);
const caught = lowerer.caughtLocalOf(recv);
let receiver: IrExpr;
if (caught) {
Expand Down Expand Up @@ -12714,6 +12716,9 @@ function lowerInExpression(lowerer: Lowerer, expr: ts.BinaryExpression, loc: Src
loc,
};
}
if (key === "code" && isExecExceptionType(lowerer, lowerer.typeOf(expr.right))) {
refuseExecExceptionCode(lowerer, expr);
}
let recv = lowerer.lowerExpr(expr.right);
const classInfo =
recv.type.kind === "classval" ? lowerer.classes.get(recv.type.className) : undefined;
Expand Down
17 changes: 17 additions & 0 deletions packages/compiler/src/frontend/type-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,23 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
) {
return { kind: "object", className: "%Error" };
}
// child_process.ExecException, exec's callback error, is an interface
// extending Error with optional metadata members; the value is one
// ordinary Error as well.
const execException = type.getSymbol();
if (
execException?.name === "ExecException" &&
checker
.declarationsOf(execException)
.some(
(decl) =>
ts.isInterfaceDeclaration(decl) &&
ctx.isStdlibFile(decl.getSourceFile()) &&
isDeclaredInAmbientModule(decl, "child_process"),
)
) {
return { kind: "object", className: "%Error" };
}
if (isOsConstantsType(type, ctx) || isLegacyUrlType(type, ctx)) return DYN;
// Immutable<T> (@scriptc/types) is a deep-readonly view: the value is
// still the T it maps.
Expand Down
1 change: 1 addition & 0 deletions tests/harness/self-hosting-native-driver.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// CI-only: run the native bootstrap lane for this check.
import { verifyBootstrapSeed } from "../../scripts/bootstrap-seed.mjs";
import { execFile, spawnSync } from "node:child_process";
import {
Expand Down
Loading