mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-24 06:14:01 +08:00
Do not merge: CI check of the exec lowering changes
This commit is contained in:
+4
-4
@@ -2971,10 +2971,10 @@ declare module "child_process" {
|
||||
args?: string[] | null,
|
||||
options?: ExecSyncOptions,
|
||||
): unknown;
|
||||
/* The async shell form — declared surface without full lowering (the
|
||||
* type-level tolerance story): harness code on paths tests don't reach
|
||||
* must typecheck; a reached call fences at its site. util.promisify
|
||||
* recognizes it statically, as it does execFile. */
|
||||
/* The async shell form. The callback forms lower like execFile's, with
|
||||
* the command line run by /bin/sh -c, and util.promisify recognizes it
|
||||
* statically, as it does execFile; reached calls without a callback
|
||||
* fence at their site. */
|
||||
export function exec(
|
||||
command: string,
|
||||
options?: ExecSyncOptions | ((error: Error | null, stdout: string, stderr: string) => void),
|
||||
|
||||
@@ -18,12 +18,13 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
const argv = host.emitExpr(e.args[1]!);
|
||||
const cb = host.emitExpr(callbackArg);
|
||||
host.moveTemp(cb);
|
||||
const shell = host.emitExpr(e.args[3]!);
|
||||
const adapter = host.execFileThunkFor(cbT);
|
||||
host.usesTimers = true;
|
||||
host.declare(`declare ptr @scr_exec_file(ptr, ptr, ptr, ptr)`);
|
||||
host.declare(`declare ptr @scr_exec_file(ptr, ptr, ptr, ptr, i1)`);
|
||||
const out = B.tmp();
|
||||
B.line(
|
||||
`${out} = call ptr @scr_exec_file(ptr ${cmd.name}, ptr ${argv.name}, ptr ${cb.name}, ptr @${adapter})`,
|
||||
`${out} = call ptr @scr_exec_file(ptr ${cmd.name}, ptr ${argv.name}, ptr ${cb.name}, ptr @${adapter}, i1 ${shell.name})`,
|
||||
);
|
||||
return host.own({ name: out, type: e.type });
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ export function lowerBuiltinCall(
|
||||
loc: SrcLoc,
|
||||
namespaceMember?: ts.MemberName,
|
||||
): IrExpr {
|
||||
if (bi.module === "child_process" && bi.member === "execFile") {
|
||||
return lowerer.lowerExecFileCall(expr, loc);
|
||||
if (bi.module === "child_process" && (bi.member === "execFile" || bi.member === "exec")) {
|
||||
return lowerer.lowerExecFileCall(expr, loc, bi.member === "exec");
|
||||
}
|
||||
if (bi.module === "events" && bi.member === "addAbortListener") {
|
||||
return lowerAddAbortListener(lowerer, expr, loc);
|
||||
|
||||
@@ -725,35 +725,57 @@ export function lowerForkCall(lowerer: Lowerer, expr: ts.CallExpression, loc: Sr
|
||||
}
|
||||
|
||||
/** `execFile(file[, args][, options], callback)` — the asynchronous callback
|
||||
* slice. The child starts immediately with stdin/stdout/stderr piped; the
|
||||
* runtime captures both outputs and invokes the error-first callback after
|
||||
* settlement. The callback may ignore a suffix of `(error, stdout,
|
||||
* slice. With `shell`, `exec(command[, options], callback)`: the command
|
||||
* line runs with /bin/sh -c, as execSync runs it, and failure messages show
|
||||
* the command line as Node's do. The child starts immediately with
|
||||
* stdin/stdout/stderr piped; the runtime captures both outputs and invokes
|
||||
* the error-first callback after settlement. The callback may ignore a suffix of `(error, stdout,
|
||||
* stderr)`, but every declared parameter must have the Node shape. Inline
|
||||
* `encoding: "utf8"` and a pure numeric `maxBuffer` are accepted; the latter
|
||||
* is not enforced by the growing native capture, as
|
||||
* with the synchronous and promisified capture forms. Other options remain
|
||||
* fenced until their lifecycle can share this asynchronous core. */
|
||||
export function lowerExecFileCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr {
|
||||
export function lowerExecFileCall(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.CallExpression,
|
||||
loc: SrcLoc,
|
||||
shell = false,
|
||||
): IrExpr {
|
||||
const what = shell ? "exec" : "execFile";
|
||||
if (
|
||||
expr.arguments.some(ts.isSpreadElement) ||
|
||||
expr.arguments.length < 2 ||
|
||||
expr.arguments.length > 4
|
||||
expr.arguments.length > (shell ? 3 : 4)
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
`execFile with ${expr.arguments.length} arguments`,
|
||||
`${what} with ${expr.arguments.length} arguments`,
|
||||
expr,
|
||||
"the supported callback forms are execFile(file, callback), execFile(file, args, callback), and execFile(file, args, { encoding: 'utf8', maxBuffer: N }, callback)",
|
||||
shell
|
||||
? "the supported callback forms are exec(command, callback) and exec(command, { encoding: 'utf8', maxBuffer: N }, callback)"
|
||||
: "the supported callback forms are execFile(file, callback), execFile(file, args, callback), and execFile(file, args, { encoding: 'utf8', maxBuffer: N }, callback)",
|
||||
);
|
||||
}
|
||||
const cmd = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
|
||||
const argsNode = expr.arguments.length >= 3 ? expr.arguments[1] : undefined;
|
||||
const first = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
|
||||
const callbackNode = expr.arguments[expr.arguments.length - 1]!;
|
||||
const argv = lowerChildArgsArg(lowerer, argsNode, loc);
|
||||
if (expr.arguments.length === 4) {
|
||||
const options = expr.arguments[2]!;
|
||||
// exec: /bin/sh -c <command>, execSync's lowering (the runtime reads the
|
||||
// command line back from argv[1] for failure messages); execFile: the
|
||||
// file and its args list.
|
||||
const cmd: IrExpr = shell ? { kind: "strLit", value: "/bin/sh", type: STRING, loc } : first;
|
||||
const argv: IrExpr = shell
|
||||
? {
|
||||
kind: "arrayLit",
|
||||
elems: [{ kind: "strLit", value: "-c", type: STRING, loc }, first],
|
||||
type: arrayOf(STRING),
|
||||
loc,
|
||||
}
|
||||
: lowerChildArgsArg(lowerer, expr.arguments.length >= 3 ? expr.arguments[1] : undefined, loc);
|
||||
const optionsNode =
|
||||
expr.arguments.length === (shell ? 3 : 4) ? expr.arguments[shell ? 1 : 2] : undefined;
|
||||
if (optionsNode) {
|
||||
const options = optionsNode;
|
||||
if (!ts.isObjectLiteralExpression(options)) {
|
||||
lowerer.noLowering(
|
||||
"execFile with a non-literal options argument",
|
||||
`${what} with a non-literal options argument`,
|
||||
options,
|
||||
"pass encoding and maxBuffer in an inline object literal",
|
||||
);
|
||||
@@ -774,7 +796,7 @@ export function lowerExecFileCall(lowerer: Lowerer, expr: ts.CallExpression, loc
|
||||
const member = optionMember(property);
|
||||
if (!member)
|
||||
lowerer.noLowering(
|
||||
"execFile with this options shape",
|
||||
`${what} with this options shape`,
|
||||
property,
|
||||
"use plain encoding and maxBuffer properties without spreads or computed keys",
|
||||
);
|
||||
@@ -784,7 +806,7 @@ export function lowerExecFileCall(lowerer: Lowerer, expr: ts.CallExpression, loc
|
||||
(member.value.text !== "utf8" && member.value.text !== "utf-8")
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
"execFile with a non-literal utf8 encoding",
|
||||
`${what} with a non-literal utf8 encoding`,
|
||||
member.value,
|
||||
"pass the literal 'utf8' or 'utf-8' (other expressions might have side effects)",
|
||||
);
|
||||
@@ -792,14 +814,14 @@ export function lowerExecFileCall(lowerer: Lowerer, expr: ts.CallExpression, loc
|
||||
} else if (member.name === "maxBuffer") {
|
||||
if (!pureNumber(member.value)) {
|
||||
lowerer.noLowering(
|
||||
"execFile with a non-literal maxBuffer",
|
||||
`${what} with a non-literal maxBuffer`,
|
||||
member.value,
|
||||
"pass a numeric literal or literal arithmetic (the native capture grows without enforcing this limit)",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
lowerer.noLowering(
|
||||
`execFile option '${member.name}'`,
|
||||
`${what} option '${member.name}'`,
|
||||
property,
|
||||
"only encoding: 'utf8' and a pure numeric maxBuffer are supported in the callback form",
|
||||
);
|
||||
@@ -815,14 +837,14 @@ export function lowerExecFileCall(lowerer: Lowerer, expr: ts.CallExpression, loc
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
callbackNode,
|
||||
"execFile callbacks take (error), (error, stdout), (error, stdout, stderr), or no parameters",
|
||||
`${what} callbacks take (error), (error, stdout), (error, stdout, stderr), or no parameters`,
|
||||
);
|
||||
}
|
||||
if (callback.type.ret.kind !== "void") {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
callbackNode,
|
||||
"execFile callbacks returning a value (make the callback body a block, or return nothing)",
|
||||
`${what} callbacks returning a value (make the callback body a block, or return nothing)`,
|
||||
);
|
||||
}
|
||||
const errorParam = callback.type.params[0];
|
||||
@@ -837,7 +859,7 @@ export function lowerExecFileCall(lowerer: Lowerer, expr: ts.CallExpression, loc
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
callbackNode,
|
||||
`execFile callbacks whose first parameter is not 'Error | null' (got '${lowerer.fmt(errorParam)}')`,
|
||||
`${what} callbacks whose first parameter is not 'Error | null' (got '${lowerer.fmt(errorParam)}')`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -846,9 +868,15 @@ export function lowerExecFileCall(lowerer: Lowerer, expr: ts.CallExpression, loc
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
callbackNode,
|
||||
`execFile callbacks whose ${i === 1 ? "stdout" : "stderr"} parameter is not 'string'`,
|
||||
`${what} callbacks whose ${i === 1 ? "stdout" : "stderr"} parameter is not 'string'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return { kind: "libCall", fn: "cp.execFile", args: [cmd, argv, callback], type: CHILD_T, loc };
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "cp.execFile",
|
||||
args: [cmd, argv, callback, boolLit(shell, loc)],
|
||||
type: CHILD_T,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11485,8 +11485,8 @@ export class Lowerer {
|
||||
return lowerSpawnCall(this, expr, loc);
|
||||
}
|
||||
|
||||
lowerExecFileCall(expr: ts.CallExpression, loc: SrcLoc): IrExpr {
|
||||
return lowerExecFileCall(this, expr, loc);
|
||||
lowerExecFileCall(expr: ts.CallExpression, loc: SrcLoc, shell = false): IrExpr {
|
||||
return lowerExecFileCall(this, expr, loc, shell);
|
||||
}
|
||||
|
||||
lowerExecSyncCall(expr: ts.CallExpression, shell: boolean, loc: SrcLoc): IrExpr {
|
||||
|
||||
@@ -2222,8 +2222,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
/** child_process.execFile's callback slice: starts an all-piped child,
|
||||
* captures stdout/stderr, and moves the error-first callback into the
|
||||
* child registry. The callback shape is program-dependent and checked
|
||||
* by the validator/backends. */
|
||||
"cp.execFile": { argTypes: [STRING, arrayOf(STRING), null], result: CHILD_T },
|
||||
* by the validator/backends. The last argument is exec's shell flag:
|
||||
* the argv is ["-c", command] and failure messages show the command. */
|
||||
"cp.execFile": { argTypes: [STRING, arrayOf(STRING), null, BOOL], result: CHILD_T },
|
||||
// spawn's options form: per-slot stdio modes (0 ignore / 1 inherit /
|
||||
// 2 fd) with the out/err fds for mode 2, detached, env replacement
|
||||
// pairs, cwd ("" = inherit).
|
||||
|
||||
@@ -951,6 +951,7 @@ test("library callbacks retain child, specialized, and generic result diagnostic
|
||||
{ kind: "strLit", value: "tool", type: STRING, loc },
|
||||
{ kind: "arrayLit", elems: [], type: arrayOf(STRING), loc },
|
||||
{ kind: "boolLit", value: true, type: F64, loc },
|
||||
{ kind: "boolLit", value: false, type: BOOL, loc },
|
||||
],
|
||||
};
|
||||
expect(validateModule(expressionModule(expr, [])).map((error) => error.message)).toEqual([
|
||||
|
||||
@@ -2265,14 +2265,14 @@ static void scr_exec_file_fire(ScrChild *c) {
|
||||
}
|
||||
|
||||
ScrChild *scr_exec_file(ScrStr *cmd, ScrArr *args, ScrClosure *cb,
|
||||
ScrExecFileFn fn) {
|
||||
ScrExecFileFn fn, bool shell) {
|
||||
ScrStr *empty = scr_str_new("", 0);
|
||||
ScrChild *c = scr_spawn_opts(cmd, args, 3, 3, 3, 0, 0, false, false, false,
|
||||
NULL, empty);
|
||||
scr_str_release(empty);
|
||||
c->exec_cb = cb;
|
||||
c->exec_fn = fn;
|
||||
char *display = scr_exec_display(cmd, args, false);
|
||||
char *display = scr_exec_display(cmd, args, shell);
|
||||
c->exec_display = scr_str_new(display, strlen(display));
|
||||
free(display);
|
||||
scr_child_stream_capture_begin(c->out_stream);
|
||||
@@ -4905,14 +4905,14 @@ static void scr_exec_file_fire(ScrChild *c) {
|
||||
}
|
||||
|
||||
ScrChild *scr_exec_file(ScrStr *cmd, ScrArr *args, ScrClosure *cb,
|
||||
ScrExecFileFn fn) {
|
||||
ScrExecFileFn fn, bool shell) {
|
||||
ScrStr *empty = scr_str_new("", 0);
|
||||
ScrChild *c = scr_spawn_opts(cmd, args, 3, 3, 3, 0, 0, false, false, false,
|
||||
NULL, empty);
|
||||
scr_str_release(empty);
|
||||
c->exec_cb = cb;
|
||||
c->exec_fn = fn;
|
||||
char *display = scr_exec_display(cmd, args, false);
|
||||
char *display = scr_exec_display(cmd, args, shell);
|
||||
c->exec_display = scr_str_new(display, strlen(display));
|
||||
free(display);
|
||||
scr_child_stream_capture_begin(c->out_stream);
|
||||
|
||||
@@ -155,9 +155,11 @@ ScrChild *scr_spawn_dynamic(ScrStr *cmd, ScrArr *args, const ScrDyn *options);
|
||||
void scr_child_on_spawn(ScrChild *child, ScrClosure *cb); /* moves zero-argument callback */
|
||||
/* The callback slice: file + args, default options. The returned child has
|
||||
* all three stdio slots piped; stdout/stderr are captured internally and
|
||||
* the callback moves into the child registry until settlement. */
|
||||
* the callback moves into the child registry until settlement. With shell
|
||||
* (exec), args is ["-c", command] for /bin/sh and failure messages show
|
||||
* the command, as for scr_exec_sync. */
|
||||
ScrChild *scr_exec_file(ScrStr *cmd, ScrArr *args, ScrClosure *cb /*moves*/,
|
||||
ScrExecFileFn fn);
|
||||
ScrExecFileFn fn, bool shell);
|
||||
ScrChild *scr_child_retain(ScrChild *c);
|
||||
void scr_child_release(ScrChild *c);
|
||||
void *scr_child_retain_v(void *p);
|
||||
|
||||
Reference in New Issue
Block a user