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.
Files changed (45) hide show
  1. package/dist/index.js +2674 -1760
  2. package/dist/index.js.map +4 -4
  3. package/dist/metafile.json +1 -1
  4. package/package.json +1 -1
  5. package/packages/safe-js/dist/chunks/{chunk-VYUHL33E.js → chunk-2J6W3YGB.js} +2 -2
  6. package/packages/safe-js/dist/chunks/{chunk-6UY4Z7WY.js → chunk-YEZOXLY4.js} +6279 -5426
  7. package/packages/safe-js/dist/chunks/chunk-YEZOXLY4.js.map +7 -0
  8. package/packages/safe-js/dist/cli.js +2 -2
  9. package/packages/safe-js/dist/core.js +1 -1
  10. package/packages/safe-js/dist/graph-depth.js +10 -0
  11. package/packages/safe-js/dist/index.js +2 -2
  12. package/packages/safe-js/dist/interp/cancel.js +3 -0
  13. package/packages/safe-js/dist/interp/globals/misc.js +2 -1
  14. package/packages/safe-js/dist/interp/globals/object-array.js +9 -7
  15. package/packages/safe-js/dist/interp/globals/object.js +5 -2
  16. package/packages/safe-js/dist/interp/globals/regex.js +88 -7
  17. package/packages/safe-js/dist/interp/interpreter.js +21 -14
  18. package/packages/safe-js/dist/interp/iteration.js +6 -1
  19. package/packages/safe-js/dist/interp/methods/regex.d.ts +7 -3
  20. package/packages/safe-js/dist/interp/methods/regex.js +132 -23
  21. package/packages/safe-js/dist/interp/methods/regexp-iterator.d.ts +8 -0
  22. package/packages/safe-js/dist/interp/methods/regexp-iterator.js +49 -0
  23. package/packages/safe-js/dist/interp/methods/string.d.ts +2 -0
  24. package/packages/safe-js/dist/interp/methods/string.js +460 -127
  25. package/packages/safe-js/dist/interp/object-model.d.ts +2 -0
  26. package/packages/safe-js/dist/interp/object-model.js +45 -12
  27. package/packages/safe-js/dist/interp/regexp-iterator.d.ts +14 -0
  28. package/packages/safe-js/dist/interp/regexp-iterator.js +18 -0
  29. package/packages/safe-js/dist/interp/regexp-properties.d.ts +3 -0
  30. package/packages/safe-js/dist/interp/regexp-properties.js +7 -0
  31. package/packages/safe-js/dist/interp/string-coercion.js +11 -7
  32. package/packages/safe-js/dist/interp/values.d.ts +3 -1
  33. package/packages/safe-js/dist/interp/values.js +111 -2
  34. package/packages/safe-js/dist/snapshot/dump-format.js +51 -9
  35. package/packages/safe-js/dist/snapshot/regexp-properties.d.ts +18 -0
  36. package/packages/safe-js/dist/snapshot/regexp-properties.js +58 -0
  37. package/packages/safe-js/dist/snapshot/replay-data.d.ts +12 -3
  38. package/packages/safe-js/dist/snapshot/replay-data.js +35 -1
  39. package/packages/safe-js/dist/snapshot/replay-inputs.js +17 -0
  40. package/packages/safe-js/dist/snapshot/restore.js +19 -1
  41. package/packages/safe-js/dist/snapshot/serialize.d.ts +12 -3
  42. package/packages/safe-js/dist/snapshot/serialize.js +40 -17
  43. package/packages/safe-js/dist/snapshot/validation.js +25 -2
  44. package/packages/safe-js/dist/chunks/chunk-6UY4Z7WY.js.map +0 -7
  45. /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 { createSandboxClosure, createSandboxRegex, deepCopyFromSandbox, isSandboxClosure, isSandboxRegex } from "../values.js";
8
- import { executeRegex, toMatchArray } from "./regex.js";
9
- const SPLIT_STRING_MESSAGE = "String#split only supports string separator values.";
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 = () => typeof value === "string"
90
- ? callStringMethodBody(value, methodName, args, budget, callClosure, parent, context)
91
- : Promise.resolve(sandboxString(value, budget, context)).then(string => callStringMethodBody(string, methodName, args, budget, callClosure, parent, context));
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 === "split" ? Symbol.split : undefined;
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 applyHook = (hook) => {
99
- if (hook === null || hook === undefined)
100
- return fallback();
101
- if (!isSandboxClosure(hook))
102
- throw new TypeError(`String#${methodName} symbol hook must be callable.`);
103
- return invokeBuiltinClosure(hook, methodName === "split" ? [value, args[1]] : [value], budget, context, pattern);
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
- return context?.getProperty !== undefined
106
- ? Promise.resolve(context.getProperty(pattern, symbol)).then(applyHook)
107
- : applyHook(getSandboxDataProperty(pattern, symbol, budget));
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, callClosure = async (closure, closureArgs) => await closure.call(closureArgs), parent, context) {
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 (isSandboxRegex(regex) && regex.lastIndex !== null && typeof regex.lastIndex === "object" &&
155
- ((methodName === "match" && !regex.flags.includes("g")) ||
156
- (methodName === "matchAll" && regex.flags.includes("g")))) {
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
- if ((!isSandboxRegex(search) && typeof search !== "string") ||
331
+ const regexSearch = useRegex && isSandboxRegex(search);
332
+ if ((!regexSearch && typeof search !== "string") ||
287
333
  (typeof replacement !== "string" && !isSandboxClosure(replacement))) {
288
- throw new TypeError(`String#${methodName} only supports string or regex search values and string or function replacements.`);
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
- if (methodName === "replaceAll" && !search.flags.includes("g")) {
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, replaceAll, budget, callClosure, context) {
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, replaceAll || regex.flags.includes("g"), undefined, lastIndex);
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
- : String(await callClosure(replacement, [match.text, ...match.captures, match.index, value]));
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 expandReplacement(replacement, match, captures, input, matchIndex) {
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
- for (let index = 0; index < replacement.length; index += 1) {
332
- const character = replacement.charAt(index);
333
- if (character !== "$") {
334
- result += character;
335
- continue;
336
- }
337
- const token = replacement.charAt(index + 1);
338
- if (token === "$") {
339
- result += "$";
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
- else if (token === "`") {
345
- result += input.slice(0, matchIndex);
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
- else if (token === "'") {
348
- result += input.slice(matchIndex + match.length);
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
- else if (token >= "0" && token <= "9") {
351
- let captureIndex = Number(token);
352
- const nextDigit = replacement.charAt(index + 2);
353
- if (nextDigit >= "0" && nextDigit <= "9") {
354
- const twoDigitIndex = Number(token + nextDigit);
355
- if (twoDigitIndex > 0 && twoDigitIndex <= captures.length) {
356
- captureIndex = twoDigitIndex;
357
- index += 1;
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 (captureIndex === 0 || captureIndex > captures.length) {
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
- result += captures[captureIndex - 1] ?? "";
365
- }
366
- else {
367
- result += "$";
368
- continue;
556
+ index += 1;
369
557
  }
370
- index += 1;
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
- for (const offset of offsets) {
379
- result += value.slice(copiedThrough, offset);
380
- result += String(await callClosure(replacer, [searchValue, offset, value]));
381
- copiedThrough = offset + searchValue.length;
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, compilation) {
406
- if (args.some(isSandboxClosure))
407
- throw new TypeError("String#split does not support function arguments.");
408
- if (isSandboxRegex(args[0])) {
409
- const regex = args[0];
410
- const splitter = createSandboxRegex(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`, 0, compilation);
411
- const limit = asNumberOrUndefined(args[1]) ?? 2 ** 32 - 1;
412
- const result = [];
413
- let copiedThrough = 0;
414
- let endedWithZeroWidthMatch = false;
415
- for (const match of collectRegexMatches(splitter, value, true)) {
416
- endedWithZeroWidthMatch = match.text.length === 0 && match.index === value.length;
417
- if (match.text.length === 0 &&
418
- (match.index === copiedThrough || match.index === value.length))
419
- continue;
420
- if (result.length >= limit)
421
- break;
422
- result.push(budget.allocateString(value.slice(copiedThrough, match.index)));
423
- for (const capture of match.captures) {
424
- if (result.length >= limit)
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
- result.push(capture === undefined ? undefined : budget.allocateString(capture));
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
- copiedThrough = match.index + match.text.length;
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
- if (args[0] !== undefined && typeof args[0] !== "string")
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 callStringRegexCursor(value, methodName, regex, budget, parent, context) {
459
- const operation = budget.acquireCompileOwner(false, parent?.owner);
460
- const compilation = new CompileScope(operation.owner);
461
- const cursor = regex.lastIndex;
462
- const retained = {};
463
- budget.setRetainedValues(retained, () => [value, regex, cursor]);
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
- const lastIndex = await sandboxNumber(cursor, budget, context);
466
- return callMatchLikeMethod(value, methodName, [regex], compilation, lastIndex);
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
- const matches = collectRegexMatches(matcher, value, true, compilation.owner?.budget, Number(matcher.lastIndex));
497
- if (methodName === "match" && matches.length === 0)
498
- return null;
499
- return methodName === "match"
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 = [];