@poe-platform/safe-bash 0.1.748 → 0.1.750
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/safe-bash/chunks/{chunk-MSEZFB2F.js → chunk-U7DRVFRD.js} +261 -49
- package/dist/safe-bash/chunks/{src-2MBSXQOA.js → src-YKSQQDD3.js} +2 -2
- package/dist/safe-bash/commands/basic.js +2 -2
- package/dist/safe-bash/core.browser.js +126 -364
- package/dist/safe-bash/shell/conditional.js +18 -4
- package/dist/safe-bash/shell/runtime.d.ts +0 -7
- package/dist/safe-bash/shell/runtime.js +141 -526
- package/dist/safe-bash-command-ssconvert/index.js +255 -48
- package/package.json +2 -2
|
@@ -9405,7 +9405,7 @@ export class Runtime {
|
|
|
9405
9405
|
}
|
|
9406
9406
|
}
|
|
9407
9407
|
if (command.kind === "conditional") {
|
|
9408
|
-
if (rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(command.expression, 0, rawState))
|
|
9408
|
+
if (rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(command.expression, 0, rawState, true))
|
|
9409
9409
|
return false;
|
|
9410
9410
|
const checkCondNoFile = (e) => {
|
|
9411
9411
|
if (e.kind === "nonempty")
|
|
@@ -9439,7 +9439,8 @@ export class Runtime {
|
|
|
9439
9439
|
}
|
|
9440
9440
|
return !rem.associative && rem.references === 1;
|
|
9441
9441
|
}
|
|
9442
|
-
|
|
9442
|
+
// Unquoted patterns may need the asynchronous matcher after values change.
|
|
9443
|
+
return this.isPureSyncValueWord(e.left, rawState) && this.isPureSyncValueWord(e.right, rawState) && (!(e.operator === "==" || e.operator === "=" || e.operator === "!=") || (e.right.parts.length > 0 && e.right.parts.every(p => p.quoted)));
|
|
9443
9444
|
}
|
|
9444
9445
|
if (e.kind === "not")
|
|
9445
9446
|
return checkCondNoFile(e.operand);
|
|
@@ -9775,9 +9776,15 @@ export class Runtime {
|
|
|
9775
9776
|
}
|
|
9776
9777
|
return false;
|
|
9777
9778
|
}
|
|
9778
|
-
canSyncConditional(expr, depth = 0, rawState) {
|
|
9779
|
+
canSyncConditional(expr, depth = 0, rawState, requireTotal = false) {
|
|
9779
9780
|
if (depth > 8)
|
|
9780
9781
|
return false;
|
|
9782
|
+
// Loops and pure substitutions cannot replay after an unsupported result.
|
|
9783
|
+
// These operators depend on arithmetic, filesystem or collation state that
|
|
9784
|
+
// the speculative synchronous evaluator does not fully support.
|
|
9785
|
+
if (requireTotal && ((expr.kind === "unary" && syncConditionalFileUnaryOps.has(expr.operator)) ||
|
|
9786
|
+
(expr.kind === "binary" && expr.operator !== "==" && expr.operator !== "=" && expr.operator !== "!=" && expr.operator !== "=~")))
|
|
9787
|
+
return false;
|
|
9781
9788
|
const okWord = (w) => !Runtime.wordMayMutate(w) || (rawState !== undefined && this.isPureSyncValueWord(w, rawState));
|
|
9782
9789
|
if (expr.kind === "nonempty")
|
|
9783
9790
|
return okWord(expr.operand);
|
|
@@ -9800,9 +9807,9 @@ export class Runtime {
|
|
|
9800
9807
|
return (expr.operator === "<" || expr.operator === ">" || expr.operator === "-eq" || expr.operator === "-ne" || expr.operator === "-lt" || expr.operator === "-le" || expr.operator === "-gt" || expr.operator === "-ge");
|
|
9801
9808
|
}
|
|
9802
9809
|
if (expr.kind === "not")
|
|
9803
|
-
return this.canSyncConditional(expr.operand, depth + 1, rawState);
|
|
9810
|
+
return this.canSyncConditional(expr.operand, depth + 1, rawState, requireTotal);
|
|
9804
9811
|
if (expr.kind === "and" || expr.kind === "or")
|
|
9805
|
-
return this.canSyncConditional(expr.left, depth + 1, rawState) && this.canSyncConditional(expr.right, depth + 1, rawState);
|
|
9812
|
+
return this.canSyncConditional(expr.left, depth + 1, rawState, requireTotal) && this.canSyncConditional(expr.right, depth + 1, rawState, requireTotal);
|
|
9806
9813
|
return false;
|
|
9807
9814
|
}
|
|
9808
9815
|
tryEvalFastMemoryFileUnary(op, rawVal, rawState) {
|
|
@@ -13069,6 +13076,7 @@ export class Runtime {
|
|
|
13069
13076
|
positionalSetVersion,
|
|
13070
13077
|
savedPositionals: resumeSavedPos,
|
|
13071
13078
|
callerLoopDepth,
|
|
13079
|
+
lastArgument: fnArgs.at(-1) ?? w0Plain,
|
|
13072
13080
|
listIndex: bodyRes.listIndex,
|
|
13073
13081
|
pipelineIndex: bodyRes.pipelineIndex,
|
|
13074
13082
|
});
|
|
@@ -13133,6 +13141,7 @@ export class Runtime {
|
|
|
13133
13141
|
this.setSyncPipeStatusCell(activeExisting, statusStr);
|
|
13134
13142
|
activeStore.changed(monitor.chargeInternal(syncPipeStatusCharge, syncPipeStatusTickets), "PIPESTATUS");
|
|
13135
13143
|
}
|
|
13144
|
+
rawState.lastArgument = fnArgs.at(-1) ?? w0Plain;
|
|
13136
13145
|
rawState.status = exitStatus;
|
|
13137
13146
|
if (exitStatus !== 0 && !ignored && rawState.errexit)
|
|
13138
13147
|
throw new Flow("exit", exitStatus);
|
|
@@ -13730,25 +13739,12 @@ export class Runtime {
|
|
|
13730
13739
|
if (list.terminator || list.pipelines.length !== 1)
|
|
13731
13740
|
return false;
|
|
13732
13741
|
const p = list.pipelines[0];
|
|
13733
|
-
|
|
13742
|
+
// Pipeline buffers can overflow after variable or stage expansion.
|
|
13743
|
+
// A synchronous loop cannot resume after earlier body effects, so keep
|
|
13744
|
+
// bounded pipelines on the normal substitution execution path.
|
|
13745
|
+
if (p.negate || p.commands.length !== 1)
|
|
13734
13746
|
return false;
|
|
13735
13747
|
let cmd = p.commands[0];
|
|
13736
|
-
const hasSingleStdinRedir = cmd.kind === "simple" &&
|
|
13737
|
-
cmd.redirects.length === 1 &&
|
|
13738
|
-
cmd.redirects[0].operator === "<" &&
|
|
13739
|
-
(cmd.redirects[0].descriptor === undefined || cmd.redirects[0].descriptor === 0) &&
|
|
13740
|
-
!cmd.redirects[0].move &&
|
|
13741
|
-
!cmd.redirects[0].document &&
|
|
13742
|
-
cmd.redirects[0].target.plain !== undefined &&
|
|
13743
|
-
!cmd.redirects[0].target.plain.startsWith("-") &&
|
|
13744
|
-
cmd.redirects[0].target.plain !== "/dev/stdin";
|
|
13745
|
-
const hasSingleHereStringRedir = cmd.kind === "simple" &&
|
|
13746
|
-
cmd.redirects.length === 1 &&
|
|
13747
|
-
cmd.redirects[0].operator === "<<<" &&
|
|
13748
|
-
(cmd.redirects[0].descriptor === undefined || cmd.redirects[0].descriptor === 0) &&
|
|
13749
|
-
!cmd.redirects[0].move &&
|
|
13750
|
-
!cmd.redirects[0].document &&
|
|
13751
|
-
this.isPureSyncValueWord(cmd.redirects[0].target, rawState);
|
|
13752
13748
|
// Loop substitutions require a real subshell: header admission can
|
|
13753
13749
|
// decline at runtime, and induction writes also change export attributes.
|
|
13754
13750
|
if (cmd.kind === "for" || cmd.kind === "arithmetic-for" || cmd.kind === "while")
|
|
@@ -13756,7 +13752,7 @@ export class Runtime {
|
|
|
13756
13752
|
if (p.commands.length === 1 && (cmd.kind === "if" || cmd.kind === "case") && this.isPureSyncSubIfOrCase(cmd, rawState)) {
|
|
13757
13753
|
continue;
|
|
13758
13754
|
}
|
|
13759
|
-
if (cmd.kind !== "simple" ||
|
|
13755
|
+
if (cmd.kind !== "simple" || cmd.redirects.length > 0 || cmd.words.length === 0)
|
|
13760
13756
|
return false;
|
|
13761
13757
|
let w0Plain = cmd.words[0].plain;
|
|
13762
13758
|
// Range-aware tr and Buffer-free 76-col base64 are handled below.
|
|
@@ -13771,10 +13767,10 @@ export class Runtime {
|
|
|
13771
13767
|
fnBody.body.lists[0].pipelines[0].commands.length === 1 &&
|
|
13772
13768
|
fnBody.body.lists[0].pipelines[0].commands[0].kind === "simple" &&
|
|
13773
13769
|
fnBody.body.lists[0].pipelines[0].commands[0].redirects.length === 0 &&
|
|
13774
|
-
cmd.words.slice(1).every(w => this.isPureSyncValueWord(w, rawState))) {
|
|
13770
|
+
cmd.words.slice(1).every(w => this.isPureSyncValueWord(w, rawState) && w.parts.length > 0 && w.parts.every(part => part.quoted || (part.kind === "text" && part.value.length > 0 && !part.value.includes(" ") && !part.value.includes("\t") && !part.value.includes("\n") && !part.value.includes("{") && !hasGlobOrEscape(part.value, true))))) {
|
|
13775
13771
|
const innerCmd = fnBody.body.lists[0].pipelines[0].commands[0];
|
|
13776
13772
|
const innerW0 = innerCmd.words[0]?.plain;
|
|
13777
|
-
if (innerW0 && !rawState.functions.has(innerW0) && !rawState.extensions?.builtins.has(innerW0)) {
|
|
13773
|
+
if (innerW0 && (innerW0 === "echo" || innerW0 === "printf" || innerW0 === "dirname" || innerW0 === "basename" || innerW0 === "pwd" || innerW0 === "command" || innerW0 === "type" || innerW0 === "seq") && !rawState.functions.has(innerW0) && !rawState.extensions?.builtins.has(innerW0)) {
|
|
13778
13774
|
cmd = innerCmd;
|
|
13779
13775
|
w0Plain = innerW0;
|
|
13780
13776
|
}
|
|
@@ -13790,143 +13786,23 @@ export class Runtime {
|
|
|
13790
13786
|
return false;
|
|
13791
13787
|
}
|
|
13792
13788
|
if ((w0Plain === "command" && cmd.words[1]?.plain === "-v") || (w0Plain === "type" && cmd.words[1]?.plain === "-t")) {
|
|
13793
|
-
|
|
13794
|
-
cmd.words.length === 3 &&
|
|
13795
|
-
!rawState.functions.has(w0Plain) &&
|
|
13796
|
-
!rawState.extensions?.builtins.has(w0Plain) &&
|
|
13797
|
-
this.isPureSyncValueWord(cmd.words[2], rawState) &&
|
|
13798
|
-
((cmd.words[2].plain !== undefined && this.tryResolveSyncDiscovery(w0Plain === "command" ? "name" : "kind", cmd.words[2].plain, rawState) !== undefined) ||
|
|
13799
|
-
(this._isMemoryBackingFs && this.backingFs?.capabilities?.permissions !== true))) {
|
|
13800
|
-
continue;
|
|
13801
|
-
}
|
|
13789
|
+
// PATH entries can change after admission, making discovery decline.
|
|
13802
13790
|
return false;
|
|
13803
13791
|
}
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
w0Plain === "awk" ||
|
|
13809
|
-
w0Plain === "grep" ||
|
|
13810
|
-
w0Plain === "sed" ||
|
|
13811
|
-
w0Plain === "cut" ||
|
|
13812
|
-
w0Plain === "wc" ||
|
|
13813
|
-
w0Plain === "sort" ||
|
|
13814
|
-
w0Plain === "uniq" ||
|
|
13815
|
-
w0Plain === "tr" ||
|
|
13816
|
-
(hasSingleHereStringRedir && (w0Plain === "base64" || w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl" || w0Plain === "paste" || w0Plain === "numfmt"));
|
|
13817
|
-
const def = w0Plain ? (this.commands.get(w0Plain) ?? ((w0Plain === "rev" || w0Plain === "tac" || w0Plain === "nl" || w0Plain === "paste" || w0Plain === "jq") ? this.getExternalCommand(w0Plain) : undefined)) : undefined;
|
|
13818
|
-
if (!w0Plain || (w0Plain !== "printf" && w0Plain !== "echo" && w0Plain !== "dirname" && w0Plain !== "basename" && w0Plain !== "seq" && !isFileCommand) || !def || rawState.functions.has(w0Plain) || rawState.extensions?.builtins.has(w0Plain))
|
|
13819
|
-
return false;
|
|
13820
|
-
if ((hasSingleStdinRedir || hasSingleHereStringRedir) && !isFileCommand)
|
|
13792
|
+
// File and here-string evaluators can decline on mutable input. Keep
|
|
13793
|
+
// these on the normal path, which can handle arbitrary input sizes.
|
|
13794
|
+
const def = w0Plain ? this.commands.get(w0Plain) : undefined;
|
|
13795
|
+
if (!w0Plain || (w0Plain !== "printf" && w0Plain !== "echo" && w0Plain !== "dirname" && w0Plain !== "basename" && w0Plain !== "seq") || !def || rawState.functions.has(w0Plain) || rawState.extensions?.builtins.has(w0Plain))
|
|
13821
13796
|
return false;
|
|
13822
|
-
if (w0Plain === "printf" ? def.execute !== printfCommand.execute : w0Plain === "echo" ? !defaultEchoExecutors.has(def.execute) :
|
|
13797
|
+
if (w0Plain === "printf" ? def.execute !== printfCommand.execute : w0Plain === "echo" ? !defaultEchoExecutors.has(def.execute) : w0Plain === "seq" ? (!builtInDirectContextExecutors.has(def.execute) && customRegisteredCommands.has(def.execute)) : !builtInDirectContextExecutors.has(def.execute))
|
|
13823
13798
|
return false;
|
|
13824
13799
|
if (!cmd.words.slice(1).every(w => this.isPureSyncValueWord(w, rawState)))
|
|
13825
13800
|
return false;
|
|
13826
|
-
|
|
13827
|
-
|
|
13828
|
-
|
|
13829
|
-
|
|
13830
|
-
|
|
13831
|
-
if (!hasSingleHereStringRedir) {
|
|
13832
|
-
const fPlain = hasSingleStdinRedir ? cmd.redirects[0].target.plain : cmd.words[cmd.words.length - 1]?.plain;
|
|
13833
|
-
if (!fPlain || fPlain.startsWith("-") || fPlain === "/dev/stdin")
|
|
13834
|
-
return false;
|
|
13835
|
-
const vCheck = this.tryReadMemoryFileViewSync(resolvePath(rawState.cwd, fPlain), false);
|
|
13836
|
-
if (!vCheck || vCheck.byteLength > 16384 || vCheck.includes(0))
|
|
13837
|
-
return false;
|
|
13838
|
-
}
|
|
13839
|
-
const opWords = (hasSingleStdinRedir || hasSingleHereStringRedir) ? cmd.words.slice(1) : cmd.words.slice(1, -1);
|
|
13840
|
-
const opCount = opWords.length;
|
|
13841
|
-
const op0 = opWords[0]?.plain;
|
|
13842
|
-
const op1 = opWords[1]?.plain;
|
|
13843
|
-
if (w0Plain === "cat" || w0Plain === "rev" || w0Plain === "tac") {
|
|
13844
|
-
if (opCount !== 0)
|
|
13845
|
-
return false;
|
|
13846
|
-
}
|
|
13847
|
-
else {
|
|
13848
|
-
if (p.commands.length !== 1 && !hasSingleHereStringRedir)
|
|
13849
|
-
return false;
|
|
13850
|
-
if (w0Plain === "head" || w0Plain === "tail") {
|
|
13851
|
-
const validHT = opCount === 0 ||
|
|
13852
|
-
(opCount === 1 && op0 !== undefined && (/^-(?:n|c|--lines=|--bytes=)?[0-9]{1,5}$/.test(op0) || /^--(?:lines|bytes)=[0-9]{1,5}$/.test(op0) || (w0Plain === "tail" && /^(?:-n|--lines=)\+[0-9]{1,5}$/.test(op0)) || (w0Plain === "head" && /^(?:-n|--lines=)-[0-9]{1,5}$/.test(op0)))) ||
|
|
13853
|
-
(opCount === 2 && ((op0 === "-n" && op1 !== undefined && (/^[0-9]{1,5}$/.test(op1) || (w0Plain === "tail" && /^\+[0-9]{1,5}$/.test(op1)) || (w0Plain === "head" && /^-[0-9]{1,5}$/.test(op1)))) || (op0 === "-c" && op1 !== undefined && /^[0-9]{1,5}$/.test(op1))));
|
|
13854
|
-
if (!validHT)
|
|
13855
|
-
return false;
|
|
13856
|
-
}
|
|
13857
|
-
const plainOps = [];
|
|
13858
|
-
for (let oi = 0; oi < opCount; oi++) {
|
|
13859
|
-
const ow = opWords[oi];
|
|
13860
|
-
const pw = ow.plain ?? (ow.parts.length > 0 && ow.parts.every(pt => pt.kind === "text") ? ow.parts.map(pt => pt.value).join("") : undefined);
|
|
13861
|
-
if (pw === undefined)
|
|
13862
|
-
break;
|
|
13863
|
-
plainOps.push(pw);
|
|
13864
|
-
}
|
|
13865
|
-
if (w0Plain === "cut") {
|
|
13866
|
-
// Probe non-ASCII input: loop data can change after admission.
|
|
13867
|
-
if (plainOps.length !== opCount || this.evalSyncCut(["é"], plainOps, byteLocale(rawState.variables)) === undefined)
|
|
13868
|
-
return false;
|
|
13869
|
-
}
|
|
13870
|
-
else if (w0Plain === "sed") {
|
|
13871
|
-
if (plainOps.length !== opCount || this.evalSyncSed([], plainOps) === undefined)
|
|
13872
|
-
return false;
|
|
13873
|
-
}
|
|
13874
|
-
else if (w0Plain === "grep") {
|
|
13875
|
-
if (plainOps.length !== opCount || this.evalSyncGrep([], plainOps, Boolean(rawState.errexit)) === undefined)
|
|
13876
|
-
return false;
|
|
13877
|
-
}
|
|
13878
|
-
else if (w0Plain === "awk") {
|
|
13879
|
-
const input = this.extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState);
|
|
13880
|
-
if (plainOps.length !== opCount || input === undefined)
|
|
13881
|
-
return false;
|
|
13882
|
-
const lines = input.length === 0 ? [] : input.split("\n");
|
|
13883
|
-
if (input.endsWith("\n"))
|
|
13884
|
-
lines.pop();
|
|
13885
|
-
if (this.evalSyncAwk(lines, plainOps) === undefined)
|
|
13886
|
-
return false;
|
|
13887
|
-
}
|
|
13888
|
-
else if (w0Plain === "jq") {
|
|
13889
|
-
if (plainOps.length !== opCount)
|
|
13890
|
-
return false;
|
|
13891
|
-
const jqIn = this.extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState)?.trim();
|
|
13892
|
-
if (jqIn === undefined || this.evalSyncJq(jqIn, plainOps) === undefined)
|
|
13893
|
-
return false;
|
|
13894
|
-
}
|
|
13895
|
-
else if (w0Plain === "wc") {
|
|
13896
|
-
if (opCount !== 1 || this.normalizeSyncWcFlag(op0) === undefined)
|
|
13897
|
-
return false;
|
|
13898
|
-
}
|
|
13899
|
-
else if (w0Plain === "sort") {
|
|
13900
|
-
if (plainOps.length !== opCount || this.evalSyncSort([], plainOps, byteLocale(rawState.variables)) === undefined)
|
|
13901
|
-
return false;
|
|
13902
|
-
}
|
|
13903
|
-
else if (w0Plain === "uniq") {
|
|
13904
|
-
if (plainOps.length !== opCount || this.evalSyncUniq([], plainOps) === undefined)
|
|
13905
|
-
return false;
|
|
13906
|
-
}
|
|
13907
|
-
else if (w0Plain === "base64") {
|
|
13908
|
-
if (opCount > 1 || (opCount === 1 && op0 !== "-d" && op0 !== "--decode"))
|
|
13909
|
-
return false;
|
|
13910
|
-
}
|
|
13911
|
-
else if (w0Plain === "tr") {
|
|
13912
|
-
if (plainOps.length !== opCount || this.evalSyncTr("", plainOps) === undefined)
|
|
13913
|
-
return false;
|
|
13914
|
-
}
|
|
13915
|
-
else if (w0Plain === "nl") {
|
|
13916
|
-
if (plainOps.length !== opCount || this.evalSyncNl([], plainOps) === undefined)
|
|
13917
|
-
return false;
|
|
13918
|
-
}
|
|
13919
|
-
else if (w0Plain === "paste") {
|
|
13920
|
-
if (plainOps.length !== opCount || this.evalSyncPaste([], plainOps) === undefined)
|
|
13921
|
-
return false;
|
|
13922
|
-
}
|
|
13923
|
-
else if (w0Plain === "numfmt") {
|
|
13924
|
-
const nmCheck = this.extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState)?.trim();
|
|
13925
|
-
if (nmCheck === undefined || plainOps.length !== opCount || this.evalSyncNumfmt(nmCheck.split("\n"), plainOps) === undefined)
|
|
13926
|
-
return false;
|
|
13927
|
-
}
|
|
13928
|
-
}
|
|
13929
|
-
}
|
|
13801
|
+
// These substitutions consume one value per word. Unquoted expansions
|
|
13802
|
+
// can split, glob, or disappear, including after loop admission.
|
|
13803
|
+
if ((w0Plain === "echo" || w0Plain === "basename" || w0Plain === "dirname") &&
|
|
13804
|
+
!cmd.words.slice(1).every(w => w.parts.length > 0 && w.parts.every(part => part.quoted || (part.kind === "text" && part.value.length > 0 && !part.value.includes(" ") && !part.value.includes("\t") && !part.value.includes("\n") && !part.value.includes("{") && !hasGlobOrEscape(part.value, true)))))
|
|
13805
|
+
return false;
|
|
13930
13806
|
if (w0Plain === "printf") {
|
|
13931
13807
|
if (!this.isSyncPrintfCallOk(cmd, 1, rawState, this._activePrintfInductionName))
|
|
13932
13808
|
return false;
|
|
@@ -13966,138 +13842,18 @@ export class Runtime {
|
|
|
13966
13842
|
return false;
|
|
13967
13843
|
}
|
|
13968
13844
|
else if (w0Plain === "seq") {
|
|
13969
|
-
|
|
13970
|
-
|
|
13971
|
-
|
|
13972
|
-
if (w.parts.length === 1) {
|
|
13973
|
-
const p0 = w.parts[0];
|
|
13974
|
-
if (p0.kind === "arithmetic")
|
|
13975
|
-
return true;
|
|
13976
|
-
if (p0.kind === "variable" && !p0.indirect && !p0.prefixNames && !p0.substring && !p0.transform && p0.operator === undefined && getArraySelector(p0) === undefined) {
|
|
13977
|
-
const cur = rawState.variables[p0.name];
|
|
13978
|
-
return cur !== undefined && /^-?[0-9]{1,7}$/.test(cur);
|
|
13979
|
-
}
|
|
13980
|
-
}
|
|
13845
|
+
// Literal operands cannot change after admission. Arithmetic and
|
|
13846
|
+
// variables use the normal path, where seq can report invalid input.
|
|
13847
|
+
if (cmd.words.length < 2 || cmd.words.length > 4)
|
|
13981
13848
|
return false;
|
|
13982
|
-
|
|
13983
|
-
if (
|
|
13849
|
+
const operands = cmd.words.slice(1).map(w => w.plain);
|
|
13850
|
+
if (!operands.every(value => value !== undefined && Number.isInteger(Number(value)) && String(Number(value)) === value && Math.abs(Number(value)) <= 9999999))
|
|
13984
13851
|
return false;
|
|
13985
|
-
|
|
13986
|
-
|
|
13987
|
-
|
|
13852
|
+
const first = operands.length === 1 ? 1 : Number(operands[0]);
|
|
13853
|
+
const increment = operands.length === 3 ? Number(operands[1]) : 1;
|
|
13854
|
+
const last = Number(operands[operands.length - 1]);
|
|
13855
|
+
if (increment === 0 || Math.abs((last - first) / increment) > 1024)
|
|
13988
13856
|
return false;
|
|
13989
|
-
if (w0Plain === "printf") {
|
|
13990
|
-
if (!this.isSyncPrintfCallOk(cmd, 1, rawState, this._activePrintfInductionName))
|
|
13991
|
-
return false;
|
|
13992
|
-
}
|
|
13993
|
-
for (let sIdx = 1; sIdx < p.commands.length; sIdx++) {
|
|
13994
|
-
const sCmd = p.commands[sIdx];
|
|
13995
|
-
if (sCmd.kind !== "simple" || sCmd.redirects.length !== 0 || sCmd.words.length === 0)
|
|
13996
|
-
return false;
|
|
13997
|
-
const sName = sCmd.words[0].plain;
|
|
13998
|
-
// Range-aware tr and Buffer-free 76-col base64 are handled in pipeline stages.
|
|
13999
|
-
if (!sName || (sName !== "cut" && sName !== "tr" && sName !== "sort" && sName !== "head" && sName !== "tail" && sName !== "wc" && sName !== "sed" && sName !== "uniq" && sName !== "rev" && sName !== "awk" && sName !== "grep" && sName !== "jq" && sName !== "base64" && sName !== "tac" && sName !== "nl" && sName !== "paste" && sName !== "numfmt"))
|
|
14000
|
-
return false;
|
|
14001
|
-
const sDef = this.commands.get(sName) ?? ((sName === "rev" || sName === "tac" || sName === "nl" || sName === "paste" || sName === "jq") ? this.getExternalCommand(sName) : undefined);
|
|
14002
|
-
if (!sDef || customRegisteredCommands.has(sDef.execute) || (sName !== "rev" && sName !== "tac" && sName !== "nl" && sName !== "paste" && sName !== "jq" && !builtInDirectContextExecutors.has(sDef.execute)))
|
|
14003
|
-
return false;
|
|
14004
|
-
if (rawState.functions.has(sName) || rawState.extensions?.builtins.has(sName))
|
|
14005
|
-
return false;
|
|
14006
|
-
const sArgWords = sCmd.words.slice(1);
|
|
14007
|
-
const sPlainArgs = [];
|
|
14008
|
-
let allPlainStageArgs = true;
|
|
14009
|
-
for (let k = 0; k < sArgWords.length; k++) {
|
|
14010
|
-
const sw = sArgWords[k];
|
|
14011
|
-
const sp = sw.plain ?? (sw.parts.length > 0 && sw.parts.every(pt => pt.kind === "text") ? sw.parts.map(pt => pt.value).join("") : undefined);
|
|
14012
|
-
if (sp === undefined) {
|
|
14013
|
-
allPlainStageArgs = false;
|
|
14014
|
-
break;
|
|
14015
|
-
}
|
|
14016
|
-
sPlainArgs.push(sp);
|
|
14017
|
-
}
|
|
14018
|
-
if (!allPlainStageArgs)
|
|
14019
|
-
return false;
|
|
14020
|
-
if (sName === "sort") {
|
|
14021
|
-
if (this.evalSyncSort([], sPlainArgs, byteLocale(rawState.variables)) === undefined)
|
|
14022
|
-
return false;
|
|
14023
|
-
}
|
|
14024
|
-
else if (sName === "uniq") {
|
|
14025
|
-
if (this.evalSyncUniq([], sPlainArgs) === undefined)
|
|
14026
|
-
return false;
|
|
14027
|
-
}
|
|
14028
|
-
else if (sName === "wc") {
|
|
14029
|
-
if (sPlainArgs.length !== 1 || this.normalizeSyncWcFlag(sPlainArgs[0]) === undefined)
|
|
14030
|
-
return false;
|
|
14031
|
-
}
|
|
14032
|
-
else if (sName === "head" || sName === "tail") {
|
|
14033
|
-
const okHT = sPlainArgs.length === 0 || (sPlainArgs.length === 1 && (/^-(?:n|c)?[0-9]{1,5}$/.test(sPlainArgs[0]) || /^--(?:lines|bytes)=[0-9]{1,5}$/.test(sPlainArgs[0]) || (sName === "tail" && /^(?:-n|--lines=)\+[0-9]{1,5}$/.test(sPlainArgs[0])) || (sName === "head" && /^(?:-n|--lines=)-[0-9]{1,5}$/.test(sPlainArgs[0])))) || (sPlainArgs.length === 2 && ((sPlainArgs[0] === "-n" && (/^[0-9]{1,5}$/.test(sPlainArgs[1]) || (sName === "tail" && /^\+[0-9]{1,5}$/.test(sPlainArgs[1])) || (sName === "head" && /^-[0-9]{1,5}$/.test(sPlainArgs[1])))) || (sPlainArgs[0] === "-c" && /^[0-9]{1,5}$/.test(sPlainArgs[1]))));
|
|
14034
|
-
if (!okHT)
|
|
14035
|
-
return false;
|
|
14036
|
-
}
|
|
14037
|
-
else if (sName === "cut") {
|
|
14038
|
-
// Byte cuts may reject non-ASCII data on a later iteration.
|
|
14039
|
-
if (this.evalSyncCut(["é"], sPlainArgs, byteLocale(rawState.variables)) === undefined)
|
|
14040
|
-
return false;
|
|
14041
|
-
}
|
|
14042
|
-
else if (sName === "sed") {
|
|
14043
|
-
if (this.evalSyncSed([], sPlainArgs) === undefined)
|
|
14044
|
-
return false;
|
|
14045
|
-
}
|
|
14046
|
-
else if (sName === "awk") {
|
|
14047
|
-
if (sIdx !== 1)
|
|
14048
|
-
return false;
|
|
14049
|
-
const input = this.extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState);
|
|
14050
|
-
if (input === undefined)
|
|
14051
|
-
return false;
|
|
14052
|
-
const lines = input.length === 0 ? [] : input.split("\n");
|
|
14053
|
-
if (input.endsWith("\n"))
|
|
14054
|
-
lines.pop();
|
|
14055
|
-
if (this.evalSyncAwk(lines, sPlainArgs) === undefined)
|
|
14056
|
-
return false;
|
|
14057
|
-
}
|
|
14058
|
-
else if (sName === "grep") {
|
|
14059
|
-
if (this.evalSyncGrep([], sPlainArgs, Boolean(rawState.errexit)) === undefined)
|
|
14060
|
-
return false;
|
|
14061
|
-
}
|
|
14062
|
-
else if (sName === "tr") {
|
|
14063
|
-
if (this.evalSyncTr("", sPlainArgs) === undefined)
|
|
14064
|
-
return false;
|
|
14065
|
-
}
|
|
14066
|
-
else if (sName === "jq") {
|
|
14067
|
-
if (sIdx !== 1)
|
|
14068
|
-
return false;
|
|
14069
|
-
const jqIn = this.extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState)?.trim();
|
|
14070
|
-
if (jqIn === undefined || this.evalSyncJq(jqIn, sPlainArgs) === undefined)
|
|
14071
|
-
return false;
|
|
14072
|
-
}
|
|
14073
|
-
else if (sName === "base64") {
|
|
14074
|
-
if (sPlainArgs.length !== 0)
|
|
14075
|
-
return false;
|
|
14076
|
-
}
|
|
14077
|
-
else if (sName === "tac") {
|
|
14078
|
-
if (sPlainArgs.length !== 0 || (w0Plain !== "echo" && !hasSingleHereStringRedir))
|
|
14079
|
-
return false;
|
|
14080
|
-
}
|
|
14081
|
-
else if (sName === "nl") {
|
|
14082
|
-
if ((w0Plain !== "echo" && !hasSingleHereStringRedir) || this.evalSyncNl([], sPlainArgs) === undefined)
|
|
14083
|
-
return false;
|
|
14084
|
-
}
|
|
14085
|
-
else if (sName === "paste") {
|
|
14086
|
-
if (this.evalSyncPaste([], sPlainArgs) === undefined)
|
|
14087
|
-
return false;
|
|
14088
|
-
}
|
|
14089
|
-
else if (sName === "numfmt") {
|
|
14090
|
-
if (sIdx !== 1)
|
|
14091
|
-
return false;
|
|
14092
|
-
const nmCheck = this.extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState)?.trim();
|
|
14093
|
-
if (nmCheck === undefined || this.evalSyncNumfmt(nmCheck.split("\n"), sPlainArgs) === undefined)
|
|
14094
|
-
return false;
|
|
14095
|
-
}
|
|
14096
|
-
else if (sName === "rev") {
|
|
14097
|
-
if (sPlainArgs.length !== 0)
|
|
14098
|
-
return false;
|
|
14099
|
-
}
|
|
14100
|
-
}
|
|
14101
13857
|
}
|
|
14102
13858
|
continue;
|
|
14103
13859
|
}
|
|
@@ -14583,7 +14339,7 @@ export class Runtime {
|
|
|
14583
14339
|
return false;
|
|
14584
14340
|
}
|
|
14585
14341
|
else if (bc.kind === "conditional") {
|
|
14586
|
-
if (bc.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(bc.expression, 0, rawState))
|
|
14342
|
+
if (bc.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(bc.expression, 0, rawState, true))
|
|
14587
14343
|
return false;
|
|
14588
14344
|
if (bc.expression.kind === "binary" && bc.expression.operator === "=~")
|
|
14589
14345
|
return false;
|
|
@@ -14625,7 +14381,7 @@ export class Runtime {
|
|
|
14625
14381
|
return false;
|
|
14626
14382
|
const cmd = pipeline.commands[0];
|
|
14627
14383
|
const canSyncLoopCondCmd = (c) => {
|
|
14628
|
-
if (c.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(c.expression, 0, rawState))
|
|
14384
|
+
if (c.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(c.expression, 0, rawState, true))
|
|
14629
14385
|
return false;
|
|
14630
14386
|
const checkLeaf = (e) => {
|
|
14631
14387
|
if (e.kind === "nonempty")
|
|
@@ -14640,7 +14396,8 @@ export class Runtime {
|
|
|
14640
14396
|
return false;
|
|
14641
14397
|
return this.isPureSyncValueWord(e.left, rawState) && this.getFastAnchoredEreRegex(e.right, rawState) !== undefined && !rawState.readonlyVariables?.has("BASH_REMATCH") && !rawState.exported.has("BASH_REMATCH") && !stateMonitor(rawState)?.hasOverlay("BASH_REMATCH") && !(store ?? requireArrays(rawState))?.watches.has("BASH_REMATCH");
|
|
14642
14398
|
}
|
|
14643
|
-
|
|
14399
|
+
// A synchronous loop cannot hand off an unsupported pattern mid-iteration.
|
|
14400
|
+
return this.isPureSyncValueWord(e.left, rawState) && this.isPureSyncValueWord(e.right, rawState) && (!(e.operator === "==" || e.operator === "=" || e.operator === "!=") || (e.right.parts.length > 0 && e.right.parts.every(p => p.quoted)));
|
|
14644
14401
|
}
|
|
14645
14402
|
if (e.kind === "not")
|
|
14646
14403
|
return checkLeaf(e.operand);
|
|
@@ -14801,6 +14558,8 @@ export class Runtime {
|
|
|
14801
14558
|
// Nested step execution does not implement redirects; use normal execution.
|
|
14802
14559
|
if (nested && cmd.redirects.length !== 0)
|
|
14803
14560
|
return false;
|
|
14561
|
+
if (cmd.words.length === 0)
|
|
14562
|
+
return false;
|
|
14804
14563
|
if (cmd.redirects.length === 1) {
|
|
14805
14564
|
const r0Check = cmd.redirects[0];
|
|
14806
14565
|
const w0DevPlain = cmd.words[0].plain;
|
|
@@ -15474,22 +15233,13 @@ export class Runtime {
|
|
|
15474
15233
|
? whilePrintfInd
|
|
15475
15234
|
: (forPrintfInd && this.canSyncPrintfInductionBody(command.body, rawState, forPrintfInd) ? forPrintfInd : undefined));
|
|
15476
15235
|
const prevActivePrintfInd = this._activePrintfInductionName;
|
|
15477
|
-
const prevActiveLoopScript = this._activeLoopBodyScript;
|
|
15478
|
-
const prevActiveLoopCond = this._activeLoopCondScript;
|
|
15479
|
-
const prevActiveLoopVar = this._activeLoopVarName;
|
|
15480
15236
|
this._activePrintfInductionName = printfInductionName;
|
|
15481
|
-
this._activeLoopBodyScript = command.body;
|
|
15482
|
-
this._activeLoopCondScript = command.kind === "while" ? command.condition : undefined;
|
|
15483
|
-
this._activeLoopVarName = command.kind === "for" ? command.name : (printfInductionName ?? (whilePrintfInd || undefined));
|
|
15484
15237
|
let bodySyncOk = false;
|
|
15485
15238
|
try {
|
|
15486
15239
|
bodySyncOk = !(guestArrays(rawState) && (guestArrays(rawState).watches.size > 0 || this.budget.limits.maxExpansionBytes < 65536)) && this.canSyncLoopBody(command.body, rawState, io, false, printfInductionName);
|
|
15487
15240
|
}
|
|
15488
15241
|
finally {
|
|
15489
15242
|
this._activePrintfInductionName = prevActivePrintfInd;
|
|
15490
|
-
this._activeLoopBodyScript = prevActiveLoopScript;
|
|
15491
|
-
this._activeLoopCondScript = prevActiveLoopCond;
|
|
15492
|
-
this._activeLoopVarName = prevActiveLoopVar;
|
|
15493
15243
|
}
|
|
15494
15244
|
if (!bodySyncOk) {
|
|
15495
15245
|
command._skipTrySyncLoop = true;
|
|
@@ -16074,7 +15824,7 @@ export class Runtime {
|
|
|
16074
15824
|
whileArithProg = condCmd.expression;
|
|
16075
15825
|
}
|
|
16076
15826
|
else if (condCmd.kind === "conditional") {
|
|
16077
|
-
if (condCmd.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(condCmd.expression, 0, rawState))
|
|
15827
|
+
if (condCmd.redirects.length !== 0 || rawState.nocasematch || byteLocale(rawState.variables) || !this.canSyncConditional(condCmd.expression, 0, rawState, true))
|
|
16078
15828
|
return undefined;
|
|
16079
15829
|
const checkWhileCond = (e, depth) => {
|
|
16080
15830
|
if (e.kind === "nonempty")
|
|
@@ -16095,7 +15845,7 @@ export class Runtime {
|
|
|
16095
15845
|
!monitor.hasOverlay("BASH_REMATCH") &&
|
|
16096
15846
|
!(store ?? requireArrays(rawState))?.watches.has("BASH_REMATCH"));
|
|
16097
15847
|
}
|
|
16098
|
-
return this.isPureSyncValueWord(e.left, rawState) && (e.operator === "==" || e.operator === "=" || e.operator === "!="
|
|
15848
|
+
return this.isPureSyncValueWord(e.left, rawState) && this.isPureSyncValueWord(e.right, rawState) && (!(e.operator === "==" || e.operator === "=" || e.operator === "!=") || (e.right.parts.length > 0 && e.right.parts.every(p => p.quoted)));
|
|
16099
15849
|
}
|
|
16100
15850
|
if (e.kind === "not")
|
|
16101
15851
|
return checkWhileCond(e.operand, depth + 1);
|
|
@@ -16426,7 +16176,7 @@ export class Runtime {
|
|
|
16426
16176
|
store.epoch = restEpoch;
|
|
16427
16177
|
return loopStatus;
|
|
16428
16178
|
}
|
|
16429
|
-
if (!isShellIdentifier(command.name) || command.name === "OPTIND" || command.name.includes("[") || store?.get(command.name) || (command.words && (command.words.length === 0 || command.words.length > 64)) || rawState.braceexpand === false) {
|
|
16179
|
+
if (!isShellIdentifier(command.name) || command.name === "OPTIND" || controlNames.has(command.name) || command.name === "LC_COLLATE" || command.name.includes("[") || store?.get(command.name) || (command.words && (command.words.length === 0 || command.words.length > 64)) || rawState.braceexpand === false) {
|
|
16430
16180
|
return undefined;
|
|
16431
16181
|
}
|
|
16432
16182
|
const activeValScope = io[valueScope];
|
|
@@ -16925,187 +16675,6 @@ export class Runtime {
|
|
|
16925
16675
|
return pos <= 128 ? fastRedirectScratchViews[pos] : fastRedirectScratchBytes.subarray(0, pos);
|
|
16926
16676
|
}
|
|
16927
16677
|
_activePrintfInductionName = undefined;
|
|
16928
|
-
_activeLoopBodyScript = undefined;
|
|
16929
|
-
_activeLoopCondScript = undefined;
|
|
16930
|
-
_activeLoopVarName = undefined;
|
|
16931
|
-
isVarUnmutatedInScript(varName, script) {
|
|
16932
|
-
for (const list of script.lists) {
|
|
16933
|
-
for (const pipeline of list.pipelines) {
|
|
16934
|
-
for (const cmd of pipeline.commands) {
|
|
16935
|
-
if (cmd.kind === "arithmetic") {
|
|
16936
|
-
if (cmd.expression.hasMutation && cmd.expression.source.includes(varName))
|
|
16937
|
-
return false;
|
|
16938
|
-
continue;
|
|
16939
|
-
}
|
|
16940
|
-
if (cmd.kind === "if") {
|
|
16941
|
-
for (const br of cmd.branches) {
|
|
16942
|
-
if (!this.isVarUnmutatedInScript(varName, br.condition) || !this.isVarUnmutatedInScript(varName, br.body))
|
|
16943
|
-
return false;
|
|
16944
|
-
}
|
|
16945
|
-
if (cmd.otherwise && !this.isVarUnmutatedInScript(varName, cmd.otherwise))
|
|
16946
|
-
return false;
|
|
16947
|
-
continue;
|
|
16948
|
-
}
|
|
16949
|
-
if (cmd.kind === "case") {
|
|
16950
|
-
for (const cl of cmd.clauses) {
|
|
16951
|
-
if (!this.isVarUnmutatedInScript(varName, cl.body))
|
|
16952
|
-
return false;
|
|
16953
|
-
}
|
|
16954
|
-
continue;
|
|
16955
|
-
}
|
|
16956
|
-
if (cmd.kind === "for") {
|
|
16957
|
-
if (cmd.name === varName || !this.isVarUnmutatedInScript(varName, cmd.body))
|
|
16958
|
-
return false;
|
|
16959
|
-
continue;
|
|
16960
|
-
}
|
|
16961
|
-
if (cmd.kind === "arithmetic-for") {
|
|
16962
|
-
if (cmd.expressions.some(e => e && e.hasMutation && e.source.includes(varName)) || !this.isVarUnmutatedInScript(varName, cmd.body))
|
|
16963
|
-
return false;
|
|
16964
|
-
continue;
|
|
16965
|
-
}
|
|
16966
|
-
if (cmd.kind === "while") {
|
|
16967
|
-
if (!this.isVarUnmutatedInScript(varName, cmd.condition) || !this.isVarUnmutatedInScript(varName, cmd.body))
|
|
16968
|
-
return false;
|
|
16969
|
-
continue;
|
|
16970
|
-
}
|
|
16971
|
-
if (cmd.kind === "group" || cmd.kind === "subshell") {
|
|
16972
|
-
if (!this.isVarUnmutatedInScript(varName, cmd.body))
|
|
16973
|
-
return false;
|
|
16974
|
-
continue;
|
|
16975
|
-
}
|
|
16976
|
-
if (cmd.kind !== "simple")
|
|
16977
|
-
return false;
|
|
16978
|
-
for (const w of cmd.words) {
|
|
16979
|
-
const arrAss = getArrayAssignment(w);
|
|
16980
|
-
if (arrAss && arrAss.name === varName)
|
|
16981
|
-
return false;
|
|
16982
|
-
const p0 = w.parts[0];
|
|
16983
|
-
if (p0?.kind === "text" && !p0.quoted && (p0.value.startsWith(varName + "=") || p0.value.startsWith(varName + "+=") || p0.value.startsWith(varName + "[")))
|
|
16984
|
-
return false;
|
|
16985
|
-
}
|
|
16986
|
-
const w0 = cmd.words[0]?.plain;
|
|
16987
|
-
if (w0 === "read" || w0 === "unset" || w0 === "declare" || w0 === "typeset" || w0 === "local" || w0 === "export" || w0 === "mapfile" || w0 === "readarray") {
|
|
16988
|
-
if (w0 === "read" && varName === "REPLY")
|
|
16989
|
-
return false;
|
|
16990
|
-
if (cmd.words.slice(1).some(w => (w.plain ?? "").includes(varName)))
|
|
16991
|
-
return false;
|
|
16992
|
-
}
|
|
16993
|
-
if (w0 === "printf" && cmd.words[1]?.plain === "-v" && (cmd.words[2]?.plain ?? "").startsWith(varName))
|
|
16994
|
-
return false;
|
|
16995
|
-
for (const w of [...cmd.words, ...cmd.redirects.map(r => r.target)]) {
|
|
16996
|
-
for (const pt of w.parts) {
|
|
16997
|
-
if (pt.kind === "arithmetic" && pt.expression.hasMutation && pt.expression.source.includes(varName))
|
|
16998
|
-
return false;
|
|
16999
|
-
if (pt.kind === "substitution" && !this.isVarUnmutatedInScript(varName, pt.script))
|
|
17000
|
-
return false;
|
|
17001
|
-
}
|
|
17002
|
-
}
|
|
17003
|
-
}
|
|
17004
|
-
}
|
|
17005
|
-
}
|
|
17006
|
-
return true;
|
|
17007
|
-
}
|
|
17008
|
-
isVarUnmutatedInActiveLoop(varName) {
|
|
17009
|
-
if (varName === this._activeLoopVarName || varName === this._activePrintfInductionName)
|
|
17010
|
-
return false;
|
|
17011
|
-
if (this._activeLoopCondScript && !this.isVarUnmutatedInScript(varName, this._activeLoopCondScript))
|
|
17012
|
-
return false;
|
|
17013
|
-
if (this._activeLoopBodyScript && !this.isVarUnmutatedInScript(varName, this._activeLoopBodyScript))
|
|
17014
|
-
return false;
|
|
17015
|
-
return true;
|
|
17016
|
-
}
|
|
17017
|
-
isFileReadOnlyScript(script, rawState) {
|
|
17018
|
-
const visit = (value) => {
|
|
17019
|
-
if (value === null || typeof value !== "object")
|
|
17020
|
-
return true;
|
|
17021
|
-
if (Array.isArray(value))
|
|
17022
|
-
return value.every(visit);
|
|
17023
|
-
const node = value;
|
|
17024
|
-
if (typeof node.operator === "string" && "target" in node && !["<", "<<<", "<<", "<<-"].includes(node.operator))
|
|
17025
|
-
return false;
|
|
17026
|
-
if (node.kind === "simple") {
|
|
17027
|
-
const cmd = value;
|
|
17028
|
-
// Unknown commands (including shell functions) cannot promise read-only I/O.
|
|
17029
|
-
const name = cmd.words[0]?.plain;
|
|
17030
|
-
if (name === undefined && !cmd.words.every(word => this.assignment(word) || getArrayAssignment(word)))
|
|
17031
|
-
return false;
|
|
17032
|
-
if (name !== undefined && (hasShellFunction(rawState, name) || !["jq", "cat", "echo", "printf", "test", "[", ":", "true", "false"].includes(name)))
|
|
17033
|
-
return false;
|
|
17034
|
-
}
|
|
17035
|
-
return Object.values(node).every(visit);
|
|
17036
|
-
};
|
|
17037
|
-
return visit(script);
|
|
17038
|
-
}
|
|
17039
|
-
extractLoopInvariantInput(cmd, hasSingleStdinRedir, hasSingleHereStringRedir, rawState) {
|
|
17040
|
-
const w0 = cmd.words[0]?.plain;
|
|
17041
|
-
const resolveInvariantWord = (w) => {
|
|
17042
|
-
if (w.plain !== undefined)
|
|
17043
|
-
return w.plain;
|
|
17044
|
-
if (w.parts.length > 0 && w.parts.every(pt => pt.kind === "text"))
|
|
17045
|
-
return w.parts.map(pt => pt.value).join("");
|
|
17046
|
-
if (w.parts.length === 1 && w.parts[0].kind === "variable") {
|
|
17047
|
-
const vp = w.parts[0];
|
|
17048
|
-
if (!vp.indirect && !vp.operator && !vp.substring && !vp.transform && !vp.length && !vp.prefixNames && getArraySelector(vp) === undefined && this.isVarUnmutatedInActiveLoop(vp.name)) {
|
|
17049
|
-
const val = rawState.variables[vp.name];
|
|
17050
|
-
if (typeof val === "string")
|
|
17051
|
-
return val;
|
|
17052
|
-
}
|
|
17053
|
-
}
|
|
17054
|
-
return undefined;
|
|
17055
|
-
};
|
|
17056
|
-
if (w0 === "jq" || w0 === "numfmt" || w0 === "awk") {
|
|
17057
|
-
if (hasSingleHereStringRedir) {
|
|
17058
|
-
const resolved = resolveInvariantWord(cmd.redirects[0].target);
|
|
17059
|
-
return resolved !== undefined ? resolved + "\n" : undefined;
|
|
17060
|
-
}
|
|
17061
|
-
if (this._fileWrites?.size || this._outputFiles?.size)
|
|
17062
|
-
return undefined;
|
|
17063
|
-
if ([this._activeLoopBodyScript, this._activeLoopCondScript].some(script => script && !this.isFileReadOnlyScript(script, rawState)))
|
|
17064
|
-
return undefined;
|
|
17065
|
-
const fPlain = hasSingleStdinRedir ? cmd.redirects[0].target.plain : cmd.words[cmd.words.length - 1]?.plain;
|
|
17066
|
-
if (!fPlain || fPlain.startsWith("-") || fPlain === "/dev/stdin")
|
|
17067
|
-
return undefined;
|
|
17068
|
-
const vCheck = this.tryReadMemoryFileViewSync(resolvePath(rawState.cwd, fPlain), false);
|
|
17069
|
-
if (!vCheck || vCheck.byteLength > 16384 || vCheck.includes(0))
|
|
17070
|
-
return undefined;
|
|
17071
|
-
return sharedSyncPipeDecoder.decode(vCheck);
|
|
17072
|
-
}
|
|
17073
|
-
if (w0 === "cat") {
|
|
17074
|
-
if (hasSingleHereStringRedir) {
|
|
17075
|
-
const resolved = resolveInvariantWord(cmd.redirects[0].target);
|
|
17076
|
-
return resolved !== undefined ? resolved + "\n" : undefined;
|
|
17077
|
-
}
|
|
17078
|
-
if (this._fileWrites?.size || this._outputFiles?.size)
|
|
17079
|
-
return undefined;
|
|
17080
|
-
if ([this._activeLoopBodyScript, this._activeLoopCondScript].some(script => script && !this.isFileReadOnlyScript(script, rawState)))
|
|
17081
|
-
return undefined;
|
|
17082
|
-
const fPlain = hasSingleStdinRedir ? cmd.redirects[0].target.plain : cmd.words[1]?.plain;
|
|
17083
|
-
if (!fPlain || fPlain.startsWith("-") || fPlain === "/dev/stdin")
|
|
17084
|
-
return undefined;
|
|
17085
|
-
const vCheck = this.tryReadMemoryFileViewSync(resolvePath(rawState.cwd, fPlain), false);
|
|
17086
|
-
if (!vCheck || vCheck.byteLength > 16384 || vCheck.includes(0))
|
|
17087
|
-
return undefined;
|
|
17088
|
-
return sharedSyncPipeDecoder.decode(vCheck);
|
|
17089
|
-
}
|
|
17090
|
-
if (w0 === "echo" && cmd.words.length === 2) {
|
|
17091
|
-
const resolved = resolveInvariantWord(cmd.words[1]);
|
|
17092
|
-
if (resolved !== undefined && !resolved.startsWith("-"))
|
|
17093
|
-
return resolved + "\n";
|
|
17094
|
-
return undefined;
|
|
17095
|
-
}
|
|
17096
|
-
if (w0 === "printf" && cmd.words.length >= 2) {
|
|
17097
|
-
const args = [];
|
|
17098
|
-
for (let i = 1; i < cmd.words.length; i++) {
|
|
17099
|
-
const r = resolveInvariantWord(cmd.words[i]);
|
|
17100
|
-
if (r === undefined)
|
|
17101
|
-
return undefined;
|
|
17102
|
-
args.push(r);
|
|
17103
|
-
}
|
|
17104
|
-
const formatted = tryFastPrintf(args);
|
|
17105
|
-
return formatted;
|
|
17106
|
-
}
|
|
17107
|
-
return undefined;
|
|
17108
|
-
}
|
|
17109
16678
|
canSyncPrintfInductionBody(script, rawState, inductionName) {
|
|
17110
16679
|
for (const list of script.lists)
|
|
17111
16680
|
for (const pipeline of list.pipelines)
|
|
@@ -17800,6 +17369,10 @@ export class Runtime {
|
|
|
17800
17369
|
return check(tree);
|
|
17801
17370
|
}
|
|
17802
17371
|
canSyncErrorArithmeticStmt(expr, rawState) {
|
|
17372
|
+
// Expansion can introduce array, attributed, or control-variable references.
|
|
17373
|
+
// Their reads and writes require the full arithmetic evaluator.
|
|
17374
|
+
if (expr.source.includes("$"))
|
|
17375
|
+
return false;
|
|
17803
17376
|
if (expr.source.includes("/") || expr.source.includes("%") || expr.source.includes("<<") || expr.source.includes(">>") || expr.source.includes("**") || expr.source.includes("["))
|
|
17804
17377
|
return false;
|
|
17805
17378
|
try {
|
|
@@ -17962,19 +17535,34 @@ export class Runtime {
|
|
|
17962
17535
|
const prevFastSubPos = this._fastSubPositional;
|
|
17963
17536
|
rawState.positional = fnArgs;
|
|
17964
17537
|
this._fastSubPositional = fnArgs;
|
|
17965
|
-
const savedLocals =
|
|
17538
|
+
const savedLocals = [];
|
|
17539
|
+
const seenLocals = new Set();
|
|
17966
17540
|
try {
|
|
17967
|
-
for (let i = 0; i < fnCall.locals.length;
|
|
17968
|
-
|
|
17969
|
-
|
|
17970
|
-
|
|
17971
|
-
|
|
17972
|
-
|
|
17973
|
-
|
|
17974
|
-
|
|
17975
|
-
|
|
17976
|
-
|
|
17977
|
-
|
|
17541
|
+
for (let i = 0; i < fnCall.locals.length;) {
|
|
17542
|
+
this.budget.tick();
|
|
17543
|
+
let end = i + 1;
|
|
17544
|
+
while (end < fnCall.locals.length && !fnCall.locals[end].isFirstInCmd)
|
|
17545
|
+
end++;
|
|
17546
|
+
// Expand the entire simple command before any of its bindings change.
|
|
17547
|
+
const values = fnCall.locals.slice(i, end).map(loc => loc.valueWord === undefined
|
|
17548
|
+
? undefined
|
|
17549
|
+
: this.fastValueWord(loc.valueWord, rawState, io, false, false, false, false, 0, step.line));
|
|
17550
|
+
const start = i;
|
|
17551
|
+
for (; i < end; i++) {
|
|
17552
|
+
const loc = fnCall.locals[i];
|
|
17553
|
+
const alreadyLocal = seenLocals.has(loc.name);
|
|
17554
|
+
if (!alreadyLocal) {
|
|
17555
|
+
savedLocals.push({ name: loc.name, val: rawState.variables[loc.name], wasTouched: touched.has(loc.name) });
|
|
17556
|
+
seenLocals.add(loc.name);
|
|
17557
|
+
}
|
|
17558
|
+
const value = values[i - start];
|
|
17559
|
+
if (value !== undefined) {
|
|
17560
|
+
rawState.variables[loc.name] = value;
|
|
17561
|
+
touched.add(loc.name);
|
|
17562
|
+
}
|
|
17563
|
+
else if (!alreadyLocal) {
|
|
17564
|
+
delete rawState.variables[loc.name];
|
|
17565
|
+
}
|
|
17978
17566
|
}
|
|
17979
17567
|
}
|
|
17980
17568
|
if (fnCall.steps.length > 0) {
|
|
@@ -17982,11 +17570,12 @@ export class Runtime {
|
|
|
17982
17570
|
}
|
|
17983
17571
|
else {
|
|
17984
17572
|
rawState.status = 0;
|
|
17985
|
-
onUpdate({ lastCmd: step.cmd, lastArg: fnArgs.length > 0 ? fnArgs[fnArgs.length - 1] : fnCall.name });
|
|
17986
17573
|
}
|
|
17574
|
+
rawState.lastArgument = fnArgs.at(-1) ?? fnCall.name;
|
|
17575
|
+
onUpdate({ lastCmd: step.cmd, lastArg: rawState.lastArgument });
|
|
17987
17576
|
}
|
|
17988
17577
|
finally {
|
|
17989
|
-
for (let i =
|
|
17578
|
+
for (let i = savedLocals.length - 1; i >= 0; i--) {
|
|
17990
17579
|
const saved = savedLocals[i];
|
|
17991
17580
|
if (saved.val !== undefined) {
|
|
17992
17581
|
rawState.variables[saved.name] = saved.val;
|
|
@@ -21276,6 +20865,7 @@ export class Runtime {
|
|
|
21276
20865
|
}
|
|
21277
20866
|
if (hasThrown)
|
|
21278
20867
|
throw thrownError;
|
|
20868
|
+
state.lastArgument = pendingResume?.lastArgument ?? args.at(-1) ?? name;
|
|
21279
20869
|
return exitStatus;
|
|
21280
20870
|
}
|
|
21281
20871
|
finally {
|
|
@@ -21795,6 +21385,7 @@ export class Runtime {
|
|
|
21795
21385
|
}
|
|
21796
21386
|
if (outcome.kind === "throw")
|
|
21797
21387
|
throw outcome.reason;
|
|
21388
|
+
state.lastArgument = context.args.at(-1) ?? context.command;
|
|
21798
21389
|
return outcome.value;
|
|
21799
21390
|
}
|
|
21800
21391
|
if (selectedKind === "builtin" && !(state.externalInvocation && this.commands.has(context.command))) {
|
|
@@ -27156,6 +26747,10 @@ export class Runtime {
|
|
|
27156
26747
|
expandSyncTrCharArray(spec) {
|
|
27157
26748
|
if (spec.length === 0)
|
|
27158
26749
|
return undefined;
|
|
26750
|
+
// Non-ASCII sets require the byte-oriented tr parser.
|
|
26751
|
+
for (let i = 0; i < spec.length; i++)
|
|
26752
|
+
if (spec.charCodeAt(i) >= 128)
|
|
26753
|
+
return undefined;
|
|
27159
26754
|
// Escaped endpoints need the tr parser before range expansion.
|
|
27160
26755
|
if (spec.includes("\\") && spec.includes("-"))
|
|
27161
26756
|
return undefined;
|
|
@@ -27688,10 +27283,10 @@ export class Runtime {
|
|
|
27688
27283
|
if (!el || typeof el !== "object" || Array.isArray(el))
|
|
27689
27284
|
return undefined;
|
|
27690
27285
|
const rec = el;
|
|
27691
|
-
const k = rec.key
|
|
27286
|
+
const k = [rec.key, rec.Key, rec.name, rec.Name].find(value => value !== undefined && value !== null && value !== false);
|
|
27692
27287
|
if (typeof k !== "string")
|
|
27693
27288
|
return undefined;
|
|
27694
|
-
obj[k] = Object.hasOwn(rec, "value") ? rec.value : (Object.hasOwn(rec, "
|
|
27289
|
+
obj[k] = Object.hasOwn(rec, "value") ? rec.value : (Object.hasOwn(rec, "Value") ? rec.Value : null);
|
|
27695
27290
|
}
|
|
27696
27291
|
return [obj];
|
|
27697
27292
|
}
|
|
@@ -27724,7 +27319,7 @@ export class Runtime {
|
|
|
27724
27319
|
return [item.map(x => x === null ? "" : String(x)).join(arg)];
|
|
27725
27320
|
}
|
|
27726
27321
|
if (fn === "split")
|
|
27727
|
-
return typeof item === "string" ? [item.split(arg)] : undefined;
|
|
27322
|
+
return typeof item === "string" ? [arg === "" ? Array.from(item) : item.split(arg)] : undefined;
|
|
27728
27323
|
if (fn === "ltrimstr")
|
|
27729
27324
|
return typeof item === "string" ? [item.startsWith(arg) ? item.slice(arg.length) : item] : undefined;
|
|
27730
27325
|
if (fn === "rtrimstr")
|
|
@@ -27986,9 +27581,8 @@ export class Runtime {
|
|
|
27986
27581
|
break;
|
|
27987
27582
|
}
|
|
27988
27583
|
if (!a.startsWith("-") || a === "-") {
|
|
27989
|
-
operands.push(
|
|
27990
|
-
|
|
27991
|
-
continue;
|
|
27584
|
+
operands.push(...args.slice(idx));
|
|
27585
|
+
break;
|
|
27992
27586
|
}
|
|
27993
27587
|
if (a === "-a" || a === "--multiple") {
|
|
27994
27588
|
multiple = true;
|
|
@@ -28370,7 +27964,9 @@ export class Runtime {
|
|
|
28370
27964
|
}
|
|
28371
27965
|
if (st.kind === "=") {
|
|
28372
27966
|
out.push(String(idx1));
|
|
28373
|
-
|
|
27967
|
+
// Line numbers are generated records, always terminated, even when
|
|
27968
|
+
// the final input record is partial. Later prints can override this.
|
|
27969
|
+
lastInputIndex = -1;
|
|
28374
27970
|
continue;
|
|
28375
27971
|
}
|
|
28376
27972
|
if (st.kind === "q") {
|
|
@@ -28413,8 +28009,10 @@ export class Runtime {
|
|
|
28413
28009
|
}
|
|
28414
28010
|
return replacement;
|
|
28415
28011
|
});
|
|
28416
|
-
if (matchedOnce && st.printOnMatch)
|
|
28012
|
+
if (matchedOnce && st.printOnMatch) {
|
|
28417
28013
|
out.push(l);
|
|
28014
|
+
lastInputIndex = i;
|
|
28015
|
+
}
|
|
28418
28016
|
}
|
|
28419
28017
|
if (quitNow) {
|
|
28420
28018
|
break;
|
|
@@ -29600,22 +29198,16 @@ export class Runtime {
|
|
|
29600
29198
|
if (endField !== undefined && endField < startField)
|
|
29601
29199
|
return undefined;
|
|
29602
29200
|
const kf = km[3] ?? "";
|
|
29603
|
-
if (kf.
|
|
29604
|
-
keyNum =
|
|
29605
|
-
|
|
29606
|
-
|
|
29607
|
-
|
|
29608
|
-
|
|
29609
|
-
|
|
29610
|
-
|
|
29611
|
-
|
|
29612
|
-
|
|
29613
|
-
if (kf.includes("f"))
|
|
29614
|
-
keyFold = true;
|
|
29615
|
-
if (kf.includes("b"))
|
|
29616
|
-
keyBlanks = true;
|
|
29617
|
-
if (kf.includes("V"))
|
|
29618
|
-
keyVer = true;
|
|
29201
|
+
if (kf.length > 0) {
|
|
29202
|
+
keyNum = kf.includes("n");
|
|
29203
|
+
keyHuman = kf.includes("h");
|
|
29204
|
+
keyMonth = kf.includes("M");
|
|
29205
|
+
keyDict = kf.includes("d");
|
|
29206
|
+
keyRev = kf.includes("r");
|
|
29207
|
+
keyFold = kf.includes("f");
|
|
29208
|
+
keyBlanks = kf.includes("b");
|
|
29209
|
+
keyVer = kf.includes("V");
|
|
29210
|
+
}
|
|
29619
29211
|
}
|
|
29620
29212
|
const extractKey = (l) => {
|
|
29621
29213
|
let raw = keySpec === undefined
|
|
@@ -29647,7 +29239,14 @@ export class Runtime {
|
|
|
29647
29239
|
raw = raw.replace(/^[ \t]+/, "");
|
|
29648
29240
|
if (keyDict)
|
|
29649
29241
|
raw = raw.replace(/[^a-zA-Z0-9 \t]+/g, "");
|
|
29650
|
-
|
|
29242
|
+
if (!keyFold)
|
|
29243
|
+
return raw;
|
|
29244
|
+
let folded = "";
|
|
29245
|
+
for (let i = 0; i < raw.length; i++) {
|
|
29246
|
+
const code = raw.charCodeAt(i);
|
|
29247
|
+
folded += code >= 97 && code <= 122 ? String.fromCharCode(code - 32) : raw[i];
|
|
29248
|
+
}
|
|
29249
|
+
return folded;
|
|
29651
29250
|
};
|
|
29652
29251
|
const parseNum = (s) => {
|
|
29653
29252
|
const m = /^[ \t]*(-?(?:\d+(?:\.\d*)?|\.\d+))/.exec(s);
|
|
@@ -29680,6 +29279,24 @@ export class Runtime {
|
|
|
29680
29279
|
const idx = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"].indexOf(m[1].toUpperCase());
|
|
29681
29280
|
return idx === -1 ? 0 : idx + 1;
|
|
29682
29281
|
};
|
|
29282
|
+
const encoded = new Map();
|
|
29283
|
+
const bytesOf = (line) => {
|
|
29284
|
+
let bytes = encoded.get(line);
|
|
29285
|
+
if (bytes === undefined) {
|
|
29286
|
+
bytes = fastSharedTextEncoder.encode(line);
|
|
29287
|
+
encoded.set(line, bytes);
|
|
29288
|
+
}
|
|
29289
|
+
return bytes;
|
|
29290
|
+
};
|
|
29291
|
+
const compareBytes = (left, right) => {
|
|
29292
|
+
const a = bytesOf(left);
|
|
29293
|
+
const b = bytesOf(right);
|
|
29294
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
29295
|
+
if (a[i] !== b[i])
|
|
29296
|
+
return a[i] - b[i];
|
|
29297
|
+
}
|
|
29298
|
+
return a.length - b.length;
|
|
29299
|
+
};
|
|
29683
29300
|
const sorted = [...rawLines].sort((a, b) => {
|
|
29684
29301
|
const ka = extractKey(a);
|
|
29685
29302
|
const kb = extractKey(b);
|
|
@@ -29706,15 +29323,13 @@ export class Runtime {
|
|
|
29706
29323
|
return keyRev ? -vc : vc;
|
|
29707
29324
|
}
|
|
29708
29325
|
else if (ka !== kb) {
|
|
29709
|
-
|
|
29326
|
+
const comparison = compareBytes(ka, kb);
|
|
29327
|
+
return keyRev ? -comparison : comparison;
|
|
29710
29328
|
}
|
|
29711
29329
|
if (uniq || stable)
|
|
29712
29330
|
return 0;
|
|
29713
|
-
const
|
|
29714
|
-
|
|
29715
|
-
if (fa !== fb)
|
|
29716
|
-
return fa < fb ? (rev ? 1 : -1) : (rev ? -1 : 1);
|
|
29717
|
-
return a < b ? (rev ? 1 : -1) : a > b ? (rev ? -1 : 1) : 0;
|
|
29331
|
+
const comparison = compareBytes(a, b);
|
|
29332
|
+
return rev ? -comparison : comparison;
|
|
29718
29333
|
});
|
|
29719
29334
|
if (!uniq)
|
|
29720
29335
|
return sorted;
|