poe-code 14.0.72 → 14.0.74
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/index.js +2674 -1760
- package/dist/index.js.map +4 -4
- package/dist/metafile.json +1 -1
- package/package.json +1 -1
- package/packages/safe-js/dist/chunks/{chunk-VYUHL33E.js → chunk-2J6W3YGB.js} +2 -2
- package/packages/safe-js/dist/chunks/{chunk-6UY4Z7WY.js → chunk-YEZOXLY4.js} +6279 -5426
- package/packages/safe-js/dist/chunks/chunk-YEZOXLY4.js.map +7 -0
- package/packages/safe-js/dist/cli.js +2 -2
- package/packages/safe-js/dist/core.js +1 -1
- package/packages/safe-js/dist/graph-depth.js +10 -0
- package/packages/safe-js/dist/index.js +2 -2
- package/packages/safe-js/dist/interp/cancel.js +3 -0
- package/packages/safe-js/dist/interp/globals/misc.js +2 -1
- package/packages/safe-js/dist/interp/globals/object-array.js +9 -7
- package/packages/safe-js/dist/interp/globals/object.js +5 -2
- package/packages/safe-js/dist/interp/globals/regex.js +88 -7
- package/packages/safe-js/dist/interp/interpreter.js +21 -14
- package/packages/safe-js/dist/interp/iteration.js +6 -1
- package/packages/safe-js/dist/interp/methods/regex.d.ts +7 -3
- package/packages/safe-js/dist/interp/methods/regex.js +132 -23
- package/packages/safe-js/dist/interp/methods/regexp-iterator.d.ts +8 -0
- package/packages/safe-js/dist/interp/methods/regexp-iterator.js +49 -0
- package/packages/safe-js/dist/interp/methods/string.d.ts +2 -0
- package/packages/safe-js/dist/interp/methods/string.js +460 -127
- package/packages/safe-js/dist/interp/object-model.d.ts +2 -0
- package/packages/safe-js/dist/interp/object-model.js +45 -12
- package/packages/safe-js/dist/interp/regexp-iterator.d.ts +14 -0
- package/packages/safe-js/dist/interp/regexp-iterator.js +18 -0
- package/packages/safe-js/dist/interp/regexp-properties.d.ts +3 -0
- package/packages/safe-js/dist/interp/regexp-properties.js +7 -0
- package/packages/safe-js/dist/interp/string-coercion.js +11 -7
- package/packages/safe-js/dist/interp/values.d.ts +3 -1
- package/packages/safe-js/dist/interp/values.js +111 -2
- package/packages/safe-js/dist/snapshot/dump-format.js +51 -9
- package/packages/safe-js/dist/snapshot/regexp-properties.d.ts +18 -0
- package/packages/safe-js/dist/snapshot/regexp-properties.js +58 -0
- package/packages/safe-js/dist/snapshot/replay-data.d.ts +12 -3
- package/packages/safe-js/dist/snapshot/replay-data.js +35 -1
- package/packages/safe-js/dist/snapshot/replay-inputs.js +17 -0
- package/packages/safe-js/dist/snapshot/restore.js +19 -1
- package/packages/safe-js/dist/snapshot/serialize.d.ts +12 -3
- package/packages/safe-js/dist/snapshot/serialize.js +40 -17
- package/packages/safe-js/dist/snapshot/validation.js +25 -2
- package/packages/safe-js/dist/chunks/chunk-6UY4Z7WY.js.map +0 -7
- /package/packages/safe-js/dist/chunks/{chunk-VYUHL33E.js.map → chunk-2J6W3YGB.js.map} +0 -0
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { types } from "node:util";
|
|
1
2
|
import { invokeBuiltinClosure } from "../builtin-call.js";
|
|
2
3
|
import { CompileScope } from "../regex/compile-guard.js";
|
|
3
4
|
import { normalizeLastIndex } from "../regex/engine.js";
|
|
4
5
|
import { sandboxNumber, sandboxString } from "../string-coercion.js";
|
|
5
6
|
import { retainValues } from "../resources.js";
|
|
6
|
-
import { getSandboxDataProperty } from "../object-model.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
7
|
+
import { getSandboxDataProperty, getSandboxPropertyDescriptor, hasRegexPropertyOverride } from "../object-model.js";
|
|
8
|
+
import { readPropertyDescriptor } from "../accessors.js";
|
|
9
|
+
import { createSandboxBox } from "../boxed.js";
|
|
10
|
+
import { setSandboxProperty } from "../interpreter.js";
|
|
11
|
+
import { createSandboxClosure, createSandboxRegex, deepCopyFromSandbox, isSandboxClosure, isSandboxRegex, measureSandboxData } from "../values.js";
|
|
12
|
+
import { executeRegex, getRegexMember, regexExec, regexFlagProperties, regexSearch, toMatchArray } from "./regex.js";
|
|
13
|
+
import { restoreSandboxRegExpIterator } from "../regexp-iterator.js";
|
|
10
14
|
export const stringMethodNames = new Set([
|
|
11
15
|
"at",
|
|
12
16
|
"charAt",
|
|
@@ -86,27 +90,68 @@ export function validateStringMethodArguments(_methodName, _args) { }
|
|
|
86
90
|
export function callStringMethod(value, methodName, args, budget, callClosure = async (closure, closureArgs) => await closure.call(closureArgs), parent, context) {
|
|
87
91
|
if (value === null || value === undefined)
|
|
88
92
|
throw new TypeError(`String#${methodName} requires a non-null receiver.`);
|
|
89
|
-
const fallback = () =>
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
const fallback = (coercePattern = false) => {
|
|
94
|
+
const apply = (string) => {
|
|
95
|
+
if (coercePattern && (methodName === "match" || methodName === "matchAll" || methodName === "search"))
|
|
96
|
+
return callStringPattern(string, methodName, args[0], budget, parent, context);
|
|
97
|
+
const useRegex = isSandboxRegex(args[0]) && !coercePattern;
|
|
98
|
+
if (methodName === "replace" || methodName === "replaceAll")
|
|
99
|
+
return callReplaceLikeMethod(string, methodName, args, budget, callClosure, useRegex, context);
|
|
100
|
+
if (methodName === "split")
|
|
101
|
+
return callSplit(string, args, budget, useRegex, parent, context);
|
|
102
|
+
return callStringMethodBody(string, methodName, args, budget, parent, context);
|
|
103
|
+
};
|
|
104
|
+
return typeof value === "string" ? apply(value) : Promise.resolve(sandboxString(value, budget, context)).then(apply);
|
|
105
|
+
};
|
|
92
106
|
const symbol = methodName === "match" ? Symbol.match
|
|
93
107
|
: methodName === "search" ? Symbol.search
|
|
94
|
-
: methodName === "
|
|
108
|
+
: methodName === "matchAll" ? Symbol.matchAll
|
|
109
|
+
: methodName === "replace" || methodName === "replaceAll" ? Symbol.replace
|
|
110
|
+
: methodName === "split" ? Symbol.split : undefined;
|
|
95
111
|
const pattern = args[0];
|
|
112
|
+
if (symbol !== undefined && types.isRegExp(pattern))
|
|
113
|
+
throw new TypeError(`String#${methodName} does not accept unbranded host RegExp values.`);
|
|
96
114
|
if (symbol === undefined || pattern === null || pattern === undefined)
|
|
97
115
|
return fallback();
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
116
|
+
const readProperty = (key) => context?.getProperty !== undefined
|
|
117
|
+
? context.getProperty(pattern, key)
|
|
118
|
+
: key === "flags" && isSandboxRegex(pattern) && getSandboxPropertyDescriptor(pattern, key, budget) === undefined
|
|
119
|
+
? getRegexMember(pattern, key, budget, context) : getSandboxDataProperty(pattern, key, budget);
|
|
120
|
+
const dispatch = () => {
|
|
121
|
+
const overriddenRegex = isSandboxRegex(pattern) && getSandboxPropertyDescriptor(pattern, symbol, budget) !== undefined;
|
|
122
|
+
const applyHook = (hook) => {
|
|
123
|
+
if (hook === null || hook === undefined)
|
|
124
|
+
return fallback(overriddenRegex);
|
|
125
|
+
if (!isSandboxClosure(hook))
|
|
126
|
+
throw new TypeError(`String#${methodName} symbol hook must be callable.`);
|
|
127
|
+
return invokeBuiltinClosure(hook, symbol === Symbol.split || symbol === Symbol.replace ? [value, args[1]] : [value], budget, context, pattern);
|
|
128
|
+
};
|
|
129
|
+
const hook = readProperty(symbol);
|
|
130
|
+
return hook instanceof Promise ? hook.then(applyHook) : applyHook(hook);
|
|
104
131
|
};
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
132
|
+
if ((methodName === "replaceAll" || methodName === "matchAll") &&
|
|
133
|
+
(typeof pattern === "object" || typeof pattern === "function")) {
|
|
134
|
+
const checkFlags = (flags) => {
|
|
135
|
+
const checkText = (text) => {
|
|
136
|
+
if (!text.includes("g"))
|
|
137
|
+
throw new TypeError(`String#${methodName} requires a global regex.`);
|
|
138
|
+
return dispatch();
|
|
139
|
+
};
|
|
140
|
+
const text = sandboxString(flags, budget, context);
|
|
141
|
+
return typeof text === "string" ? checkText(text) : text.then(checkText);
|
|
142
|
+
};
|
|
143
|
+
const checkMatch = (match) => {
|
|
144
|
+
if (!(match === undefined ? isSandboxRegex(pattern) : Boolean(match)))
|
|
145
|
+
return dispatch();
|
|
146
|
+
const flags = readProperty("flags");
|
|
147
|
+
return flags instanceof Promise ? flags.then(checkFlags) : checkFlags(flags);
|
|
148
|
+
};
|
|
149
|
+
const match = readProperty(Symbol.match);
|
|
150
|
+
return match instanceof Promise ? match.then(checkMatch) : checkMatch(match);
|
|
151
|
+
}
|
|
152
|
+
return dispatch();
|
|
108
153
|
}
|
|
109
|
-
function callStringMethodBody(value, methodName, args, budget,
|
|
154
|
+
function callStringMethodBody(value, methodName, args, budget, parent, context) {
|
|
110
155
|
if (methodName === "concat" && args.some(argument => argument !== null && typeof argument === "object")) {
|
|
111
156
|
return callConcat(value, args, budget, context);
|
|
112
157
|
}
|
|
@@ -147,13 +192,16 @@ function callStringMethodBody(value, methodName, args, budget, callClosure = asy
|
|
|
147
192
|
}
|
|
148
193
|
return budget.allocateString(result + value.slice(spanStart));
|
|
149
194
|
}
|
|
150
|
-
if (methodName === "replace" || methodName === "replaceAll") {
|
|
151
|
-
return callReplaceLikeMethod(value, methodName, args, budget, callClosure, context);
|
|
152
|
-
}
|
|
153
195
|
const regex = args[0];
|
|
154
|
-
if (
|
|
155
|
-
(
|
|
156
|
-
|
|
196
|
+
if (methodName === "search" && isSandboxRegex(regex) &&
|
|
197
|
+
hasRegexPropertyOverride(regex, ["exec"], budget))
|
|
198
|
+
return regexSearch(regex, value, budget, context);
|
|
199
|
+
if (methodName === "match" && isSandboxRegex(regex) &&
|
|
200
|
+
hasRegexPropertyOverride(regex, ["exec", "flags", ...Object.keys(regexFlagProperties)], budget))
|
|
201
|
+
return regexMatch(value, regex, budget, context);
|
|
202
|
+
if (isSandboxRegex(regex) && (methodName === "matchAll" ||
|
|
203
|
+
(methodName === "match" && !regex.flags.includes("g") &&
|
|
204
|
+
regex.lastIndex !== null && typeof regex.lastIndex === "object"))) {
|
|
157
205
|
return callStringRegexCursor(value, methodName, regex, budget, parent, context);
|
|
158
206
|
}
|
|
159
207
|
if ((methodName === "match" || methodName === "matchAll" || methodName === "search") && !isSandboxRegex(args[0])) {
|
|
@@ -162,9 +210,6 @@ function callStringMethodBody(value, methodName, args, budget, callClosure = asy
|
|
|
162
210
|
const operation = budget.acquireCompileOwner(false, parent?.owner);
|
|
163
211
|
const compilation = new CompileScope(operation.owner);
|
|
164
212
|
try {
|
|
165
|
-
if (methodName === "split") {
|
|
166
|
-
return callSplit(value, args, budget, compilation);
|
|
167
|
-
}
|
|
168
213
|
if (methodName === "match" || methodName === "matchAll" || methodName === "search") {
|
|
169
214
|
return callMatchLikeMethod(value, methodName, args, compilation);
|
|
170
215
|
}
|
|
@@ -280,43 +325,55 @@ async function callConcat(value, args, budget, context) {
|
|
|
280
325
|
release();
|
|
281
326
|
}
|
|
282
327
|
}
|
|
283
|
-
function callReplaceLikeMethod(value, methodName, args, budget, callClosure, context) {
|
|
328
|
+
function callReplaceLikeMethod(value, methodName, args, budget, callClosure, useRegex, context) {
|
|
284
329
|
const search = args[0];
|
|
285
330
|
const replacement = args[1];
|
|
286
|
-
|
|
331
|
+
const regexSearch = useRegex && isSandboxRegex(search);
|
|
332
|
+
if ((!regexSearch && typeof search !== "string") ||
|
|
287
333
|
(typeof replacement !== "string" && !isSandboxClosure(replacement))) {
|
|
288
|
-
|
|
334
|
+
return (async () => {
|
|
335
|
+
let normalizedSearch;
|
|
336
|
+
let normalizedReplacement;
|
|
337
|
+
const release = retainValues(budget, () => [normalizedSearch, normalizedReplacement]);
|
|
338
|
+
try {
|
|
339
|
+
normalizedSearch = regexSearch ? search : await sandboxString(search, budget, context);
|
|
340
|
+
normalizedReplacement = isSandboxClosure(replacement) ? replacement : await sandboxString(replacement, budget, context);
|
|
341
|
+
return await callReplaceLikeMethod(value, methodName, [normalizedSearch, normalizedReplacement], budget, callClosure, useRegex, context);
|
|
342
|
+
}
|
|
343
|
+
finally {
|
|
344
|
+
release();
|
|
345
|
+
}
|
|
346
|
+
})();
|
|
289
347
|
}
|
|
290
348
|
if (isSandboxRegex(search)) {
|
|
291
|
-
|
|
292
|
-
throw new TypeError("String#replaceAll requires a global regex.");
|
|
293
|
-
}
|
|
294
|
-
return replaceRegex(value, search, replacement, methodName === "replaceAll", budget, callClosure, context);
|
|
349
|
+
return replaceRegex(value, search, replacement, budget, callClosure, context);
|
|
295
350
|
}
|
|
296
351
|
if (typeof replacement === "string") {
|
|
297
352
|
return budget.allocateString(methodName === "replace"
|
|
298
353
|
? value.replace(search, replacement)
|
|
299
354
|
: value.replaceAll(search, replacement));
|
|
300
355
|
}
|
|
301
|
-
return replaceWithClosure(value, search, replacement, methodName === "replaceAll", budget, callClosure);
|
|
356
|
+
return replaceWithClosure(value, search, replacement, methodName === "replaceAll", budget, callClosure, context);
|
|
302
357
|
}
|
|
303
|
-
async function replaceRegex(value, regex, replacement,
|
|
358
|
+
async function replaceRegex(value, regex, replacement, budget, callClosure, context) {
|
|
359
|
+
if (hasRegexPropertyOverride(regex, ["exec", "flags", ...Object.keys(regexFlagProperties)], budget))
|
|
360
|
+
return regexReplace(value, regex, replacement, budget, callClosure, context);
|
|
304
361
|
if (regex.flags.includes("g"))
|
|
305
362
|
regex.lastIndex = 0;
|
|
306
363
|
const cursor = regex.lastIndex;
|
|
364
|
+
let result = "";
|
|
307
365
|
const retained = {};
|
|
308
|
-
budget.setRetainedValues(retained, () => [value, regex, cursor, replacement]);
|
|
366
|
+
budget.setRetainedValues(retained, () => [value, regex, cursor, replacement, result]);
|
|
309
367
|
try {
|
|
310
368
|
const lastIndex = await sandboxNumber(cursor, budget, context);
|
|
311
|
-
const matches = collectRegexMatches(regex, value,
|
|
312
|
-
let result = "";
|
|
369
|
+
const matches = collectRegexMatches(regex, value, regex.flags.includes("g"), undefined, lastIndex);
|
|
313
370
|
let copiedThrough = 0;
|
|
314
371
|
for (const match of matches) {
|
|
315
372
|
result += value.slice(copiedThrough, match.index);
|
|
316
373
|
result +=
|
|
317
374
|
typeof replacement === "string"
|
|
318
|
-
? expandReplacement(replacement, match.text, match.captures, value, match.index)
|
|
319
|
-
:
|
|
375
|
+
? await expandReplacement(replacement, match.text, match.captures, value, match.index, budget, context)
|
|
376
|
+
: await sandboxString(await callClosure(replacement, [match.text, ...match.captures, match.index, value]), budget, context);
|
|
320
377
|
copiedThrough = match.index + match.text.length;
|
|
321
378
|
}
|
|
322
379
|
result += value.slice(copiedThrough);
|
|
@@ -326,62 +383,201 @@ async function replaceRegex(value, regex, replacement, replaceAll, budget, callC
|
|
|
326
383
|
budget.setRetainedValues(retained, undefined);
|
|
327
384
|
}
|
|
328
385
|
}
|
|
329
|
-
function
|
|
386
|
+
function readReplacementProperty(target, key, budget, context) {
|
|
387
|
+
if (context?.getProperty !== undefined)
|
|
388
|
+
return context.getProperty(target, key);
|
|
389
|
+
const descriptor = getSandboxPropertyDescriptor(target, key, budget);
|
|
390
|
+
if (descriptor !== undefined)
|
|
391
|
+
return readPropertyDescriptor(descriptor, target, context);
|
|
392
|
+
return isSandboxRegex(target) ? getRegexMember(target, key, budget, context) : undefined;
|
|
393
|
+
}
|
|
394
|
+
export async function regexReplace(input, regex, replacementInput, budget, callClosure, context) {
|
|
395
|
+
if (regex === null || typeof regex !== "object")
|
|
396
|
+
throw new TypeError("RegExp replace requires an object receiver.");
|
|
397
|
+
let value;
|
|
398
|
+
let replacement;
|
|
399
|
+
const matches = [];
|
|
400
|
+
let current;
|
|
401
|
+
let field;
|
|
402
|
+
let groups;
|
|
403
|
+
let matched;
|
|
404
|
+
let captures = [];
|
|
330
405
|
let result = "";
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
else if (token === "&") {
|
|
342
|
-
result += match;
|
|
406
|
+
let flags;
|
|
407
|
+
const release = retainValues(budget, () => [input, value, regex, replacementInput, replacement, matches, current, field, groups, matched, captures, result, flags]);
|
|
408
|
+
try {
|
|
409
|
+
value = await sandboxString(input, budget, context);
|
|
410
|
+
input = undefined;
|
|
411
|
+
replacement = isSandboxClosure(replacementInput) ? replacementInput : await sandboxString(replacementInput, budget, context);
|
|
412
|
+
replacementInput = undefined;
|
|
413
|
+
if (isSandboxRegex(regex) && !hasRegexPropertyOverride(regex, ["exec", "flags", ...Object.keys(regexFlagProperties)], budget)) {
|
|
414
|
+
release();
|
|
415
|
+
return await replaceRegex(value, regex, replacement, budget, callClosure, context);
|
|
343
416
|
}
|
|
344
|
-
|
|
345
|
-
|
|
417
|
+
field = await readReplacementProperty(regex, "flags", budget, context);
|
|
418
|
+
flags = await sandboxString(field, budget, context);
|
|
419
|
+
field = undefined;
|
|
420
|
+
const global = flags.includes("g");
|
|
421
|
+
const fullUnicode = flags.includes("u") || flags.includes("v");
|
|
422
|
+
flags = undefined;
|
|
423
|
+
if (global)
|
|
424
|
+
await setSandboxProperty(regex, "lastIndex", 0, budget, true, context);
|
|
425
|
+
while (true) {
|
|
426
|
+
budget.visitNode();
|
|
427
|
+
current = await regexExec(regex, value, budget, context);
|
|
428
|
+
if (current === null)
|
|
429
|
+
break;
|
|
430
|
+
budget.allocateArrayLength(matches.length + 1);
|
|
431
|
+
matches.push(current);
|
|
432
|
+
if (!global)
|
|
433
|
+
break;
|
|
434
|
+
field = await readReplacementProperty(current, "0", budget, context);
|
|
435
|
+
matched = await sandboxString(field, budget, context);
|
|
436
|
+
field = undefined;
|
|
437
|
+
if (matched.length === 0) {
|
|
438
|
+
field = await readReplacementProperty(regex, "lastIndex", budget, context);
|
|
439
|
+
const index = normalizeLastIndex(await sandboxNumber(field, budget, context));
|
|
440
|
+
const point = fullUnicode ? value.codePointAt(index) : undefined;
|
|
441
|
+
await setSandboxProperty(regex, "lastIndex", index + (point !== undefined && point > 0xffff ? 2 : 1), budget, true, context);
|
|
442
|
+
field = undefined;
|
|
443
|
+
}
|
|
444
|
+
matched = undefined;
|
|
445
|
+
current = undefined;
|
|
346
446
|
}
|
|
347
|
-
|
|
348
|
-
|
|
447
|
+
let copiedThrough = 0;
|
|
448
|
+
for (current of matches) {
|
|
449
|
+
field = await readReplacementProperty(current, "length", budget, context);
|
|
450
|
+
const count = Math.max(normalizeLastIndex(await sandboxNumber(field, budget, context)) - 1, 0);
|
|
451
|
+
field = await readReplacementProperty(current, "0", budget, context);
|
|
452
|
+
matched = await sandboxString(field, budget, context);
|
|
453
|
+
field = await readReplacementProperty(current, "index", budget, context);
|
|
454
|
+
const number = await sandboxNumber(field, budget, context);
|
|
455
|
+
const position = Math.min(Math.max(Number.isNaN(number) ? 0 : Math.trunc(number), 0), value.length);
|
|
456
|
+
field = undefined;
|
|
457
|
+
captures = [];
|
|
458
|
+
budget.allocateArrayLength(count);
|
|
459
|
+
for (let index = 1; index <= count; index++) {
|
|
460
|
+
budget.visitNode();
|
|
461
|
+
field = await readReplacementProperty(current, String(index), budget, context);
|
|
462
|
+
captures.push(field === undefined ? undefined : await sandboxString(field, budget, context));
|
|
463
|
+
field = undefined;
|
|
464
|
+
}
|
|
465
|
+
groups = await readReplacementProperty(current, "groups", budget, context);
|
|
466
|
+
let text;
|
|
467
|
+
if (typeof replacement === "string") {
|
|
468
|
+
if (groups === null)
|
|
469
|
+
throw new TypeError("Replacement groups cannot be null.");
|
|
470
|
+
if (groups !== undefined && typeof groups !== "object") {
|
|
471
|
+
groups = createSandboxBox(groups);
|
|
472
|
+
budget.chargeDataUsage(measureSandboxData([groups]));
|
|
473
|
+
}
|
|
474
|
+
text = await expandReplacement(replacement, matched, captures, value, position, budget, context, groups);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
const args = [matched, ...captures, position, value];
|
|
478
|
+
if (groups !== undefined)
|
|
479
|
+
args.push(groups);
|
|
480
|
+
budget.allocateArrayLength(args.length);
|
|
481
|
+
field = await callClosure(replacement, args);
|
|
482
|
+
text = await sandboxString(field, budget, context);
|
|
483
|
+
field = undefined;
|
|
484
|
+
}
|
|
485
|
+
if (position >= copiedThrough) {
|
|
486
|
+
result = budget.allocateString(result + value.slice(copiedThrough, position) + text);
|
|
487
|
+
copiedThrough = position + matched.length;
|
|
488
|
+
}
|
|
489
|
+
matched = undefined;
|
|
490
|
+
captures = [];
|
|
491
|
+
groups = undefined;
|
|
349
492
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
493
|
+
return budget.allocateString(result + value.slice(copiedThrough));
|
|
494
|
+
}
|
|
495
|
+
finally {
|
|
496
|
+
release();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async function expandReplacement(replacement, match, captures, input, matchIndex, budget, context, groups) {
|
|
500
|
+
let result = "";
|
|
501
|
+
const release = retainValues(budget, () => [replacement, match, captures, input, groups, result]);
|
|
502
|
+
try {
|
|
503
|
+
for (let index = 0; index < replacement.length; index += 1) {
|
|
504
|
+
budget.visitNode();
|
|
505
|
+
budget.allocateString(result);
|
|
506
|
+
const character = replacement.charAt(index);
|
|
507
|
+
if (character !== "$") {
|
|
508
|
+
result += character;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const token = replacement.charAt(index + 1);
|
|
512
|
+
if (token === "$") {
|
|
513
|
+
result += "$";
|
|
514
|
+
}
|
|
515
|
+
else if (token === "&") {
|
|
516
|
+
result += match;
|
|
517
|
+
}
|
|
518
|
+
else if (token === "`") {
|
|
519
|
+
result += input.slice(0, matchIndex);
|
|
520
|
+
}
|
|
521
|
+
else if (token === "'") {
|
|
522
|
+
result += input.slice(matchIndex + match.length);
|
|
523
|
+
}
|
|
524
|
+
else if (token === "<" && groups !== undefined) {
|
|
525
|
+
const end = replacement.indexOf(">", index + 2);
|
|
526
|
+
if (end === -1) {
|
|
527
|
+
result += "$";
|
|
528
|
+
continue;
|
|
358
529
|
}
|
|
530
|
+
const capture = await readReplacementProperty(groups, replacement.slice(index + 2, end), budget, context);
|
|
531
|
+
if (capture !== undefined)
|
|
532
|
+
result += await sandboxString(capture, budget, context);
|
|
533
|
+
index = end;
|
|
534
|
+
continue;
|
|
359
535
|
}
|
|
360
|
-
if (
|
|
536
|
+
else if (token >= "0" && token <= "9") {
|
|
537
|
+
let captureIndex = Number(token);
|
|
538
|
+
const nextDigit = replacement.charAt(index + 2);
|
|
539
|
+
if (nextDigit >= "0" && nextDigit <= "9") {
|
|
540
|
+
const twoDigitIndex = Number(token + nextDigit);
|
|
541
|
+
if (twoDigitIndex > 0 && twoDigitIndex <= captures.length) {
|
|
542
|
+
captureIndex = twoDigitIndex;
|
|
543
|
+
index += 1;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (captureIndex === 0 || captureIndex > captures.length) {
|
|
547
|
+
result += "$";
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
result += captures[captureIndex - 1] ?? "";
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
361
553
|
result += "$";
|
|
362
554
|
continue;
|
|
363
555
|
}
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
else {
|
|
367
|
-
result += "$";
|
|
368
|
-
continue;
|
|
556
|
+
index += 1;
|
|
369
557
|
}
|
|
370
|
-
|
|
558
|
+
return budget.allocateString(result);
|
|
559
|
+
}
|
|
560
|
+
finally {
|
|
561
|
+
release();
|
|
371
562
|
}
|
|
372
|
-
return result;
|
|
373
563
|
}
|
|
374
|
-
async function replaceWithClosure(value, searchValue, replacer, replaceAll, budget, callClosure) {
|
|
564
|
+
async function replaceWithClosure(value, searchValue, replacer, replaceAll, budget, callClosure, context) {
|
|
375
565
|
const offsets = findReplacementOffsets(value, searchValue, replaceAll);
|
|
376
566
|
let result = "";
|
|
377
567
|
let copiedThrough = 0;
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
568
|
+
const release = retainValues(budget, () => [value, searchValue, replacer, result]);
|
|
569
|
+
try {
|
|
570
|
+
for (const offset of offsets) {
|
|
571
|
+
result += value.slice(copiedThrough, offset);
|
|
572
|
+
result += await sandboxString(await callClosure(replacer, [searchValue, offset, value]), budget, context);
|
|
573
|
+
copiedThrough = offset + searchValue.length;
|
|
574
|
+
}
|
|
575
|
+
result += value.slice(copiedThrough);
|
|
576
|
+
return budget.allocateString(result);
|
|
577
|
+
}
|
|
578
|
+
finally {
|
|
579
|
+
release();
|
|
382
580
|
}
|
|
383
|
-
result += value.slice(copiedThrough);
|
|
384
|
-
return budget.allocateString(result);
|
|
385
581
|
}
|
|
386
582
|
function findReplacementOffsets(value, searchValue, replaceAll) {
|
|
387
583
|
const firstOffset = value.indexOf(searchValue);
|
|
@@ -402,40 +598,76 @@ function findReplacementOffsets(value, searchValue, replaceAll) {
|
|
|
402
598
|
}
|
|
403
599
|
return offsets;
|
|
404
600
|
}
|
|
405
|
-
function callSplit(value, args, budget,
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const
|
|
411
|
-
const
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
result.
|
|
423
|
-
|
|
424
|
-
|
|
601
|
+
function callSplit(value, args, budget, useRegex, parent, context) {
|
|
602
|
+
const pattern = args[0];
|
|
603
|
+
let separator;
|
|
604
|
+
const release = retainValues(budget, () => [value, ...args, separator]);
|
|
605
|
+
const withLimit = (number) => {
|
|
606
|
+
const limit = number >>> 0;
|
|
607
|
+
const converted = useRegex || pattern === undefined ? pattern : sandboxString(pattern, budget, context);
|
|
608
|
+
const split = (converted) => {
|
|
609
|
+
separator = converted;
|
|
610
|
+
return splitNormalized(value, converted, limit, budget, parent);
|
|
611
|
+
};
|
|
612
|
+
return converted instanceof Promise ? converted.then(split) : split(converted);
|
|
613
|
+
};
|
|
614
|
+
try {
|
|
615
|
+
const limit = args[1] === undefined ? 2 ** 32 - 1 : sandboxNumber(args[1], budget, context);
|
|
616
|
+
const result = limit instanceof Promise ? limit.then(withLimit) : withLimit(limit);
|
|
617
|
+
if (result instanceof Promise)
|
|
618
|
+
return result.finally(release);
|
|
619
|
+
release();
|
|
620
|
+
return result;
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
release();
|
|
624
|
+
throw error;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
function splitNormalized(value, separator, limit, budget, parent) {
|
|
628
|
+
if (limit === 0)
|
|
629
|
+
return [];
|
|
630
|
+
if (isSandboxRegex(separator)) {
|
|
631
|
+
const regex = separator;
|
|
632
|
+
const operation = budget.acquireCompileOwner(false, parent?.owner);
|
|
633
|
+
const compilation = new CompileScope(operation.owner);
|
|
634
|
+
try {
|
|
635
|
+
const splitter = createSandboxRegex(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`, 0, compilation);
|
|
636
|
+
const result = [];
|
|
637
|
+
let copiedThrough = 0;
|
|
638
|
+
let endedWithZeroWidthMatch = false;
|
|
639
|
+
while (result.length < limit) {
|
|
640
|
+
const match = executeRegex(splitter, value, Number(splitter.lastIndex));
|
|
641
|
+
if (match === null)
|
|
425
642
|
break;
|
|
426
|
-
|
|
643
|
+
if (match.text.length === 0)
|
|
644
|
+
splitter.lastIndex = match.index + 1;
|
|
645
|
+
endedWithZeroWidthMatch = match.text.length === 0 && match.index === value.length;
|
|
646
|
+
if (match.text.length === 0 &&
|
|
647
|
+
(match.index === copiedThrough || match.index === value.length))
|
|
648
|
+
continue;
|
|
649
|
+
budget.allocateArrayLength(result.length + 1);
|
|
650
|
+
result.push(budget.allocateString(value.slice(copiedThrough, match.index)));
|
|
651
|
+
for (const capture of match.captures) {
|
|
652
|
+
if (result.length >= limit)
|
|
653
|
+
break;
|
|
654
|
+
budget.allocateArrayLength(result.length + 1);
|
|
655
|
+
result.push(capture === undefined ? undefined : budget.allocateString(capture));
|
|
656
|
+
}
|
|
657
|
+
copiedThrough = match.index + match.text.length;
|
|
427
658
|
}
|
|
428
|
-
|
|
659
|
+
if (result.length < limit && (value.length > 0 || !endedWithZeroWidthMatch)) {
|
|
660
|
+
budget.allocateArrayLength(result.length + 1);
|
|
661
|
+
result.push(budget.allocateString(value.slice(copiedThrough)));
|
|
662
|
+
}
|
|
663
|
+
return result;
|
|
664
|
+
}
|
|
665
|
+
finally {
|
|
666
|
+
compilation.dispose();
|
|
667
|
+
operation.release();
|
|
429
668
|
}
|
|
430
|
-
if (result.length < limit && (value.length > 0 || !endedWithZeroWidthMatch))
|
|
431
|
-
result.push(budget.allocateString(value.slice(copiedThrough)));
|
|
432
|
-
budget.allocateArrayLength(result.length);
|
|
433
|
-
return result;
|
|
434
669
|
}
|
|
435
|
-
|
|
436
|
-
throw new TypeError(SPLIT_STRING_MESSAGE);
|
|
437
|
-
const limit = asNumberOrUndefined(args[1]);
|
|
438
|
-
const result = splitString(value, args[0], limit).map((part) => budget.allocateString(part));
|
|
670
|
+
const result = splitString(value, separator, limit).map((part) => budget.allocateString(part));
|
|
439
671
|
budget.allocateArrayLength(result.length);
|
|
440
672
|
return result;
|
|
441
673
|
}
|
|
@@ -455,20 +687,125 @@ async function callStringPattern(value, methodName, pattern, budget, parent, con
|
|
|
455
687
|
operation.release();
|
|
456
688
|
}
|
|
457
689
|
}
|
|
458
|
-
async function
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const
|
|
463
|
-
|
|
690
|
+
export async function regexMatch(input, regex, budget, context) {
|
|
691
|
+
if (regex === null || typeof regex !== "object")
|
|
692
|
+
throw new TypeError("RegExp match requires an object receiver.");
|
|
693
|
+
let value;
|
|
694
|
+
const matches = [];
|
|
695
|
+
let result;
|
|
696
|
+
let matched;
|
|
697
|
+
let cursor;
|
|
698
|
+
let flagsValue;
|
|
699
|
+
let flags;
|
|
700
|
+
const release = retainValues(budget, () => [input, value, regex, matches, result, matched, cursor, flagsValue, flags]);
|
|
701
|
+
const read = (target, key) => {
|
|
702
|
+
if (context?.getProperty !== undefined)
|
|
703
|
+
return context.getProperty(target, key);
|
|
704
|
+
const descriptor = getSandboxPropertyDescriptor(target, key, budget);
|
|
705
|
+
return descriptor === undefined ? undefined : readPropertyDescriptor(descriptor, target, context);
|
|
706
|
+
};
|
|
464
707
|
try {
|
|
465
|
-
|
|
466
|
-
|
|
708
|
+
value = await sandboxString(input, budget, context);
|
|
709
|
+
input = undefined;
|
|
710
|
+
if (isSandboxRegex(regex) && !hasRegexPropertyOverride(regex, ["exec", "flags", ...Object.keys(regexFlagProperties)], budget)) {
|
|
711
|
+
release();
|
|
712
|
+
return await callStringMethodBody(value, "match", [regex], budget, context?.compilation, context);
|
|
713
|
+
}
|
|
714
|
+
flagsValue = await read(regex, "flags");
|
|
715
|
+
flags = await sandboxString(flagsValue, budget, context);
|
|
716
|
+
flagsValue = undefined;
|
|
717
|
+
if (!flags.includes("g"))
|
|
718
|
+
return await regexExec(regex, value, budget, context);
|
|
719
|
+
const fullUnicode = flags.includes("u") || flags.includes("v");
|
|
720
|
+
flags = undefined;
|
|
721
|
+
await setSandboxProperty(regex, "lastIndex", 0, budget, true, context);
|
|
722
|
+
while (true) {
|
|
723
|
+
budget.visitNode();
|
|
724
|
+
result = await regexExec(regex, value, budget, context);
|
|
725
|
+
if (result === null)
|
|
726
|
+
return matches.length === 0 ? null : matches;
|
|
727
|
+
const descriptor = context?.getProperty === undefined
|
|
728
|
+
? getSandboxPropertyDescriptor(result, "0", budget) : undefined;
|
|
729
|
+
matched = await (context?.getProperty !== undefined ? context.getProperty(result, "0")
|
|
730
|
+
: descriptor === undefined ? undefined : readPropertyDescriptor(descriptor, result, context));
|
|
731
|
+
const text = await sandboxString(matched, budget, context);
|
|
732
|
+
matched = undefined;
|
|
733
|
+
budget.allocateArrayLength(matches.length + 1);
|
|
734
|
+
matches.push(text);
|
|
735
|
+
if (text.length === 0) {
|
|
736
|
+
cursor = await read(regex, "lastIndex");
|
|
737
|
+
const index = normalizeLastIndex(await sandboxNumber(cursor, budget, context));
|
|
738
|
+
const point = fullUnicode ? value.codePointAt(index) : undefined;
|
|
739
|
+
await setSandboxProperty(regex, "lastIndex", index + (point !== undefined && point > 0xffff ? 2 : 1), budget, true, context);
|
|
740
|
+
cursor = undefined;
|
|
741
|
+
}
|
|
742
|
+
result = undefined;
|
|
743
|
+
}
|
|
467
744
|
}
|
|
468
745
|
finally {
|
|
746
|
+
release();
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
function callStringRegexCursor(value, methodName, regex, budget, parent, context) {
|
|
750
|
+
const operation = budget.acquireCompileOwner(false, parent?.owner);
|
|
751
|
+
const compilation = new CompileScope(operation.owner);
|
|
752
|
+
let cursor;
|
|
753
|
+
let flagsValue;
|
|
754
|
+
let flags;
|
|
755
|
+
let matcher;
|
|
756
|
+
const retained = {};
|
|
757
|
+
budget.setRetainedValues(retained, () => [value, regex, cursor, flagsValue, flags, matcher]);
|
|
758
|
+
const release = () => {
|
|
469
759
|
budget.setRetainedValues(retained, undefined);
|
|
470
760
|
compilation.dispose();
|
|
471
761
|
operation.release();
|
|
762
|
+
};
|
|
763
|
+
const readCursor = () => {
|
|
764
|
+
cursor = regex.lastIndex;
|
|
765
|
+
const finish = (lastIndex) => {
|
|
766
|
+
if (matcher !== undefined) {
|
|
767
|
+
matcher.lastIndex = normalizeLastIndex(lastIndex);
|
|
768
|
+
return restoreSandboxRegExpIterator({ matcher, input: value, exhausted: false });
|
|
769
|
+
}
|
|
770
|
+
return callMatchLikeMethod(value, methodName, [regex], compilation, lastIndex);
|
|
771
|
+
};
|
|
772
|
+
const lastIndex = sandboxNumber(cursor, budget, context);
|
|
773
|
+
return lastIndex instanceof Promise ? lastIndex.then(finish) : finish(lastIndex);
|
|
774
|
+
};
|
|
775
|
+
const clone = (text) => {
|
|
776
|
+
flags = text;
|
|
777
|
+
flagsValue = undefined;
|
|
778
|
+
matcher = createSandboxRegex(regex.source, flags, 0, compilation);
|
|
779
|
+
flags = undefined;
|
|
780
|
+
return readCursor();
|
|
781
|
+
};
|
|
782
|
+
const convertFlags = (value) => {
|
|
783
|
+
flagsValue = value;
|
|
784
|
+
const text = sandboxString(value, budget, context);
|
|
785
|
+
return text instanceof Promise ? text.then(clone) : clone(text);
|
|
786
|
+
};
|
|
787
|
+
try {
|
|
788
|
+
let result;
|
|
789
|
+
if (methodName === "matchAll") {
|
|
790
|
+
const descriptor = context?.getProperty === undefined
|
|
791
|
+
? getSandboxPropertyDescriptor(regex, "flags", budget) : undefined;
|
|
792
|
+
const value = context?.getProperty !== undefined
|
|
793
|
+
? context.getProperty(regex, "flags")
|
|
794
|
+
: descriptor !== undefined
|
|
795
|
+
? readPropertyDescriptor(descriptor, regex, context)
|
|
796
|
+
: getRegexMember(regex, "flags", budget, context);
|
|
797
|
+
result = value instanceof Promise ? value.then(convertFlags) : convertFlags(value);
|
|
798
|
+
}
|
|
799
|
+
else
|
|
800
|
+
result = readCursor();
|
|
801
|
+
if (result instanceof Promise)
|
|
802
|
+
return result.finally(release);
|
|
803
|
+
release();
|
|
804
|
+
return result;
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
release();
|
|
808
|
+
throw error;
|
|
472
809
|
}
|
|
473
810
|
}
|
|
474
811
|
function callMatchLikeMethod(value, methodName, args, compilation, lastIndex) {
|
|
@@ -484,8 +821,6 @@ function callMatchLikeMethod(value, methodName, args, compilation, lastIndex) {
|
|
|
484
821
|
regex.lastIndex = lastIndex;
|
|
485
822
|
return match?.index ?? -1;
|
|
486
823
|
}
|
|
487
|
-
if (methodName === "matchAll" && !regex.flags.includes("g"))
|
|
488
|
-
throw new TypeError("String#matchAll requires a global regex.");
|
|
489
824
|
if (methodName === "match" && !regex.flags.includes("g"))
|
|
490
825
|
return toMatchArray(executeRegex(regex, value, lastIndex ?? Number(regex.lastIndex)), value);
|
|
491
826
|
if (methodName === "match")
|
|
@@ -493,12 +828,10 @@ function callMatchLikeMethod(value, methodName, args, compilation, lastIndex) {
|
|
|
493
828
|
const matcher = methodName === "matchAll"
|
|
494
829
|
? createSandboxRegex(regex.source, regex.flags, normalizeLastIndex(lastIndex ?? Number(regex.lastIndex)), compilation)
|
|
495
830
|
: regex;
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
return
|
|
500
|
-
? matches.map((match) => match.text)
|
|
501
|
-
: matches.map((match) => toMatchArray(match, value));
|
|
831
|
+
if (methodName === "matchAll")
|
|
832
|
+
return restoreSandboxRegExpIterator({ matcher, input: value, exhausted: false });
|
|
833
|
+
const matches = collectRegexMatches(matcher, value, matcher.flags.includes("g"), compilation.owner?.budget, Number(matcher.lastIndex));
|
|
834
|
+
return matches.length === 0 ? null : matches.map((match) => match.text);
|
|
502
835
|
}
|
|
503
836
|
function collectRegexMatches(regex, value, all, budget, lastIndex = 0) {
|
|
504
837
|
const matches = [];
|