@poe-platform/safe-bash 0.1.74 → 0.1.76
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/dist/safe-bash/browser.js +435 -176
- package/dist/safe-bash/browser.js.map +4 -4
- package/dist/safe-bash/commands/structured/limits.js +3 -1
- package/dist/safe-bash/commands/structured/limits.js.map +1 -1
- package/dist/safe-bash/commands/structured/split.d.ts.map +1 -1
- package/dist/safe-bash/commands/structured/split.js +7 -2
- package/dist/safe-bash/commands/structured/split.js.map +1 -1
- package/dist/safe-bash/commands/structured/values.d.ts.map +1 -1
- package/dist/safe-bash/commands/structured/values.js +4 -2
- package/dist/safe-bash/commands/structured/values.js.map +1 -1
- package/dist/safe-bash/commands/text-programs/awk-reader.d.ts +25 -0
- package/dist/safe-bash/commands/text-programs/awk-reader.d.ts.map +1 -0
- package/dist/safe-bash/commands/text-programs/awk-reader.js +180 -0
- package/dist/safe-bash/commands/text-programs/awk-reader.js.map +1 -0
- package/dist/safe-bash/commands/text-programs/awk-retention.d.ts +11 -0
- package/dist/safe-bash/commands/text-programs/awk-retention.d.ts.map +1 -0
- package/dist/safe-bash/commands/text-programs/awk-retention.js +31 -0
- package/dist/safe-bash/commands/text-programs/awk-retention.js.map +1 -0
- package/dist/safe-bash/commands/text-programs/awk-runtime.d.ts +11 -1
- package/dist/safe-bash/commands/text-programs/awk-runtime.d.ts.map +1 -1
- package/dist/safe-bash/commands/text-programs/awk-runtime.js +291 -191
- package/dist/safe-bash/commands/text-programs/awk-runtime.js.map +1 -1
- package/dist/safe-bash/commands/text-programs/awk.d.ts.map +1 -1
- package/dist/safe-bash/commands/text-programs/awk.js +2 -1
- package/dist/safe-bash/commands/text-programs/awk.js.map +1 -1
- package/dist/safe-bash/commands/text.d.ts.map +1 -1
- package/dist/safe-bash/commands/text.js +194 -52
- package/dist/safe-bash/commands/text.js.map +1 -1
- package/dist/safe-bash/shell/arithmetic-parameters.d.ts +2 -0
- package/dist/safe-bash/shell/arithmetic-parameters.d.ts.map +1 -1
- package/dist/safe-bash/shell/arithmetic-parameters.js +1 -1
- package/dist/safe-bash/shell/arithmetic-parameters.js.map +1 -1
- package/dist/safe-bash/shell/arithmetic.d.ts +4 -3
- package/dist/safe-bash/shell/arithmetic.d.ts.map +1 -1
- package/dist/safe-bash/shell/arithmetic.js +19 -7
- package/dist/safe-bash/shell/arithmetic.js.map +1 -1
- package/dist/safe-bash/shell/arrays/syntax.d.ts +6 -5
- package/dist/safe-bash/shell/arrays/syntax.d.ts.map +1 -1
- package/dist/safe-bash/shell/arrays/syntax.js +22 -12
- package/dist/safe-bash/shell/arrays/syntax.js.map +1 -1
- package/dist/safe-bash/shell/index.d.ts +1 -1
- package/dist/safe-bash/shell/index.d.ts.map +1 -1
- package/dist/safe-bash/shell/parse-budget.d.ts +10 -0
- package/dist/safe-bash/shell/parse-budget.d.ts.map +1 -0
- package/dist/safe-bash/shell/parse-budget.js +29 -0
- package/dist/safe-bash/shell/parse-budget.js.map +1 -0
- package/dist/safe-bash/shell/parser.d.ts +6 -4
- package/dist/safe-bash/shell/parser.d.ts.map +1 -1
- package/dist/safe-bash/shell/parser.js +80 -29
- package/dist/safe-bash/shell/parser.js.map +1 -1
- package/dist/safe-bash/shell/runtime.d.ts +3 -0
- package/dist/safe-bash/shell/runtime.d.ts.map +1 -1
- package/dist/safe-bash/shell/runtime.js +136 -78
- package/dist/safe-bash/shell/runtime.js.map +1 -1
- package/dist/safe-bash/shell/shell.js +2 -2
- package/dist/safe-bash/shell/shell.js.map +1 -1
- package/dist/safe-bash/shell/types.d.ts +4 -0
- package/dist/safe-bash/shell/types.d.ts.map +1 -1
- package/dist/safe-bash/shell/types.js.map +1 -1
- package/dist/safe-bash/shell/worker-limits.d.ts.map +1 -1
- package/dist/safe-bash/shell/worker-limits.js +1 -0
- package/dist/safe-bash/shell/worker-limits.js.map +1 -1
- package/package.json +2 -2
|
@@ -4673,6 +4673,103 @@ async function sortRecords(records, compare, work) {
|
|
|
4673
4673
|
}
|
|
4674
4674
|
return source;
|
|
4675
4675
|
}
|
|
4676
|
+
async function cutRanges(list, work) {
|
|
4677
|
+
const ranges = [];
|
|
4678
|
+
let tokenStart = 0;
|
|
4679
|
+
let dash = -1;
|
|
4680
|
+
let start = 0;
|
|
4681
|
+
let end = 0;
|
|
4682
|
+
let invalid = false;
|
|
4683
|
+
for (let index = 0; index <= list.length; index++) {
|
|
4684
|
+
const checkpoint = work.charge();
|
|
4685
|
+
if (checkpoint) await checkpoint;
|
|
4686
|
+
const character = list[index];
|
|
4687
|
+
if (character === "," || character === " " || index === list.length) {
|
|
4688
|
+
if (index === tokenStart) {
|
|
4689
|
+
if (index === 0 || index === list.length) throw new UsageError("invalid range ''");
|
|
4690
|
+
tokenStart = index + 1;
|
|
4691
|
+
continue;
|
|
4692
|
+
}
|
|
4693
|
+
if (invalid || dash === tokenStart && dash === index - 1) throw new UsageError(`invalid range '${list.slice(tokenStart, index)}'`);
|
|
4694
|
+
if (dash === tokenStart) start = 1;
|
|
4695
|
+
if (dash < 0) end = start;
|
|
4696
|
+
const openEnd = dash >= 0 && dash === index - 1;
|
|
4697
|
+
if (openEnd) end = Infinity;
|
|
4698
|
+
if (!Number.isSafeInteger(start) || start < 1) throw new UsageError(`invalid number '${list.slice(tokenStart, dash < 0 ? index : dash)}'`);
|
|
4699
|
+
if (!openEnd && (!Number.isSafeInteger(end) || end < 1)) throw new UsageError(`invalid number '${list.slice(dash < 0 ? tokenStart : dash + 1, index)}'`);
|
|
4700
|
+
if (end < start) throw new UsageError(`decreasing range '${list.slice(tokenStart, index)}'`);
|
|
4701
|
+
ranges.push({ start, end });
|
|
4702
|
+
tokenStart = index + 1;
|
|
4703
|
+
dash = -1;
|
|
4704
|
+
start = 0;
|
|
4705
|
+
end = 0;
|
|
4706
|
+
invalid = false;
|
|
4707
|
+
} else if (character === "-" && dash < 0) {
|
|
4708
|
+
dash = index;
|
|
4709
|
+
} else {
|
|
4710
|
+
const digit2 = list.charCodeAt(index) - 48;
|
|
4711
|
+
if (digit2 < 0 || digit2 > 9) invalid = true;
|
|
4712
|
+
else if (dash < 0) start = start * 10 + digit2;
|
|
4713
|
+
else end = end * 10 + digit2;
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
const ordered = await sortRecords(ranges, async (left, right) => left.start - right.start, work);
|
|
4717
|
+
const normalized = [];
|
|
4718
|
+
for (const range of ordered) {
|
|
4719
|
+
const checkpoint = work.charge();
|
|
4720
|
+
if (checkpoint) await checkpoint;
|
|
4721
|
+
const previous = normalized.at(-1);
|
|
4722
|
+
if (previous && range.start <= previous.end + 1) previous.end = Math.max(previous.end, range.end);
|
|
4723
|
+
else normalized.push(range);
|
|
4724
|
+
}
|
|
4725
|
+
return normalized;
|
|
4726
|
+
}
|
|
4727
|
+
var CutOutput = class {
|
|
4728
|
+
constructor(context, work) {
|
|
4729
|
+
this.context = context;
|
|
4730
|
+
this.work = work;
|
|
4731
|
+
}
|
|
4732
|
+
context;
|
|
4733
|
+
work;
|
|
4734
|
+
#buffer = new Uint8Array(64 * 1024);
|
|
4735
|
+
#used = 0;
|
|
4736
|
+
async write(bytes2) {
|
|
4737
|
+
for (let offset = 0; offset < bytes2.length; ) {
|
|
4738
|
+
const length = Math.min(4096, bytes2.length - offset, this.#buffer.length - this.#used);
|
|
4739
|
+
const checkpoint = this.work.charge(length);
|
|
4740
|
+
if (checkpoint) await checkpoint;
|
|
4741
|
+
this.#buffer.set(bytes2.subarray(offset, offset + length), this.#used);
|
|
4742
|
+
this.#used += length;
|
|
4743
|
+
offset += length;
|
|
4744
|
+
if (this.#used === this.#buffer.length) await this.flush();
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
async text(text) {
|
|
4748
|
+
for (let offset = 0; offset < text.length; ) {
|
|
4749
|
+
let end = Math.min(offset + 4096, text.length);
|
|
4750
|
+
const last = text.charCodeAt(end - 1);
|
|
4751
|
+
if (end < text.length && last >= 55296 && last <= 56319) end--;
|
|
4752
|
+
await this.write(encoder2.encode(text.slice(offset, end)));
|
|
4753
|
+
offset = end;
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
async flush() {
|
|
4757
|
+
if (!this.#used) return;
|
|
4758
|
+
const bytes2 = this.#buffer.slice(0, this.#used);
|
|
4759
|
+
this.#used = 0;
|
|
4760
|
+
await output(this.context, bytes2);
|
|
4761
|
+
}
|
|
4762
|
+
};
|
|
4763
|
+
async function cutFieldBoundary(record4, separator, start, work) {
|
|
4764
|
+
for (let offset = start; offset < record4.length; offset += 4096) {
|
|
4765
|
+
const window = record4.subarray(offset, Math.min(record4.length, offset + 4096 + separator.length - 1));
|
|
4766
|
+
const found = window.indexOf(separator);
|
|
4767
|
+
const checkpoint = work.charge(found < 0 ? Math.min(4096, window.length) : found + separator.length);
|
|
4768
|
+
if (checkpoint) await checkpoint;
|
|
4769
|
+
if (found >= 0) return offset + found;
|
|
4770
|
+
}
|
|
4771
|
+
return -1;
|
|
4772
|
+
}
|
|
4676
4773
|
async function compareSortBytes(left, right, work) {
|
|
4677
4774
|
const length = Math.min(left.length, right.length);
|
|
4678
4775
|
for (let offset = 0; offset < length; offset += 1024) {
|
|
@@ -5032,85 +5129,104 @@ function textCommands() {
|
|
|
5032
5129
|
if (modes.length !== 1) throw new UsageError("exactly one byte, character, or field list is required");
|
|
5033
5130
|
const mode = modes[0];
|
|
5034
5131
|
if (mode !== "f" && (parsed.flags.has("d") || parsed.flags.has("s"))) throw new UsageError("delimiter options require field mode");
|
|
5035
|
-
const
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
const start = match[3] === void 0 ? integer(match[1], 1) : 1;
|
|
5039
|
-
const end = match[3] !== void 0 ? integer(match[3], 1) : match[2] === void 0 ? start : match[2] === "" ? Infinity : integer(match[2], 1);
|
|
5040
|
-
if (end < start) throw new UsageError(`decreasing range '${part}'`);
|
|
5041
|
-
return { start, end };
|
|
5042
|
-
});
|
|
5043
|
-
const selected = (position) => ranges.some((range) => position >= range.start && position <= range.end) !== parsed.flags.has("C");
|
|
5132
|
+
const work = new SortWork(context.signal);
|
|
5133
|
+
const ranges = await cutRanges(value(parsed, mode), work);
|
|
5134
|
+
const complement = parsed.flags.has("C");
|
|
5044
5135
|
const delimiter = value(parsed, "d") ?? " ";
|
|
5045
|
-
if (
|
|
5136
|
+
if (delimiter.length !== (delimiter.codePointAt(0) > 65535 ? 2 : 1)) throw new UsageError("delimiter must be a single character");
|
|
5046
5137
|
const outputDelimiter = value(parsed, "o");
|
|
5047
5138
|
const recordDelimiter = parsed.flags.has("z") ? 0 : 10;
|
|
5139
|
+
const separator = import_buffer.Buffer.from(delimiter);
|
|
5140
|
+
const writer = new CutOutput(context, work);
|
|
5048
5141
|
let exitCode = 0;
|
|
5049
5142
|
for (const name2 of parsed.operands.length ? parsed.operands : ["-"]) {
|
|
5050
5143
|
try {
|
|
5051
5144
|
for await (const line of lines(input(context, name2), recordDelimiter)) {
|
|
5052
5145
|
context.signal.throwIfAborted();
|
|
5053
|
-
let
|
|
5146
|
+
let cursor = 0;
|
|
5147
|
+
const selected = (position) => {
|
|
5148
|
+
while (cursor < ranges.length && position > ranges[cursor].end) cursor++;
|
|
5149
|
+
return (cursor < ranges.length && position >= ranges[cursor].start) !== complement;
|
|
5150
|
+
};
|
|
5054
5151
|
if (mode === "f") {
|
|
5055
5152
|
const record4 = import_buffer.Buffer.from(line.bytes.buffer, line.bytes.byteOffset, line.bytes.byteLength);
|
|
5056
|
-
|
|
5057
|
-
let boundary = record4.indexOf(separator);
|
|
5153
|
+
let boundary = await cutFieldBoundary(record4, separator, 0, work);
|
|
5058
5154
|
if (boundary < 0) {
|
|
5059
5155
|
if (parsed.flags.has("s")) continue;
|
|
5060
|
-
|
|
5156
|
+
await writer.write(line.bytes);
|
|
5061
5157
|
} else {
|
|
5062
|
-
const pieces = [];
|
|
5063
|
-
const joiner = encoder2.encode(outputDelimiter ?? delimiter);
|
|
5064
5158
|
let field = 1;
|
|
5065
5159
|
let start = 0;
|
|
5066
5160
|
let emitted = false;
|
|
5067
5161
|
while (true) {
|
|
5162
|
+
const checkpoint = work.charge();
|
|
5163
|
+
if (checkpoint) await checkpoint;
|
|
5068
5164
|
if (selected(field++)) {
|
|
5069
|
-
if (emitted)
|
|
5070
|
-
|
|
5165
|
+
if (emitted) await writer.text(outputDelimiter ?? delimiter);
|
|
5166
|
+
await writer.write(record4.subarray(start, boundary < 0 ? record4.length : boundary));
|
|
5071
5167
|
emitted = true;
|
|
5072
5168
|
}
|
|
5073
5169
|
if (boundary < 0) break;
|
|
5074
5170
|
start = boundary + separator.length;
|
|
5075
|
-
boundary = record4
|
|
5171
|
+
boundary = await cutFieldBoundary(record4, separator, start, work);
|
|
5076
5172
|
}
|
|
5077
|
-
bytes2 = concatenate(pieces);
|
|
5078
5173
|
}
|
|
5079
5174
|
} else if (mode === "b") {
|
|
5080
|
-
const chunks = [];
|
|
5081
|
-
let start = -1;
|
|
5082
5175
|
let emitted = false;
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5176
|
+
let previousIncluded = false;
|
|
5177
|
+
for (let offset = 0; offset < line.bytes.length; offset += 4096) {
|
|
5178
|
+
const end = Math.min(line.bytes.length, offset + 4096);
|
|
5179
|
+
const checkpoint = work.charge(end - offset);
|
|
5180
|
+
if (checkpoint) await checkpoint;
|
|
5181
|
+
let start = -1;
|
|
5182
|
+
for (let index = offset; index < end; index++) {
|
|
5183
|
+
const included = selected(index + 1);
|
|
5184
|
+
if (included && start < 0) {
|
|
5185
|
+
if (!previousIncluded && emitted && outputDelimiter !== void 0) await writer.text(outputDelimiter);
|
|
5186
|
+
start = index;
|
|
5187
|
+
emitted = true;
|
|
5188
|
+
}
|
|
5189
|
+
if (!included && start >= 0) {
|
|
5190
|
+
await writer.write(line.bytes.subarray(start, index));
|
|
5191
|
+
start = -1;
|
|
5192
|
+
}
|
|
5193
|
+
previousIncluded = included;
|
|
5091
5194
|
}
|
|
5195
|
+
if (start >= 0) await writer.write(line.bytes.subarray(start, end));
|
|
5092
5196
|
}
|
|
5093
|
-
bytes2 = concatenate(chunks);
|
|
5094
5197
|
} else {
|
|
5095
|
-
const
|
|
5096
|
-
const pieces = [];
|
|
5198
|
+
const decoder3 = new TextDecoder();
|
|
5097
5199
|
let index = 0;
|
|
5098
|
-
let
|
|
5099
|
-
let
|
|
5100
|
-
for (
|
|
5101
|
-
const
|
|
5102
|
-
|
|
5103
|
-
if (
|
|
5104
|
-
|
|
5105
|
-
|
|
5200
|
+
let emitted = false;
|
|
5201
|
+
let previousIncluded = false;
|
|
5202
|
+
for (let offset = 0; offset < line.bytes.length; offset += 4096) {
|
|
5203
|
+
const end = Math.min(line.bytes.length, offset + 4096);
|
|
5204
|
+
const checkpoint = work.charge(end - offset);
|
|
5205
|
+
if (checkpoint) await checkpoint;
|
|
5206
|
+
const text = decoder3.decode(line.bytes.subarray(offset, end), { stream: end < line.bytes.length });
|
|
5207
|
+
let start = -1;
|
|
5208
|
+
let position = 0;
|
|
5209
|
+
for (const character of text) {
|
|
5210
|
+
const checkpoint2 = work.charge();
|
|
5211
|
+
if (checkpoint2) await checkpoint2;
|
|
5212
|
+
const included = selected(++index);
|
|
5213
|
+
if (included && start < 0) {
|
|
5214
|
+
if (!previousIncluded && emitted && outputDelimiter !== void 0) await writer.text(outputDelimiter);
|
|
5215
|
+
start = position;
|
|
5216
|
+
emitted = true;
|
|
5217
|
+
}
|
|
5218
|
+
if (!included && start >= 0) {
|
|
5219
|
+
await writer.text(text.slice(start, position));
|
|
5220
|
+
start = -1;
|
|
5221
|
+
}
|
|
5222
|
+
position += character.length;
|
|
5223
|
+
previousIncluded = included;
|
|
5106
5224
|
}
|
|
5107
|
-
|
|
5225
|
+
if (start >= 0) await writer.text(text.slice(start));
|
|
5108
5226
|
}
|
|
5109
|
-
if (start >= 0) pieces.push(text.slice(start));
|
|
5110
|
-
bytes2 = encoder2.encode(pieces.join(outputDelimiter ?? ""));
|
|
5111
5227
|
}
|
|
5112
|
-
await
|
|
5113
|
-
await
|
|
5228
|
+
await writer.write(Uint8Array.of(recordDelimiter));
|
|
5229
|
+
await writer.flush();
|
|
5114
5230
|
}
|
|
5115
5231
|
} catch (error) {
|
|
5116
5232
|
await diagnostic(context, error);
|
|
@@ -5169,6 +5285,33 @@ var ShellLimitError = class extends Error {
|
|
|
5169
5285
|
limit;
|
|
5170
5286
|
};
|
|
5171
5287
|
|
|
5288
|
+
// packages/safe-bash/src/shell/parse-budget.ts
|
|
5289
|
+
init_platform();
|
|
5290
|
+
var defaultMaxParseUnits = 262144;
|
|
5291
|
+
var ParseBudget = class {
|
|
5292
|
+
constructor(maximum = defaultMaxParseUnits, signal, onLimit) {
|
|
5293
|
+
this.signal = signal;
|
|
5294
|
+
this.onLimit = onLimit;
|
|
5295
|
+
if (!Number.isSafeInteger(maximum) || maximum < 0) throw new RangeError("maxParseUnits must be a nonnegative safe integer");
|
|
5296
|
+
this.#remaining = maximum;
|
|
5297
|
+
}
|
|
5298
|
+
signal;
|
|
5299
|
+
onLimit;
|
|
5300
|
+
#remaining;
|
|
5301
|
+
#failure;
|
|
5302
|
+
admit(units = 1) {
|
|
5303
|
+
this.signal?.throwIfAborted();
|
|
5304
|
+
if (this.#failure) throw this.#failure;
|
|
5305
|
+
if (!Number.isSafeInteger(units) || units < 0) throw new RangeError("Parse admission must be a nonnegative safe integer");
|
|
5306
|
+
if (units > this.#remaining) {
|
|
5307
|
+
const error = this.#failure = new ShellLimitError("maxParseUnits");
|
|
5308
|
+
this.onLimit?.(error);
|
|
5309
|
+
throw error;
|
|
5310
|
+
}
|
|
5311
|
+
this.#remaining -= units;
|
|
5312
|
+
}
|
|
5313
|
+
};
|
|
5314
|
+
|
|
5172
5315
|
// packages/safe-bash/src/shell/arithmetic.ts
|
|
5173
5316
|
init_platform();
|
|
5174
5317
|
var ArithmeticFailure = class extends Error {
|
|
@@ -5178,11 +5321,13 @@ var ArithmeticFailure = class extends Error {
|
|
|
5178
5321
|
}
|
|
5179
5322
|
offset;
|
|
5180
5323
|
};
|
|
5181
|
-
function prepareArithmetic(source) {
|
|
5324
|
+
function prepareArithmetic(source, budget = new ParseBudget()) {
|
|
5325
|
+
budget.admit();
|
|
5182
5326
|
try {
|
|
5183
|
-
return { source, tree: parseArithmetic(source) };
|
|
5327
|
+
return { source, tree: parseArithmetic(source, 0, budget) };
|
|
5184
5328
|
} catch (error) {
|
|
5185
5329
|
if (!(error instanceof ShellSyntaxError) || /nesting/u.test(error.reason)) throw error;
|
|
5330
|
+
budget.admit();
|
|
5186
5331
|
return { source, error };
|
|
5187
5332
|
}
|
|
5188
5333
|
}
|
|
@@ -5237,7 +5382,7 @@ function integer2(text) {
|
|
|
5237
5382
|
}
|
|
5238
5383
|
return value2;
|
|
5239
5384
|
}
|
|
5240
|
-
function parseArithmetic(source, offset = 0) {
|
|
5385
|
+
function parseArithmetic(source, offset = 0, budget = new ParseBudget()) {
|
|
5241
5386
|
const tokens2 = [];
|
|
5242
5387
|
let position = 0;
|
|
5243
5388
|
while (position < source.length) {
|
|
@@ -5247,6 +5392,7 @@ function parseArithmetic(source, offset = 0) {
|
|
|
5247
5392
|
}
|
|
5248
5393
|
const value2 = /^(?:\d+#[\da-zA-Z@_]+|0[xX][\da-fA-F]+|\d+|[a-zA-Z_][a-zA-Z_0-9]*|<<=|>>=|\*\*|\+\+|--|&&|\|\||<<|>>|[+*/%&^|!<>=-]=|[()+*/%~!<>=&^|?:,\-])/u.exec(source.slice(position))?.[0];
|
|
5249
5394
|
if (!value2) throw new ShellSyntaxError("Unsupported arithmetic token", offset + position);
|
|
5395
|
+
budget.admit();
|
|
5250
5396
|
tokens2.push({ value: value2, offset: offset + position });
|
|
5251
5397
|
position += value2.length;
|
|
5252
5398
|
}
|
|
@@ -5263,6 +5409,7 @@ function parseArithmetic(source, offset = 0) {
|
|
|
5263
5409
|
const token = current();
|
|
5264
5410
|
cursor++;
|
|
5265
5411
|
if (["+", "-", "!", "~", "++", "--"].includes(token)) {
|
|
5412
|
+
budget.admit();
|
|
5266
5413
|
const operand = expression(15);
|
|
5267
5414
|
if (["++", "--"].includes(token) && operand.kind !== "name") error("Arithmetic assignment requires a variable");
|
|
5268
5415
|
left = { kind: "unary", operator: token, operand, postfix: false };
|
|
@@ -5270,8 +5417,11 @@ function parseArithmetic(source, offset = 0) {
|
|
|
5270
5417
|
left = expression();
|
|
5271
5418
|
if (current() !== ")") error("Unclosed arithmetic parenthesis");
|
|
5272
5419
|
cursor++;
|
|
5273
|
-
} else if (/^[a-zA-Z_]/u.test(token))
|
|
5274
|
-
|
|
5420
|
+
} else if (/^[a-zA-Z_]/u.test(token)) {
|
|
5421
|
+
budget.admit();
|
|
5422
|
+
left = { kind: "name", name: token };
|
|
5423
|
+
} else {
|
|
5424
|
+
budget.admit();
|
|
5275
5425
|
try {
|
|
5276
5426
|
left = { kind: "literal", value: BigInt.asIntN(64, integer2(token)) };
|
|
5277
5427
|
} catch {
|
|
@@ -5284,12 +5434,14 @@ function parseArithmetic(source, offset = 0) {
|
|
|
5284
5434
|
if (operator === "++" || operator === "--") {
|
|
5285
5435
|
if (left.kind !== "name") error("Arithmetic assignment requires a variable");
|
|
5286
5436
|
cursor++;
|
|
5437
|
+
budget.admit();
|
|
5287
5438
|
left = { kind: "unary", operator, operand: left, postfix: true, start };
|
|
5288
5439
|
continue;
|
|
5289
5440
|
}
|
|
5290
5441
|
const priority = Object.hasOwn(precedence, operator) ? precedence[operator] : 0;
|
|
5291
5442
|
if (priority < minimum || priority === 0) break;
|
|
5292
5443
|
cursor++;
|
|
5444
|
+
budget.admit();
|
|
5293
5445
|
if (operator === "?") {
|
|
5294
5446
|
const yes = expression();
|
|
5295
5447
|
if (current() !== ":") error("Expected arithmetic colon");
|
|
@@ -5305,7 +5457,10 @@ function parseArithmetic(source, offset = 0) {
|
|
|
5305
5457
|
depth--;
|
|
5306
5458
|
return left;
|
|
5307
5459
|
};
|
|
5308
|
-
if (!tokens2.length)
|
|
5460
|
+
if (!tokens2.length) {
|
|
5461
|
+
budget.admit();
|
|
5462
|
+
return { kind: "literal", value: 0n };
|
|
5463
|
+
}
|
|
5309
5464
|
const tree = expression();
|
|
5310
5465
|
if (cursor < tokens2.length) error("Unexpected arithmetic token");
|
|
5311
5466
|
return tree;
|
|
@@ -5322,7 +5477,7 @@ function arithmeticEnd(source, start) {
|
|
|
5322
5477
|
}
|
|
5323
5478
|
throw new ShellSyntaxError("Unterminated arithmetic expression", start);
|
|
5324
5479
|
}
|
|
5325
|
-
function evaluateArithmetic(program, variables) {
|
|
5480
|
+
function evaluateArithmetic(program, variables, budget = new ParseBudget()) {
|
|
5326
5481
|
const visiting = /* @__PURE__ */ new Set();
|
|
5327
5482
|
let steps = 0;
|
|
5328
5483
|
const binary = (operator, left, right, offset) => {
|
|
@@ -5390,7 +5545,7 @@ function evaluateArithmetic(program, variables) {
|
|
|
5390
5545
|
else if (node.kind === "name") {
|
|
5391
5546
|
if (visiting.has(node.name) || visiting.size >= 64) throw new Error("Arithmetic variable recursion");
|
|
5392
5547
|
visiting.add(node.name);
|
|
5393
|
-
pending.push({ kind: "variable", name: node.name }, { kind: "evaluate", node: parseArithmetic(variables[node.name] ?? "0") });
|
|
5548
|
+
pending.push({ kind: "variable", name: node.name }, { kind: "evaluate", node: parseArithmetic(variables[node.name] ?? "0", 0, budget) });
|
|
5394
5549
|
} else if (node.kind === "conditional") {
|
|
5395
5550
|
pending.push({ kind: "conditional", node }, { kind: "evaluate", node: node.condition });
|
|
5396
5551
|
} else if (node.kind === "unary") {
|
|
@@ -5456,21 +5611,23 @@ function setQuoteMarker(part, synthetic) {
|
|
|
5456
5611
|
function isQuoteMarker(part) {
|
|
5457
5612
|
return quoteMarkers.has(part);
|
|
5458
5613
|
}
|
|
5459
|
-
function literalIndex(source, offset) {
|
|
5614
|
+
function literalIndex(source, offset, budget = new ParseBudget()) {
|
|
5460
5615
|
let decimal = source;
|
|
5461
5616
|
if (source[0] === "'" || source[0] === '"') {
|
|
5462
5617
|
if (source.length < 2 || source.at(-1) !== source[0]) throw new ShellSyntaxError("Unsupported indexed-array subscript", offset);
|
|
5463
5618
|
decimal = source.slice(1, -1);
|
|
5464
5619
|
}
|
|
5465
5620
|
if (!/^(?:0|[1-9][0-9]*)$/u.test(decimal)) throw new ShellSyntaxError("Unsupported indexed-array subscript", offset);
|
|
5621
|
+
budget.admit();
|
|
5466
5622
|
return { decimal };
|
|
5467
5623
|
}
|
|
5468
5624
|
function numericIndex(index) {
|
|
5469
5625
|
if (index.decimal.length > 10 || index.decimal.length === 10 && index.decimal > "2147483647") return void 0;
|
|
5470
5626
|
return Number(index.decimal);
|
|
5471
5627
|
}
|
|
5472
|
-
function arraySelector(source, offset) {
|
|
5473
|
-
|
|
5628
|
+
function arraySelector(source, offset, budget = new ParseBudget()) {
|
|
5629
|
+
budget.admit();
|
|
5630
|
+
return source === "@" || source === "*" ? { kind: "members", separator: source } : { kind: "element", index: literalIndex(source, offset, budget) };
|
|
5474
5631
|
}
|
|
5475
5632
|
function setArraySelector(part, selector) {
|
|
5476
5633
|
selectors.set(part, selector);
|
|
@@ -5490,7 +5647,8 @@ function setArrayAssignment(word, assignment) {
|
|
|
5490
5647
|
function getArrayAssignment(word) {
|
|
5491
5648
|
return assignments.get(word);
|
|
5492
5649
|
}
|
|
5493
|
-
function removePrefix(word, length) {
|
|
5650
|
+
function removePrefix(word, length, budget) {
|
|
5651
|
+
budget.admit();
|
|
5494
5652
|
const parts = [];
|
|
5495
5653
|
for (const part of word.parts) {
|
|
5496
5654
|
if (length === 0) parts.push(part);
|
|
@@ -5498,6 +5656,7 @@ function removePrefix(word, length) {
|
|
|
5498
5656
|
if (part.kind !== "text") throw new ShellSyntaxError("Unsupported indexed-array subscript", word.offset);
|
|
5499
5657
|
if (part.value.length <= length) length -= part.value.length;
|
|
5500
5658
|
else {
|
|
5659
|
+
budget.admit();
|
|
5501
5660
|
parts.push({ ...part, value: part.value.slice(length) });
|
|
5502
5661
|
length = 0;
|
|
5503
5662
|
}
|
|
@@ -5506,7 +5665,7 @@ function removePrefix(word, length) {
|
|
|
5506
5665
|
if (length !== 0) throw new ShellSyntaxError("Invalid indexed-array assignment", word.offset);
|
|
5507
5666
|
return { offset: word.offset, parts };
|
|
5508
5667
|
}
|
|
5509
|
-
function elementAssignment(word) {
|
|
5668
|
+
function elementAssignment(word, budget = new ParseBudget()) {
|
|
5510
5669
|
const source = word.spelling;
|
|
5511
5670
|
const first = word.parts[0];
|
|
5512
5671
|
if (source === void 0 || first?.kind !== "text" || first.quoted) return void 0;
|
|
@@ -5517,19 +5676,23 @@ function elementAssignment(word) {
|
|
|
5517
5676
|
if (source.includes("=")) throw new ShellSyntaxError("Invalid indexed-array assignment", word.offset);
|
|
5518
5677
|
return void 0;
|
|
5519
5678
|
}
|
|
5520
|
-
|
|
5679
|
+
budget.admit();
|
|
5680
|
+
const index = literalIndex(source.slice(name2.length + 1, end), word.offset + name2.length + 1, budget);
|
|
5521
5681
|
const append = source[end + 1] === "+";
|
|
5522
|
-
const value2 = removePrefix(word, name2.length + index.decimal.length + (append ? 4 : 3));
|
|
5682
|
+
const value2 = removePrefix(word, name2.length + index.decimal.length + (append ? 4 : 3), budget);
|
|
5523
5683
|
return { kind: "element", name: name2, index, append, value: value2 };
|
|
5524
5684
|
}
|
|
5525
|
-
function compoundHead(word) {
|
|
5685
|
+
function compoundHead(word, budget = new ParseBudget()) {
|
|
5526
5686
|
if (word.parts.length !== 1) return void 0;
|
|
5527
5687
|
const first = word.parts[0];
|
|
5528
5688
|
if (first?.kind !== "text" || first.quoted) return void 0;
|
|
5529
5689
|
const match = /^([a-zA-Z_][a-zA-Z_0-9]*)(\+?)=$/u.exec(first.value);
|
|
5530
|
-
|
|
5690
|
+
if (!match) return void 0;
|
|
5691
|
+
budget.admit();
|
|
5692
|
+
return { name: match[1], append: match[2] === "+" };
|
|
5531
5693
|
}
|
|
5532
|
-
function compoundEntry(word) {
|
|
5694
|
+
function compoundEntry(word, budget = new ParseBudget()) {
|
|
5695
|
+
budget.admit();
|
|
5533
5696
|
const source = word.spelling;
|
|
5534
5697
|
const first = word.parts[0];
|
|
5535
5698
|
if (source === void 0 || source[0] !== "[" || first?.kind !== "text" || first.quoted) return { value: word };
|
|
@@ -5538,8 +5701,8 @@ function compoundEntry(word) {
|
|
|
5538
5701
|
if (source.includes("=")) throw new ShellSyntaxError("Invalid indexed-array entry", word.offset);
|
|
5539
5702
|
return { value: word };
|
|
5540
5703
|
}
|
|
5541
|
-
const index = literalIndex(source.slice(1, end), word.offset + 1);
|
|
5542
|
-
return { index, value: removePrefix(word, index.decimal.length + 3) };
|
|
5704
|
+
const index = literalIndex(source.slice(1, end), word.offset + 1, budget);
|
|
5705
|
+
return { index, value: removePrefix(word, index.decimal.length + 3, budget) };
|
|
5543
5706
|
}
|
|
5544
5707
|
function scalarAssignmentName(word) {
|
|
5545
5708
|
const first = word.parts[0];
|
|
@@ -5859,7 +6022,8 @@ var HereDocumentSyntaxError = class extends Error {
|
|
|
5859
6022
|
}
|
|
5860
6023
|
diagnostic;
|
|
5861
6024
|
};
|
|
5862
|
-
function printedSimpleLines(lists, separators) {
|
|
6025
|
+
function printedSimpleLines(lists, separators, budget) {
|
|
6026
|
+
budget.admit();
|
|
5863
6027
|
const printedLines = /* @__PURE__ */ new Map();
|
|
5864
6028
|
let line = 1;
|
|
5865
6029
|
for (let index = 0; index < lists.length; index++) {
|
|
@@ -5877,7 +6041,8 @@ function printedSimpleLines(lists, separators) {
|
|
|
5877
6041
|
var IncompleteShellInput = class extends Error {
|
|
5878
6042
|
};
|
|
5879
6043
|
var Lexer = class {
|
|
5880
|
-
constructor(source, depth, warnings = [], lineOffset = 0, byteLocale2 = false, documentLine, partial = false) {
|
|
6044
|
+
constructor(budget, source, depth, warnings = [], lineOffset = 0, byteLocale2 = false, documentLine, partial = false) {
|
|
6045
|
+
this.budget = budget;
|
|
5881
6046
|
this.source = source;
|
|
5882
6047
|
this.depth = depth;
|
|
5883
6048
|
this.warnings = warnings;
|
|
@@ -5888,6 +6053,7 @@ var Lexer = class {
|
|
|
5888
6053
|
if (depth > 64) throw new ShellSyntaxError("Syntax nesting exceeds 64", 0);
|
|
5889
6054
|
for (let offset = source.indexOf("\n"); offset !== -1; offset = source.indexOf("\n", offset + 1)) this.newlineOffsets.push(offset);
|
|
5890
6055
|
}
|
|
6056
|
+
budget;
|
|
5891
6057
|
source;
|
|
5892
6058
|
depth;
|
|
5893
6059
|
warnings;
|
|
@@ -5917,6 +6083,7 @@ var Lexer = class {
|
|
|
5917
6083
|
throw new ShellSyntaxError(message2, this.position, 2, void 0, unclosedQuote);
|
|
5918
6084
|
}
|
|
5919
6085
|
next() {
|
|
6086
|
+
this.budget.admit();
|
|
5920
6087
|
while (this.position < this.source.length) {
|
|
5921
6088
|
const current = this.source[this.position];
|
|
5922
6089
|
if (current === " " || current === " " || this.conditional && current === "\n") {
|
|
@@ -5958,6 +6125,7 @@ var Lexer = class {
|
|
|
5958
6125
|
if (this.position <= offset) this.error("Tokenizer made no progress");
|
|
5959
6126
|
let document;
|
|
5960
6127
|
if (delimiterOperator) {
|
|
6128
|
+
this.budget.admit();
|
|
5961
6129
|
document = {
|
|
5962
6130
|
delimiter: word.parts.map((part) => part.kind === "text" ? part.value : "").join(""),
|
|
5963
6131
|
quoted: word.parts.some((part) => part.quoted),
|
|
@@ -5969,6 +6137,7 @@ var Lexer = class {
|
|
|
5969
6137
|
};
|
|
5970
6138
|
this.documents.push(document);
|
|
5971
6139
|
}
|
|
6140
|
+
this.budget.admit();
|
|
5972
6141
|
return { kind: "word", value: word.plain ?? "", offset, end: this.position, word: { ...word, spelling: this.source.slice(offset, this.position) }, ...document ? { document } : {} };
|
|
5973
6142
|
}
|
|
5974
6143
|
readDocuments() {
|
|
@@ -6003,10 +6172,12 @@ var Lexer = class {
|
|
|
6003
6172
|
const current = this.source[this.position];
|
|
6004
6173
|
if (current === "$" || current === "`") {
|
|
6005
6174
|
if (text) {
|
|
6175
|
+
this.budget.admit(2);
|
|
6006
6176
|
yield { offset: 0, parts: [{ kind: "text", value: text, quoted: true }] };
|
|
6007
6177
|
text = "";
|
|
6008
6178
|
}
|
|
6009
6179
|
const offset = this.position;
|
|
6180
|
+
this.budget.admit();
|
|
6010
6181
|
const parts = [];
|
|
6011
6182
|
try {
|
|
6012
6183
|
this.expansion(parts, true);
|
|
@@ -6030,11 +6201,15 @@ var Lexer = class {
|
|
|
6030
6201
|
this.position++;
|
|
6031
6202
|
}
|
|
6032
6203
|
if (text.length >= 1024) {
|
|
6204
|
+
this.budget.admit(2);
|
|
6033
6205
|
yield { offset: 0, parts: [{ kind: "text", value: text, quoted: true }] };
|
|
6034
6206
|
text = "";
|
|
6035
6207
|
}
|
|
6036
6208
|
}
|
|
6037
|
-
if (text)
|
|
6209
|
+
if (text) {
|
|
6210
|
+
this.budget.admit(2);
|
|
6211
|
+
yield { offset: 0, parts: [{ kind: "text", value: text, quoted: true }] };
|
|
6212
|
+
}
|
|
6038
6213
|
}
|
|
6039
6214
|
documentSubstitutionError(source, error, backtick = false) {
|
|
6040
6215
|
const line = this.documentLine + source.slice(0, error.offset).split("\n").length;
|
|
@@ -6081,6 +6256,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6081
6256
|
this.error("Unterminated delimiter expansion syntax");
|
|
6082
6257
|
}
|
|
6083
6258
|
word(terminator, enclosingQuoted = false, literal = false, arithmetic = false) {
|
|
6259
|
+
this.budget.admit();
|
|
6084
6260
|
const offset = this.position;
|
|
6085
6261
|
const reduction = this.printedNewlineReduction;
|
|
6086
6262
|
const unprinted = this.unprintedWords;
|
|
@@ -6105,6 +6281,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6105
6281
|
previous.value += value2;
|
|
6106
6282
|
if (!synthetic) setQuoteMarker(previous, false);
|
|
6107
6283
|
} else {
|
|
6284
|
+
this.budget.admit();
|
|
6108
6285
|
const part = { kind: "text", value: projection, quoted, ...typeof value2 === "string" ? {} : { byteValue: value2 } };
|
|
6109
6286
|
setQuoteMarker(part, synthetic);
|
|
6110
6287
|
parts.push(part);
|
|
@@ -6212,6 +6389,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6212
6389
|
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(raw);
|
|
6213
6390
|
} catch (error) {
|
|
6214
6391
|
if (!(error instanceof TypeError) || !("code" in error) || error.code !== "ERR_ENCODING_INVALID_ENCODED_DATA") throw error;
|
|
6392
|
+
this.budget.admit(2);
|
|
6215
6393
|
return shellValueFromBytes(raw);
|
|
6216
6394
|
}
|
|
6217
6395
|
}
|
|
@@ -6254,6 +6432,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6254
6432
|
this.error("Unterminated ANSI-C quote", { quote: "'", line: quoteLine });
|
|
6255
6433
|
}
|
|
6256
6434
|
expansion(parts, quoted) {
|
|
6435
|
+
this.budget.admit();
|
|
6257
6436
|
const line = this.documentLine ?? this.lineAt(this.position);
|
|
6258
6437
|
if (this.source[this.position] === "`") {
|
|
6259
6438
|
this.position++;
|
|
@@ -6265,9 +6444,10 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6265
6444
|
if (this.source[this.position] !== "`") this.error("Unterminated command substitution");
|
|
6266
6445
|
this.position++;
|
|
6267
6446
|
try {
|
|
6268
|
-
parts.push({ kind: "substitution", script: parseSource(source, this.depth + 1, this.warnings, line - 1, this.byteLocale), line, quoted });
|
|
6447
|
+
parts.push({ kind: "substitution", script: parseSource(source, this.depth + 1, this.warnings, line - 1, this.byteLocale, this.budget), line, quoted });
|
|
6269
6448
|
} catch (error) {
|
|
6270
6449
|
if (this.documentLine === void 0 || !(error instanceof ShellSyntaxError) || /nesting|exceeds/u.test(error.reason)) throw error;
|
|
6450
|
+
this.budget.admit();
|
|
6271
6451
|
parts.push({ kind: "failed-substitution", diagnostic: this.documentSubstitutionError(source, error, true).diagnostic, quoted });
|
|
6272
6452
|
}
|
|
6273
6453
|
return;
|
|
@@ -6278,14 +6458,15 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6278
6458
|
const start = this.position + 2;
|
|
6279
6459
|
const end = arithmeticEnd(this.source, start);
|
|
6280
6460
|
const source = this.source.slice(start, end);
|
|
6281
|
-
parts.push({ kind: "arithmetic", expression: prepareArithmetic(source), source, line, quoted });
|
|
6461
|
+
parts.push({ kind: "arithmetic", expression: prepareArithmetic(source, this.budget), source, line, quoted });
|
|
6282
6462
|
this.position = end + 2;
|
|
6283
6463
|
} else if (this.source[this.position] === "(") {
|
|
6284
6464
|
const start = this.position + 1;
|
|
6285
6465
|
let nested;
|
|
6286
6466
|
let script;
|
|
6287
6467
|
try {
|
|
6288
|
-
|
|
6468
|
+
this.budget.admit();
|
|
6469
|
+
nested = new Parser(this.budget, this.source.slice(start), this.depth + 1, this.warnings, this.lineAt(start) - 1, void 0, this.byteLocale);
|
|
6289
6470
|
script = nested.script(/* @__PURE__ */ new Set([")"]));
|
|
6290
6471
|
} catch (error) {
|
|
6291
6472
|
if (this.documentLine !== void 0 && error instanceof ShellSyntaxError && !/nesting|exceeds/u.test(error.reason)) throw this.documentSubstitutionError(this.source.slice(start), error);
|
|
@@ -6315,7 +6496,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6315
6496
|
const start = ++this.position;
|
|
6316
6497
|
const end = this.source.indexOf("]", start);
|
|
6317
6498
|
if (end < 0) this.error("Unterminated indexed-array subscript");
|
|
6318
|
-
selector = arraySelector(this.source.slice(start, end), start);
|
|
6499
|
+
selector = arraySelector(this.source.slice(start, end), start, this.budget);
|
|
6319
6500
|
this.position = end + 1;
|
|
6320
6501
|
if (this.source[this.position] !== "}") this.error("Unsupported indexed-array operator");
|
|
6321
6502
|
}
|
|
@@ -6340,6 +6521,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6340
6521
|
this.position++;
|
|
6341
6522
|
substringLength = this.word("}", true, false, true);
|
|
6342
6523
|
}
|
|
6524
|
+
this.budget.admit();
|
|
6343
6525
|
substring = { offset, ...substringLength ? { length: substringLength } : {}, source: this.source.slice(parameterStart, this.position + 1) };
|
|
6344
6526
|
}
|
|
6345
6527
|
if (this.source[this.position] !== "}") this.error("Unterminated or unsupported parameter expansion");
|
|
@@ -6357,17 +6539,20 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6357
6539
|
}
|
|
6358
6540
|
};
|
|
6359
6541
|
var Parser = class {
|
|
6542
|
+
constructor(budget, source, depth, warnings = [], lineOffset = 0, position, byteLocale2 = false, partial = false) {
|
|
6543
|
+
this.budget = budget;
|
|
6544
|
+
if (position === void 0 && depth === 0 && source.includes("\0")) throw new ShellSyntaxError("NUL bytes are not valid shell source", source.indexOf("\0"));
|
|
6545
|
+
budget.admit();
|
|
6546
|
+
this.lexer = new Lexer(budget, source, depth, warnings, lineOffset, byteLocale2, void 0, partial);
|
|
6547
|
+
this.lexer.position = position ?? 0;
|
|
6548
|
+
this.current = this.lexer.next();
|
|
6549
|
+
}
|
|
6550
|
+
budget;
|
|
6360
6551
|
lexer;
|
|
6361
6552
|
current;
|
|
6362
6553
|
lookahead;
|
|
6363
6554
|
nesting = 0;
|
|
6364
6555
|
openCommands = [];
|
|
6365
|
-
constructor(source, depth, warnings = [], lineOffset = 0, position, byteLocale2 = false, partial = false) {
|
|
6366
|
-
if (position === void 0 && depth === 0 && source.includes("\0")) throw new ShellSyntaxError("NUL bytes are not valid shell source", source.indexOf("\0"));
|
|
6367
|
-
this.lexer = new Lexer(source, depth, warnings, lineOffset, byteLocale2, void 0, partial);
|
|
6368
|
-
this.lexer.position = position ?? 0;
|
|
6369
|
-
this.current = this.lexer.next();
|
|
6370
|
-
}
|
|
6371
6556
|
error(message2) {
|
|
6372
6557
|
const command2 = this.current.kind === "end" ? this.openCommands.findLast((command3) => ["{", "if", "while", "until", "for", "case"].includes(command3.name)) : void 0;
|
|
6373
6558
|
throw new ShellSyntaxError(message2, this.current.offset, 2, command2);
|
|
@@ -6395,11 +6580,13 @@ var Parser = class {
|
|
|
6395
6580
|
while (this.is("\n")) this.advance();
|
|
6396
6581
|
}
|
|
6397
6582
|
script(stops = /* @__PURE__ */ new Set(), inputUnit = false) {
|
|
6583
|
+
this.budget.admit();
|
|
6398
6584
|
const lists = [];
|
|
6399
6585
|
const separators = [];
|
|
6400
6586
|
this.newlines();
|
|
6401
6587
|
const line = this.lexer.lineAt(this.current.offset);
|
|
6402
6588
|
while (this.current.kind !== "end" && !stops.has(this.current.value)) {
|
|
6589
|
+
this.budget.admit();
|
|
6403
6590
|
const pipelines = [this.pipeline()];
|
|
6404
6591
|
const operators = [];
|
|
6405
6592
|
while (this.is("&&") || this.is("||")) {
|
|
@@ -6416,21 +6603,25 @@ var Parser = class {
|
|
|
6416
6603
|
this.newlines();
|
|
6417
6604
|
} else if (!this.isEnd() && !this.is(")") && !(stops.has(this.current.value) && [";;", ";&", ";;&"].includes(this.current.value))) this.error("Expected command separator");
|
|
6418
6605
|
}
|
|
6419
|
-
const printed = printedSimpleLines(lists, separators);
|
|
6606
|
+
const printed = printedSimpleLines(lists, separators, this.budget);
|
|
6420
6607
|
return { lists, line, ...printed };
|
|
6421
6608
|
}
|
|
6422
6609
|
pipeline() {
|
|
6610
|
+
this.budget.admit();
|
|
6423
6611
|
const negate = this.is("!");
|
|
6424
6612
|
if (negate) this.advance();
|
|
6425
6613
|
const commands = [this.command()];
|
|
6426
6614
|
while (this.is("|") || this.is("|&")) {
|
|
6427
6615
|
const operator = this.advance();
|
|
6428
|
-
if (operator.value === "|&")
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6616
|
+
if (operator.value === "|&") {
|
|
6617
|
+
this.budget.admit(3);
|
|
6618
|
+
commands.at(-1).redirects.push({
|
|
6619
|
+
descriptor: 2,
|
|
6620
|
+
operator: ">&",
|
|
6621
|
+
implicitPipeline: true,
|
|
6622
|
+
target: { offset: operator.offset, plain: "1", spelling: "1", parts: [{ kind: "text", value: "1", quoted: false }] }
|
|
6623
|
+
});
|
|
6624
|
+
}
|
|
6434
6625
|
this.newlines();
|
|
6435
6626
|
commands.push(this.command());
|
|
6436
6627
|
}
|
|
@@ -6438,10 +6629,12 @@ var Parser = class {
|
|
|
6438
6629
|
}
|
|
6439
6630
|
command() {
|
|
6440
6631
|
if (++this.nesting + this.lexer.depth > 64) this.error("Syntax nesting exceeds 64");
|
|
6632
|
+
this.budget.admit();
|
|
6441
6633
|
const line = this.lexer.lineAt(this.current.offset);
|
|
6442
6634
|
this.openCommands.push({ name: this.current.value, line });
|
|
6443
6635
|
try {
|
|
6444
6636
|
const command2 = this.commandInner();
|
|
6637
|
+
this.budget.admit();
|
|
6445
6638
|
return { ...command2, line: command2.line ?? line };
|
|
6446
6639
|
} finally {
|
|
6447
6640
|
this.nesting--;
|
|
@@ -6449,6 +6642,7 @@ var Parser = class {
|
|
|
6449
6642
|
}
|
|
6450
6643
|
}
|
|
6451
6644
|
commandInner() {
|
|
6645
|
+
this.budget.admit();
|
|
6452
6646
|
let command2;
|
|
6453
6647
|
if (this.is("[[")) {
|
|
6454
6648
|
const start = this.current.end;
|
|
@@ -6470,7 +6664,9 @@ var Parser = class {
|
|
|
6470
6664
|
admit2();
|
|
6471
6665
|
this.advance();
|
|
6472
6666
|
const expression2 = primary(depth + 1);
|
|
6473
|
-
|
|
6667
|
+
if (expression2.kind === "not" && !grouped.has(expression2)) return expression2.operand;
|
|
6668
|
+
this.budget.admit();
|
|
6669
|
+
return { kind: "not", operand: expression2 };
|
|
6474
6670
|
}
|
|
6475
6671
|
if (this.is("(")) {
|
|
6476
6672
|
this.advance();
|
|
@@ -6480,6 +6676,7 @@ var Parser = class {
|
|
|
6480
6676
|
return expression2;
|
|
6481
6677
|
}
|
|
6482
6678
|
admit2();
|
|
6679
|
+
this.budget.admit();
|
|
6483
6680
|
const first = operand();
|
|
6484
6681
|
if (conditionalBinary.has(this.current.value)) {
|
|
6485
6682
|
const operator = this.current.value;
|
|
@@ -6495,6 +6692,7 @@ var Parser = class {
|
|
|
6495
6692
|
let expression2 = primary(depth);
|
|
6496
6693
|
while (this.is("&&")) {
|
|
6497
6694
|
admit2();
|
|
6695
|
+
this.budget.admit();
|
|
6498
6696
|
this.advance();
|
|
6499
6697
|
expression2 = { kind: "and", left: expression2, right: primary(depth) };
|
|
6500
6698
|
}
|
|
@@ -6504,6 +6702,7 @@ var Parser = class {
|
|
|
6504
6702
|
let expression2 = conjunction(depth);
|
|
6505
6703
|
while (this.is("||")) {
|
|
6506
6704
|
admit2();
|
|
6705
|
+
this.budget.admit();
|
|
6507
6706
|
this.advance();
|
|
6508
6707
|
expression2 = { kind: "or", left: expression2, right: conjunction(depth) };
|
|
6509
6708
|
}
|
|
@@ -6519,7 +6718,7 @@ var Parser = class {
|
|
|
6519
6718
|
const start = this.current.offset + 2;
|
|
6520
6719
|
const end = arithmeticEnd(this.lexer.source, start);
|
|
6521
6720
|
const source = this.lexer.source.slice(start, end);
|
|
6522
|
-
command2 = { kind: "arithmetic", expression: prepareArithmetic(source), source, redirects: [] };
|
|
6721
|
+
command2 = { kind: "arithmetic", expression: prepareArithmetic(source, this.budget), source, redirects: [] };
|
|
6523
6722
|
this.lexer.position = end + 2;
|
|
6524
6723
|
this.lookahead = void 0;
|
|
6525
6724
|
this.current = this.lexer.next();
|
|
@@ -6537,6 +6736,7 @@ var Parser = class {
|
|
|
6537
6736
|
const condition = this.nonemptyScript(/* @__PURE__ */ new Set(["then"]));
|
|
6538
6737
|
this.expect("then");
|
|
6539
6738
|
const body = this.nonemptyScript(/* @__PURE__ */ new Set(["elif", "else", "fi"]));
|
|
6739
|
+
this.budget.admit();
|
|
6540
6740
|
branches.push({ condition, body });
|
|
6541
6741
|
if (!this.is("elif")) break;
|
|
6542
6742
|
this.advance();
|
|
@@ -6569,6 +6769,7 @@ var Parser = class {
|
|
|
6569
6769
|
const body = this.script(/* @__PURE__ */ new Set([";;", ";&", ";;&", "esac"]));
|
|
6570
6770
|
if (![";;", ";&", ";;&", "esac"].includes(this.current.value)) this.error("Expected case terminator");
|
|
6571
6771
|
const terminator = this.current.value;
|
|
6772
|
+
this.budget.admit();
|
|
6572
6773
|
clauses.push({ patterns: patterns2, body, terminator });
|
|
6573
6774
|
if (terminator === "esac") break;
|
|
6574
6775
|
this.advance();
|
|
@@ -6612,7 +6813,7 @@ var Parser = class {
|
|
|
6612
6813
|
this.newlines();
|
|
6613
6814
|
if (!this.is("{")) this.error("Expected brace function body");
|
|
6614
6815
|
command2 = { kind: "function", name: name2, body: this.command(), redirects: [] };
|
|
6615
|
-
} else if (this.current.kind === "word" && !compoundHead(this.current.word) && this.peek().value === "(") {
|
|
6816
|
+
} else if (this.current.kind === "word" && !compoundHead(this.current.word, this.budget) && this.peek().value === "(") {
|
|
6616
6817
|
const name2 = this.advance().value;
|
|
6617
6818
|
if (!/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name2)) this.error("Invalid function name");
|
|
6618
6819
|
this.expect("(");
|
|
@@ -6634,21 +6835,22 @@ var Parser = class {
|
|
|
6634
6835
|
} else if (this.current.kind === "word") {
|
|
6635
6836
|
line ??= wordLine;
|
|
6636
6837
|
let word = this.advance().word;
|
|
6637
|
-
const head = compoundHead(word);
|
|
6838
|
+
const head = compoundHead(word, this.budget);
|
|
6638
6839
|
if (head && this.is("(")) {
|
|
6639
6840
|
this.advance();
|
|
6640
6841
|
const entries = [];
|
|
6641
6842
|
this.newlines();
|
|
6642
6843
|
while (this.current.kind === "word") {
|
|
6643
|
-
entries.push(compoundEntry(this.advance().word));
|
|
6844
|
+
entries.push(compoundEntry(this.advance().word, this.budget));
|
|
6644
6845
|
this.newlines();
|
|
6645
6846
|
}
|
|
6646
6847
|
if (!this.is(")")) this.error("Unsupported indexed-array compound assignment");
|
|
6647
6848
|
const end = this.advance().end;
|
|
6849
|
+
this.budget.admit(2);
|
|
6648
6850
|
word = { offset: word.offset, parts: word.parts, spelling: this.lexer.source.slice(word.offset, end) };
|
|
6649
6851
|
setArrayAssignment(word, { kind: "compound", ...head, entries });
|
|
6650
6852
|
} else {
|
|
6651
|
-
const assignment = words.every((previous) => getArrayAssignment(previous) || scalarAssignmentName(previous)) ? elementAssignment(word) : void 0;
|
|
6853
|
+
const assignment = words.every((previous) => getArrayAssignment(previous) || scalarAssignmentName(previous)) ? elementAssignment(word, this.budget) : void 0;
|
|
6652
6854
|
if (assignment) setArrayAssignment(word, assignment);
|
|
6653
6855
|
}
|
|
6654
6856
|
words.push(word);
|
|
@@ -6674,6 +6876,7 @@ var Parser = class {
|
|
|
6674
6876
|
if (/^(?:>|>>|<|<<|<<-|<<<|>&|<&|>\|)$/u.test(next.value) && this.current.end === next.offset) descriptor2 = Number(this.advance().value);
|
|
6675
6877
|
}
|
|
6676
6878
|
if (!/^(?:>|>>|<|<<|<<-|<<<|>&|<&|>\||&>)$/u.test(this.current.value) || this.current.kind !== "operator") return void 0;
|
|
6879
|
+
this.budget.admit();
|
|
6677
6880
|
const operator = this.advance().value;
|
|
6678
6881
|
descriptor2 ??= operator.startsWith("<") ? 0 : 1;
|
|
6679
6882
|
if (!Number.isSafeInteger(descriptor2) || descriptor2 > 255) this.error("File descriptor must be between 0 and 255");
|
|
@@ -6682,29 +6885,40 @@ var Parser = class {
|
|
|
6682
6885
|
return { descriptor: descriptor2, operator, target: target.word, line: this.lexer.lineAt(Math.max(target.offset, target.end - 1)), ...(operator === "<&" || operator === ">&") && this.lexer.source[target.end - 1] === "-" ? { move: true } : {}, ...target.document ? { document: target.document } : {} };
|
|
6683
6886
|
}
|
|
6684
6887
|
};
|
|
6685
|
-
function parseShell(source, depth = 0) {
|
|
6888
|
+
function parseShell(source, depth = 0, options3 = {}) {
|
|
6889
|
+
const budget = new ParseBudget(options3.maxParseUnits);
|
|
6686
6890
|
const warnings = [];
|
|
6687
|
-
const script = parseSource(source, depth, warnings);
|
|
6891
|
+
const script = parseSource(source, depth, warnings, 0, false, budget);
|
|
6892
|
+
budget.admit();
|
|
6688
6893
|
return { ...script, ...warnings.length ? { warnings } : {} };
|
|
6689
6894
|
}
|
|
6690
|
-
function* hereDocumentWords(document, line, byteLocale2, warnings) {
|
|
6691
|
-
if (document.quoted)
|
|
6692
|
-
|
|
6895
|
+
function* hereDocumentWords(document, line, byteLocale2, warnings, budget = new ParseBudget()) {
|
|
6896
|
+
if (document.quoted) {
|
|
6897
|
+
budget.admit(2);
|
|
6898
|
+
yield { offset: document.offset, parts: [{ kind: "text", value: document.body, quoted: true }] };
|
|
6899
|
+
} else {
|
|
6900
|
+
budget.admit();
|
|
6901
|
+
yield* new Lexer(budget, document.body, document.depth, warnings, line - 1, byteLocale2, line).documentWords();
|
|
6902
|
+
}
|
|
6693
6903
|
}
|
|
6694
|
-
function parseShellUnit(source, position = 0, byteLocale2 = false) {
|
|
6904
|
+
function parseShellUnit(source, position = 0, byteLocale2 = false, budget = new ParseBudget()) {
|
|
6695
6905
|
const warnings = [];
|
|
6696
|
-
|
|
6906
|
+
budget.admit();
|
|
6907
|
+
const parser = new Parser(budget, source, 0, warnings, 0, position, byteLocale2);
|
|
6697
6908
|
const script = parser.script(/* @__PURE__ */ new Set(), true);
|
|
6698
6909
|
const next = parser.current.end;
|
|
6699
6910
|
const nul = source.indexOf("\0", position);
|
|
6700
6911
|
if (nul >= 0 && nul < next) throw new ShellSyntaxError("NUL bytes are not valid shell source", nul);
|
|
6912
|
+
budget.admit(2);
|
|
6701
6913
|
return { script: { ...script, ...warnings.length ? { warnings } : {} }, next };
|
|
6702
6914
|
}
|
|
6703
|
-
function parseShellInputUnit(source, byteLocale2 = false) {
|
|
6915
|
+
function parseShellInputUnit(source, byteLocale2 = false, budget = new ParseBudget()) {
|
|
6704
6916
|
const warnings = [];
|
|
6705
6917
|
try {
|
|
6706
|
-
|
|
6918
|
+
budget.admit();
|
|
6919
|
+
const parser = new Parser(budget, source, 0, warnings, 0, 0, byteLocale2, true);
|
|
6707
6920
|
const script = parser.script(/* @__PURE__ */ new Set(), true);
|
|
6921
|
+
budget.admit(2);
|
|
6708
6922
|
return { script: { ...script, ...warnings.length ? { warnings } : {} }, next: parser.current.end };
|
|
6709
6923
|
} catch (error) {
|
|
6710
6924
|
if (error instanceof IncompleteShellInput) return void 0;
|
|
@@ -6712,8 +6926,9 @@ function parseShellInputUnit(source, byteLocale2 = false) {
|
|
|
6712
6926
|
throw error;
|
|
6713
6927
|
}
|
|
6714
6928
|
}
|
|
6715
|
-
function parseSource(source, depth, warnings, lineOffset
|
|
6716
|
-
|
|
6929
|
+
function parseSource(source, depth, warnings, lineOffset, byteLocale2, budget) {
|
|
6930
|
+
budget.admit();
|
|
6931
|
+
const parser = new Parser(budget, source, depth, warnings, lineOffset, void 0, byteLocale2);
|
|
6717
6932
|
const script = parser.script();
|
|
6718
6933
|
if (parser.current.kind !== "end") parser.error("Unexpected token");
|
|
6719
6934
|
return script;
|
|
@@ -7118,7 +7333,7 @@ function evaluatePositionalArithmetic(program, options3, evaluate) {
|
|
|
7118
7333
|
reserve(bytes2);
|
|
7119
7334
|
const expanded = chunks.join("");
|
|
7120
7335
|
options3.checkpoint();
|
|
7121
|
-
return evaluate(prepareArithmetic(expanded));
|
|
7336
|
+
return evaluate(prepareArithmetic(expanded, options3.parseBudget));
|
|
7122
7337
|
} finally {
|
|
7123
7338
|
for (let position = admissions.length - 1; position >= 0; position--) admissions[position].release();
|
|
7124
7339
|
header?.release();
|
|
@@ -10080,6 +10295,7 @@ async function matchEre(program, subject, ledger, signal) {
|
|
|
10080
10295
|
|
|
10081
10296
|
// packages/safe-bash/src/shell/runtime.ts
|
|
10082
10297
|
var defaultLimits = {
|
|
10298
|
+
maxParseUnits: defaultMaxParseUnits,
|
|
10083
10299
|
maxInputBytes: 32 * 1024 * 1024,
|
|
10084
10300
|
maxOutputBytes: 16 * 1024 * 1024,
|
|
10085
10301
|
maxCommands: 1e4,
|
|
@@ -10170,11 +10386,13 @@ var Budget = class {
|
|
|
10170
10386
|
constructor(limits, signal) {
|
|
10171
10387
|
this.limits = limits;
|
|
10172
10388
|
this.signal = signal ? AbortSignal.any([signal, this.controller.signal]) : this.controller.signal;
|
|
10389
|
+
this.parsing = new ParseBudget(limits.maxParseUnits, this.signal, (error) => this.controller.abort(error));
|
|
10173
10390
|
this.values = new ValueArena(limits.maxExpansionBytes, limits.maxExpansionFields, () => this.signal.throwIfAborted(), (limit) => this.fail(limit));
|
|
10174
10391
|
this.#wallClockDeadline = Date.now() + limits.maxWallClockMs;
|
|
10175
10392
|
this.#armWallClock();
|
|
10176
10393
|
}
|
|
10177
10394
|
limits;
|
|
10395
|
+
parsing;
|
|
10178
10396
|
values;
|
|
10179
10397
|
commands = 0;
|
|
10180
10398
|
iterations = 0;
|
|
@@ -11314,7 +11532,7 @@ var Runtime = class _Runtime {
|
|
|
11314
11532
|
if (shellValueByteLength(value2) > this.budget.limits.maxExpansionBytes) this.budget.fail("maxExpansionBytes");
|
|
11315
11533
|
if (name2 === "OPTIND" && state.getopts?.integer && origin !== "arithmetic") {
|
|
11316
11534
|
try {
|
|
11317
|
-
value2 = String(evaluateArithmetic(prepareArithmetic(shellValueText(value2) || "0"), this.arithmeticVariables(state)));
|
|
11535
|
+
value2 = String(evaluateArithmetic(prepareArithmetic(shellValueText(value2) || "0", this.budget.parsing), this.arithmeticVariables(state), this.budget.parsing));
|
|
11318
11536
|
} catch (error) {
|
|
11319
11537
|
this.rethrowArithmeticControl(error);
|
|
11320
11538
|
throw new ExpansionFailure(message(error));
|
|
@@ -12020,6 +12238,7 @@ var Runtime = class _Runtime {
|
|
|
12020
12238
|
if (command2.kind === "arithmetic") {
|
|
12021
12239
|
try {
|
|
12022
12240
|
return Number(evaluatePositionalArithmetic(command2.expression, {
|
|
12241
|
+
parseBudget: this.budget.parsing,
|
|
12023
12242
|
positional: state.positional,
|
|
12024
12243
|
arg0: state.arg0 ?? "virtual-bash",
|
|
12025
12244
|
owner: arrayStore(state)?.owner,
|
|
@@ -12027,7 +12246,7 @@ var Runtime = class _Runtime {
|
|
|
12027
12246
|
checkpoint: () => this.signal.throwIfAborted(),
|
|
12028
12247
|
requireParameter: (name2, value2) => this.requireParameter(value2, name2, state, io),
|
|
12029
12248
|
limit: () => this.budget.fail("maxExpansionBytes")
|
|
12030
|
-
}, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine))) === 0n);
|
|
12249
|
+
}, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine), this.budget.parsing)) === 0n);
|
|
12031
12250
|
} catch (error) {
|
|
12032
12251
|
this.rethrowArithmeticControl(error);
|
|
12033
12252
|
throw new Error(`((: ${message(error)}`);
|
|
@@ -12181,7 +12400,7 @@ var Runtime = class _Runtime {
|
|
|
12181
12400
|
let words = 0;
|
|
12182
12401
|
const warnings = [];
|
|
12183
12402
|
try {
|
|
12184
|
-
for (const word of hereDocumentWords(document, line, byteLocale(state.variables), warnings)) {
|
|
12403
|
+
for (const word of hereDocumentWords(document, line, byteLocale(state.variables), warnings, this.budget.parsing)) {
|
|
12185
12404
|
this.signal.throwIfAborted();
|
|
12186
12405
|
for (const warning of warnings.splice(0)) await writeText(io.stderr, `shell: warning: ${warning}
|
|
12187
12406
|
`);
|
|
@@ -13073,7 +13292,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
13073
13292
|
try {
|
|
13074
13293
|
do {
|
|
13075
13294
|
this.signal.throwIfAborted();
|
|
13076
|
-
const unit = parseShellUnit(source, position, byteLocale(state.variables));
|
|
13295
|
+
const unit = parseShellUnit(source, position, byteLocale(state.variables), this.budget.parsing);
|
|
13077
13296
|
for (const warning of unit.script.warnings ?? []) await writeText(io.stderr, `${io.scriptName}: warning: ${warning}
|
|
13078
13297
|
`);
|
|
13079
13298
|
if (unit.script.lists.length) {
|
|
@@ -13110,7 +13329,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
13110
13329
|
if (bytes2) source += this.sourceText(bytes2, io.scriptName ?? "shell");
|
|
13111
13330
|
const unitIO = { ...io, diagnosticOffset: offset };
|
|
13112
13331
|
try {
|
|
13113
|
-
const unit = eof ? parseShellUnit(source, 0, byteLocale(state.variables)) : parseShellInputUnit(source, byteLocale(state.variables));
|
|
13332
|
+
const unit = eof ? parseShellUnit(source, 0, byteLocale(state.variables), this.budget.parsing) : parseShellInputUnit(source, byteLocale(state.variables), this.budget.parsing);
|
|
13114
13333
|
if (unit) {
|
|
13115
13334
|
for (const warning of unit.script.warnings ?? []) await writeText(io.stderr, `${io.scriptName}: warning: ${warning}
|
|
13116
13335
|
`);
|
|
@@ -13374,7 +13593,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
13374
13593
|
let position = 0;
|
|
13375
13594
|
do {
|
|
13376
13595
|
this.signal.throwIfAborted();
|
|
13377
|
-
const unit = parseShellUnit(source, position, byteLocale(context.env));
|
|
13596
|
+
const unit = parseShellUnit(source, position, byteLocale(context.env), this.budget.parsing);
|
|
13378
13597
|
units.push(unit.script);
|
|
13379
13598
|
position = unit.next;
|
|
13380
13599
|
} while (position < source.length);
|
|
@@ -13407,7 +13626,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
13407
13626
|
try {
|
|
13408
13627
|
do {
|
|
13409
13628
|
this.signal.throwIfAborted();
|
|
13410
|
-
const unit = parseShellUnit(source, position, byteLocale(state.variables));
|
|
13629
|
+
const unit = parseShellUnit(source, position, byteLocale(state.variables), this.budget.parsing);
|
|
13411
13630
|
for (const warning of unit.script.warnings ?? []) await writeText(io.stderr, `${io.scriptName ?? "shell"}: warning: ${warning}
|
|
13412
13631
|
`);
|
|
13413
13632
|
if (unit.script.lists.length) {
|
|
@@ -13721,7 +13940,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
13721
13940
|
for (let index = offset; index < args.length; index++) {
|
|
13722
13941
|
this.signal.throwIfAborted();
|
|
13723
13942
|
try {
|
|
13724
|
-
value2 = evaluateArithmetic(prepareArithmetic(args[index]), variables);
|
|
13943
|
+
value2 = evaluateArithmetic(prepareArithmetic(args[index], this.budget.parsing), variables, this.budget.parsing);
|
|
13725
13944
|
} catch (error) {
|
|
13726
13945
|
this.rethrowArithmeticControl(error);
|
|
13727
13946
|
throw new Error(`let: ${message(error)}`);
|
|
@@ -14386,8 +14605,10 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14386
14605
|
else {
|
|
14387
14606
|
let index;
|
|
14388
14607
|
try {
|
|
14389
|
-
index = numericIndex(literalIndex(selector, 0));
|
|
14390
|
-
} catch {
|
|
14608
|
+
index = numericIndex(literalIndex(selector, 0, this.budget.parsing));
|
|
14609
|
+
} catch (error) {
|
|
14610
|
+
this.signal.throwIfAborted();
|
|
14611
|
+
if (error instanceof ShellLimitError) throw error;
|
|
14391
14612
|
await this.diagnostic(context, "indexed array: unsupported subscript");
|
|
14392
14613
|
status = 2;
|
|
14393
14614
|
continue;
|
|
@@ -14591,6 +14812,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14591
14812
|
if (part.kind === "arithmetic") {
|
|
14592
14813
|
try {
|
|
14593
14814
|
return String(evaluatePositionalArithmetic(part.expression, {
|
|
14815
|
+
parseBudget: this.budget.parsing,
|
|
14594
14816
|
positional: state.positional,
|
|
14595
14817
|
arg0: state.arg0 ?? "virtual-bash",
|
|
14596
14818
|
owner: arrayStore(state)?.owner,
|
|
@@ -14598,7 +14820,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14598
14820
|
checkpoint: () => this.signal.throwIfAborted(),
|
|
14599
14821
|
requireParameter: (name2, value3) => this.requireParameter(value3, name2, state, io, part.line),
|
|
14600
14822
|
limit: () => this.budget.fail("maxExpansionBytes")
|
|
14601
|
-
}, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine ?? part.line))));
|
|
14823
|
+
}, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine ?? part.line), this.budget.parsing)));
|
|
14602
14824
|
} catch (error) {
|
|
14603
14825
|
this.rethrowArithmeticControl(error);
|
|
14604
14826
|
throw new ExpansionFailure(message(error), io.diagnosticLine ?? part.line);
|
|
@@ -14737,7 +14959,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14737
14959
|
}
|
|
14738
14960
|
this.signal.throwIfAborted();
|
|
14739
14961
|
try {
|
|
14740
|
-
return { value: evaluateArithmetic(prepareArithmetic(source), variables), source };
|
|
14962
|
+
return { value: evaluateArithmetic(prepareArithmetic(source, this.budget.parsing), variables, this.budget.parsing), source };
|
|
14741
14963
|
} catch (error) {
|
|
14742
14964
|
this.rethrowArithmeticControl(error);
|
|
14743
14965
|
throw new ExpansionFailure(`${part.name}: ${message(error)}`, line);
|
|
@@ -14867,29 +15089,67 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14867
15089
|
const arrayOwned = word.parts.some((part) => part.kind === "variable" && (getArraySelector(part) !== void 0 || arrayStore(state)?.get(part.name) !== void 0));
|
|
14868
15090
|
const owner = arrayOwned ? requireArrays(state).owner : void 0;
|
|
14869
15091
|
const holding = owner?.hold();
|
|
15092
|
+
const scratch = !owner && split && state.variables.IFS !== "" && word.parts.some((part) => !part.quoted && part.kind !== "text") ? this.budget.values.scope() : void 0;
|
|
14870
15093
|
try {
|
|
14871
15094
|
if (owner) await this.prepareArrayObservers(state, owner);
|
|
14872
15095
|
owner?.reserve({ metadata: 128 + word.parts.length * 32, allocatedSlots: word.parts.length + 1, work: word.parts.length + 5 });
|
|
14873
|
-
|
|
15096
|
+
scratch?.reserve(word.parts.length * 32, 0);
|
|
15097
|
+
const fields = [];
|
|
15098
|
+
const addField = () => {
|
|
15099
|
+
if (fields.length >= this.budget.limits.maxExpansionFields) this.budget.fail("maxExpansionFields");
|
|
15100
|
+
scratch?.reserve(32, 0);
|
|
15101
|
+
owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
15102
|
+
fields.push({ fragments: [], bytes: false, patterns: void 0, present: false });
|
|
15103
|
+
};
|
|
15104
|
+
addField();
|
|
14874
15105
|
let expansionBytes = 0;
|
|
14875
15106
|
const append = (value2, glob, present) => {
|
|
14876
15107
|
const text = shellValueText(value2);
|
|
14877
15108
|
const size = shellValueByteLength(value2);
|
|
14878
15109
|
if (size > this.budget.limits.maxExpansionBytes - expansionBytes) this.budget.fail("maxExpansionBytes");
|
|
14879
15110
|
expansionBytes += size;
|
|
14880
|
-
regexAppend?.(text, !glob);
|
|
14881
15111
|
const field = fields.at(-1);
|
|
14882
|
-
|
|
15112
|
+
let escapes = 0;
|
|
15113
|
+
if (!glob) {
|
|
15114
|
+
const special2 = conditionalPattern ? "\\*?[]-^()|+!@" : "\\*?[]-^";
|
|
15115
|
+
for (const character of text) if (special2.includes(character)) escapes++;
|
|
15116
|
+
}
|
|
15117
|
+
scratch?.reserve(32, 0);
|
|
15118
|
+
if (owner) owner.reserve({ payload: size + (escapes ? size + escapes : 0), metadata: 64, work: text.length + 8 });
|
|
15119
|
+
if (escapes) {
|
|
15120
|
+
scratch?.reserve((field.patterns ? 32 : 32 * (field.fragments.length + 1)) + (text.length + escapes) * 2, 0);
|
|
15121
|
+
field.patterns ??= field.fragments.map(shellValueText);
|
|
15122
|
+
field.patterns.push(text.replace(conditionalPattern ? /[\\*?[\]\-^()|+!@]/gu : /[\\*?[\]\-^]/gu, "\\$&"));
|
|
15123
|
+
} else if (field.patterns) {
|
|
15124
|
+
scratch?.reserve(32, 0);
|
|
15125
|
+
field.patterns.push(text);
|
|
15126
|
+
}
|
|
14883
15127
|
if (typeof value2 !== "string" || field.bytes) {
|
|
14884
|
-
io[valueScope]?.reserve(32 * (field.bytes ? 1 : field.fragments.length + 1), field.bytes ? 1 : field.fragments.length + 1);
|
|
15128
|
+
if (!scratch) io[valueScope]?.reserve(32 * (field.bytes ? 1 : field.fragments.length + 1), field.bytes ? 1 : field.fragments.length + 1);
|
|
14885
15129
|
if (typeof value2 !== "string") io[valueScope]?.hold(value2);
|
|
14886
15130
|
field.bytes = true;
|
|
14887
15131
|
}
|
|
15132
|
+
regexAppend?.(text, !glob);
|
|
14888
15133
|
field.fragments.push(value2);
|
|
14889
|
-
field.value += text;
|
|
14890
|
-
field.pattern += glob ? text : text.replace(conditionalPattern ? /[\\*?[\]\-^()|+!@]/gu : /[\\*?[\]\-^]/gu, "\\$&");
|
|
14891
15134
|
field.present ||= present;
|
|
14892
15135
|
};
|
|
15136
|
+
const appendSplit = async (value2) => {
|
|
15137
|
+
const separators = state.variables.IFS ?? " \n";
|
|
15138
|
+
let boundary = false;
|
|
15139
|
+
for await (const piece of this.splitValue(value2, separators, io, scratch)) {
|
|
15140
|
+
if (typeof piece === "string" && separators.includes(piece)) {
|
|
15141
|
+
if (!" \n".includes(piece)) {
|
|
15142
|
+
fields.at(-1).present = true;
|
|
15143
|
+
addField();
|
|
15144
|
+
} else if (fields.at(-1).present) boundary = true;
|
|
15145
|
+
} else {
|
|
15146
|
+
if (boundary) addField();
|
|
15147
|
+
boundary = false;
|
|
15148
|
+
append(piece, true, true);
|
|
15149
|
+
}
|
|
15150
|
+
}
|
|
15151
|
+
if (boundary) addField();
|
|
15152
|
+
};
|
|
14893
15153
|
const parts = word.parts.map((part) => ({ part, splitText: false }));
|
|
14894
15154
|
for (let index = 0; index < parts.length; index++) {
|
|
14895
15155
|
const { part, splitText } = parts[index];
|
|
@@ -14898,6 +15158,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14898
15158
|
const value2 = this.variable(state, part.name);
|
|
14899
15159
|
const missing = value2 === void 0 || part.operator.startsWith(":") && value2 === "";
|
|
14900
15160
|
if (part.operator.endsWith("+") ? !missing : missing) {
|
|
15161
|
+
scratch?.reserve(part.alternate.parts.length * 32, 0);
|
|
14901
15162
|
const alternate = part.alternate.parts.map((entry) => ({ part: copyArraySelector(entry, { ...entry, quoted: entry.quoted || part.quoted }), splitText: true }));
|
|
14902
15163
|
if (!alternate.length && part.quoted) append("", false, true);
|
|
14903
15164
|
parts.splice(index + 1, 0, ...alternate);
|
|
@@ -14908,37 +15169,10 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14908
15169
|
if (part.kind === "variable" && selector?.kind === "members" && !part.length && split && (!part.quoted || selector.separator === "@")) {
|
|
14909
15170
|
const members = await this.arrayMembers(part.name, state);
|
|
14910
15171
|
for (let position = 0; position < members.length; position++) {
|
|
14911
|
-
if (position > 0)
|
|
14912
|
-
owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
14913
|
-
fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14914
|
-
}
|
|
15172
|
+
if (position > 0) addField();
|
|
14915
15173
|
const value2 = members[position];
|
|
14916
15174
|
if (part.quoted || state.variables.IFS === "") append(value2, !part.quoted, part.quoted || value2.length > 0);
|
|
14917
|
-
else
|
|
14918
|
-
const separators = state.variables.IFS ?? " \n";
|
|
14919
|
-
let boundary = false;
|
|
14920
|
-
for (const character of value2) {
|
|
14921
|
-
if (separators.includes(character)) {
|
|
14922
|
-
if (!/[ \t\n]/u.test(character)) {
|
|
14923
|
-
fields.at(-1).present = true;
|
|
14924
|
-
owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
14925
|
-
fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14926
|
-
} else if (fields.at(-1).present) boundary = true;
|
|
14927
|
-
} else {
|
|
14928
|
-
if (boundary) {
|
|
14929
|
-
owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
14930
|
-
fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14931
|
-
}
|
|
14932
|
-
boundary = false;
|
|
14933
|
-
append(character, true, true);
|
|
14934
|
-
}
|
|
14935
|
-
await owner.ledger.checkpoint(this.signal);
|
|
14936
|
-
}
|
|
14937
|
-
if (boundary) {
|
|
14938
|
-
owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
14939
|
-
fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14940
|
-
}
|
|
14941
|
-
}
|
|
15175
|
+
else await appendSplit(value2);
|
|
14942
15176
|
}
|
|
14943
15177
|
} else if (part.kind === "text" && !splitText) {
|
|
14944
15178
|
let value2 = invokedValues.get(part) ?? part.byteValue ?? part.value;
|
|
@@ -14946,32 +15180,14 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14946
15180
|
append(value2, !part.quoted, quotedPresence || shellValueByteLength(value2) > 0);
|
|
14947
15181
|
} else if (part.kind === "variable" && part.name === "@" && part.quoted && !part.operator && split) {
|
|
14948
15182
|
for (let position = 0; position < state.positional.length; position++) {
|
|
14949
|
-
if (position > 0)
|
|
15183
|
+
if (position > 0) addField();
|
|
14950
15184
|
append(stateMonitor(state)?.positionals.get(String(position), state.positional[position]) ?? state.positional[position], false, true);
|
|
14951
15185
|
}
|
|
14952
15186
|
if (state.positional.length === 0 && word.parts.every((entry) => entry.kind === "text" && entry.value === "" || entry === part)) fields[0].present = false;
|
|
14953
15187
|
} else {
|
|
14954
15188
|
const value2 = part.kind === "text" ? part.byteValue ?? part.value : await this.valuePart(part, state, io, hereString);
|
|
14955
15189
|
if (part.quoted || !split || state.variables.IFS === "") append(value2, !part.quoted, quotedPresence || !split || shellValueByteLength(value2) > 0);
|
|
14956
|
-
else
|
|
14957
|
-
const separators = state.variables.IFS ?? " \n";
|
|
14958
|
-
let boundary = false;
|
|
14959
|
-
const pieces = this.splitValue(value2, separators, io);
|
|
14960
|
-
for (const character of pieces) {
|
|
14961
|
-
const text = shellValueText(character);
|
|
14962
|
-
if (typeof character === "string" && separators.includes(character)) {
|
|
14963
|
-
if (!/[ \t\n]/u.test(text)) {
|
|
14964
|
-
fields.at(-1).present = true;
|
|
14965
|
-
fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14966
|
-
} else if (fields.at(-1).present) boundary = true;
|
|
14967
|
-
} else {
|
|
14968
|
-
if (boundary) fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14969
|
-
boundary = false;
|
|
14970
|
-
append(character, true, true);
|
|
14971
|
-
}
|
|
14972
|
-
}
|
|
14973
|
-
if (boundary) fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14974
|
-
}
|
|
15190
|
+
else await appendSplit(value2);
|
|
14975
15191
|
}
|
|
14976
15192
|
if (fields.length > this.budget.limits.maxExpansionFields) this.budget.fail("maxExpansionFields");
|
|
14977
15193
|
if (owner) await owner.ledger.checkpoint(this.signal);
|
|
@@ -14980,14 +15196,23 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14980
15196
|
let resultBytes = 0;
|
|
14981
15197
|
for (const field of fields) {
|
|
14982
15198
|
if (!field.present && split) continue;
|
|
15199
|
+
if (scratch && field.fragments.length > 1 && !field.bytes) scratch.reserve(field.fragments.reduce((bytes2, value2) => bytes2 + shellValueText(value2).length * 2, 0), 0);
|
|
14983
15200
|
const assembled = concatShellValues(field.fragments, io[valueScope]);
|
|
14984
15201
|
const projection = shellValueText(assembled);
|
|
14985
|
-
|
|
15202
|
+
if (field.bytes && field.fragments.length > 1 && !field.patterns) {
|
|
15203
|
+
scratch?.reserve(field.fragments.length * 32, 0);
|
|
15204
|
+
field.patterns = field.fragments.map(shellValueText);
|
|
15205
|
+
}
|
|
15206
|
+
if (scratch && field.patterns && field.patterns.length > 1) scratch.reserve(field.patterns.reduce((bytes2, text) => bytes2 + text.length * 2, 0), 0);
|
|
15207
|
+
const fieldPattern = field.patterns ? field.patterns.join("") : projection;
|
|
15208
|
+
const expanded = split ? await this.glob(projection, fieldPattern, state) : [pattern ? fieldPattern : projection];
|
|
14986
15209
|
for (const text of expanded) {
|
|
15210
|
+
if (result.length >= this.budget.limits.maxExpansionFields) this.budget.fail("maxExpansionFields");
|
|
14987
15211
|
const value2 = !pattern && expanded.length === 1 && text === projection ? assembled : text;
|
|
14988
15212
|
const size = shellValueByteLength(value2);
|
|
14989
15213
|
if (size > this.budget.limits.maxExpansionBytes - resultBytes) this.budget.fail("maxExpansionBytes");
|
|
14990
15214
|
resultBytes += size;
|
|
15215
|
+
scratch?.reserve(32, 0);
|
|
14991
15216
|
owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
14992
15217
|
result.push(value2);
|
|
14993
15218
|
}
|
|
@@ -14995,17 +15220,50 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14995
15220
|
}
|
|
14996
15221
|
return result;
|
|
14997
15222
|
} finally {
|
|
15223
|
+
scratch?.close();
|
|
14998
15224
|
holding?.release();
|
|
14999
15225
|
}
|
|
15000
15226
|
}
|
|
15001
|
-
|
|
15227
|
+
splitWork;
|
|
15228
|
+
async *splitValue(value2, separators, io, scratch) {
|
|
15229
|
+
this.budget.cpuCheckpoint();
|
|
15230
|
+
const work = this.splitWork ??= { scanned: 0 };
|
|
15002
15231
|
if (typeof value2 === "string" || Array.from(separators).some((character) => character.charCodeAt(0) > 127)) {
|
|
15003
|
-
|
|
15232
|
+
const text = shellValueText(value2);
|
|
15233
|
+
const slice = (start3, end) => {
|
|
15234
|
+
if (start3 === 0 && end === text.length) return text;
|
|
15235
|
+
scratch?.reserve((end - start3) * 2, 0);
|
|
15236
|
+
return text.slice(start3, end);
|
|
15237
|
+
};
|
|
15238
|
+
let start2 = 0;
|
|
15239
|
+
for (let index = 0; index < text.length; ) {
|
|
15240
|
+
const character = String.fromCodePoint(text.codePointAt(index));
|
|
15241
|
+
const end = index + character.length;
|
|
15242
|
+
if (separators.includes(character)) {
|
|
15243
|
+
if (start2 < index) yield slice(start2, index);
|
|
15244
|
+
yield character;
|
|
15245
|
+
start2 = end;
|
|
15246
|
+
} else if (end - start2 >= 4096) {
|
|
15247
|
+
yield slice(start2, end);
|
|
15248
|
+
start2 = end;
|
|
15249
|
+
}
|
|
15250
|
+
work.scanned += character.length;
|
|
15251
|
+
index = end;
|
|
15252
|
+
if (work.scanned >= 4096) {
|
|
15253
|
+
work.scanned = 0;
|
|
15254
|
+
await yieldTurn(this.signal);
|
|
15255
|
+
}
|
|
15256
|
+
}
|
|
15257
|
+
if (start2 < text.length) yield slice(start2, text.length);
|
|
15004
15258
|
return;
|
|
15005
15259
|
}
|
|
15006
15260
|
const bytes2 = shellValueBytes(value2, io[valueScope]);
|
|
15007
15261
|
let start = 0;
|
|
15008
15262
|
for (let index = 0; index < bytes2.length; index++) {
|
|
15263
|
+
if (++work.scanned >= 4096) {
|
|
15264
|
+
work.scanned = 0;
|
|
15265
|
+
await yieldTurn(this.signal);
|
|
15266
|
+
}
|
|
15009
15267
|
const byte = bytes2[index];
|
|
15010
15268
|
if (byte > 127 || !separators.includes(String.fromCharCode(byte))) continue;
|
|
15011
15269
|
if (start < index) yield shellValueFromBytes(bytes2.subarray(start, index), io[valueScope]);
|
|
@@ -15696,7 +15954,7 @@ var Shell = class {
|
|
|
15696
15954
|
let failed = false;
|
|
15697
15955
|
try {
|
|
15698
15956
|
try {
|
|
15699
|
-
let unit = parseShellUnit(source, 0, byteLocale({ ...this.#options.env, ...options3.env }));
|
|
15957
|
+
let unit = parseShellUnit(source, 0, byteLocale({ ...this.#options.env, ...options3.env }), budget.parsing);
|
|
15700
15958
|
stdin = new ShellInput(typeof options3.stdin === "string" || options3.stdin instanceof Uint8Array ? toByteSource(options3.stdin) : options3.stdin ?? toByteSource(""), budget);
|
|
15701
15959
|
io.stdin = stdin;
|
|
15702
15960
|
await interruptible(this.#ready, budget.signal);
|
|
@@ -15753,7 +16011,7 @@ var Shell = class {
|
|
|
15753
16011
|
}
|
|
15754
16012
|
if (unit.next >= source.length) break;
|
|
15755
16013
|
budget.signal.throwIfAborted();
|
|
15756
|
-
unit = parseShellUnit(source, unit.next, byteLocale(state.variables));
|
|
16014
|
+
unit = parseShellUnit(source, unit.next, byteLocale(state.variables), budget.parsing);
|
|
15757
16015
|
}
|
|
15758
16016
|
} catch (error) {
|
|
15759
16017
|
if (!(error instanceof ShellSyntaxError)) throw error;
|
|
@@ -15830,6 +16088,7 @@ shell: -c: line ${line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
15830
16088
|
// packages/safe-bash/src/shell/worker-limits.ts
|
|
15831
16089
|
init_platform();
|
|
15832
16090
|
var cloudflareWorkerLimits = Object.freeze({
|
|
16091
|
+
maxParseUnits: 65536,
|
|
15833
16092
|
maxInputBytes: 4 * 1024 * 1024,
|
|
15834
16093
|
maxOutputBytes: 4 * 1024 * 1024,
|
|
15835
16094
|
maxCommands: 1e3,
|