poe-code 14.0.74 → 14.0.75

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 (38) hide show
  1. package/dist/index.js +926 -84
  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-2J6W3YGB.js → chunk-BAJI3K4B.js} +2 -2
  6. package/packages/safe-js/dist/chunks/{chunk-YEZOXLY4.js → chunk-EOTY4PCT.js} +901 -84
  7. package/packages/safe-js/dist/chunks/chunk-EOTY4PCT.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/index.js +2 -2
  11. package/packages/safe-js/dist/interp/globals/regex.js +108 -2
  12. package/packages/safe-js/dist/interp/iteration.js +5 -2
  13. package/packages/safe-js/dist/interp/methods/regex-split.d.ts +3 -0
  14. package/packages/safe-js/dist/interp/methods/regex-split.js +102 -0
  15. package/packages/safe-js/dist/interp/methods/regex.d.ts +1 -1
  16. package/packages/safe-js/dist/interp/methods/regex.js +16 -5
  17. package/packages/safe-js/dist/interp/methods/regexp-iterator.d.ts +5 -1
  18. package/packages/safe-js/dist/interp/methods/regexp-iterator.js +59 -7
  19. package/packages/safe-js/dist/interp/methods/string.js +7 -7
  20. package/packages/safe-js/dist/interp/object-model.d.ts +1 -0
  21. package/packages/safe-js/dist/interp/object-model.js +4 -1
  22. package/packages/safe-js/dist/interp/regex/compile-guard.js +26 -4
  23. package/packages/safe-js/dist/interp/regex/engine.d.ts +4 -0
  24. package/packages/safe-js/dist/interp/regex/engine.js +230 -30
  25. package/packages/safe-js/dist/interp/regex/parse.d.ts +42 -4
  26. package/packages/safe-js/dist/interp/regex/parse.js +429 -25
  27. package/packages/safe-js/dist/interp/regexp-iterator.d.ts +4 -2
  28. package/packages/safe-js/dist/interp/regexp-iterator.js +6 -1
  29. package/packages/safe-js/dist/interp/values.js +5 -3
  30. package/packages/safe-js/dist/snapshot/dump-format.js +10 -2
  31. package/packages/safe-js/dist/snapshot/replay-data.d.ts +3 -0
  32. package/packages/safe-js/dist/snapshot/replay-data.js +14 -4
  33. package/packages/safe-js/dist/snapshot/restore.js +8 -2
  34. package/packages/safe-js/dist/snapshot/serialize.d.ts +3 -0
  35. package/packages/safe-js/dist/snapshot/serialize.js +4 -1
  36. package/packages/safe-js/dist/snapshot/validation.js +8 -0
  37. package/packages/safe-js/dist/chunks/chunk-YEZOXLY4.js.map +0 -7
  38. /package/packages/safe-js/dist/chunks/{chunk-2J6W3YGB.js.map → chunk-BAJI3K4B.js.map} +0 -0
@@ -22,7 +22,7 @@ import {
22
22
  resolveFsConfig,
23
23
  splitFrontmatter,
24
24
  withBrokenPipeGuard
25
- } from "./chunks/chunk-2J6W3YGB.js";
25
+ } from "./chunks/chunk-BAJI3K4B.js";
26
26
  import {
27
27
  Budget,
28
28
  SandboxError,
@@ -34,7 +34,7 @@ import {
34
34
  replaceErrorStack,
35
35
  restore,
36
36
  run
37
- } from "./chunks/chunk-YEZOXLY4.js";
37
+ } from "./chunks/chunk-EOTY4PCT.js";
38
38
  import "./chunks/chunk-ZWJ5BJUI.js";
39
39
  import "./chunks/chunk-3YTQEPZI.js";
40
40
  import "./chunks/chunk-4YIDX44G.js";
@@ -5,7 +5,7 @@ import {
5
5
  defineExtension,
6
6
  lint,
7
7
  run
8
- } from "./chunks/chunk-YEZOXLY4.js";
8
+ } from "./chunks/chunk-EOTY4PCT.js";
9
9
  import "./chunks/chunk-4YIDX44G.js";
10
10
  export {
11
11
  Budget,
@@ -26,7 +26,7 @@ import {
26
26
  runWithSpawnUsageAccumulator,
27
27
  splitFrontmatter,
28
28
  supportsSpawnMode
29
- } from "./chunks/chunk-2J6W3YGB.js";
29
+ } from "./chunks/chunk-BAJI3K4B.js";
30
30
  import {
31
31
  Budget,
32
32
  FileSnapshotBackend,
@@ -50,7 +50,7 @@ import {
50
50
  registerPendingHostCallPolicy,
51
51
  restore,
52
52
  run
53
- } from "./chunks/chunk-YEZOXLY4.js";
53
+ } from "./chunks/chunk-EOTY4PCT.js";
54
54
  import "./chunks/chunk-ZWJ5BJUI.js";
55
55
  import "./chunks/chunk-3YTQEPZI.js";
56
56
  import "./chunks/chunk-4YIDX44G.js";
@@ -1,12 +1,17 @@
1
- import { createSandboxClosure, createSandboxRegex, isSandboxRegex, reconcileCompiledValues } from "../values.js";
1
+ import { createSandboxClosure, createSandboxRegex, isSandboxClosure, isSandboxRegex, reconcileCompiledValues } from "../values.js";
2
2
  import { CompileScope } from "../regex/compile-guard.js";
3
3
  import { SandboxError } from "../budget.js";
4
- import { sandboxString } from "../string-coercion.js";
4
+ import { sandboxNumber, sandboxString } from "../string-coercion.js";
5
5
  import { getSandboxPropertyDescriptor, installRegexPrototype, materializeFunctionProperties, setSandboxPrototype } from "../object-model.js";
6
6
  import { accessorAdapter, readPropertyDescriptor } from "../accessors.js";
7
7
  import { callRegexMethod, getRegexMember, regexFlagProperties, regexSearch } from "../methods/regex.js";
8
8
  import { regexMatch, regexReplace } from "../methods/string.js";
9
9
  import { invokeBuiltinClosure } from "../builtin-call.js";
10
+ import { restoreSandboxRegExpIterator } from "../regexp-iterator.js";
11
+ import { normalizeLastIndex } from "../regex/engine.js";
12
+ import { retainValues } from "../resources.js";
13
+ import { setSandboxProperty } from "../interpreter.js";
14
+ import { regexSplit } from "../methods/regex-split.js";
10
15
  export function createRegexGlobals(options) {
11
16
  const invoke = (construct) => async (args, context) => {
12
17
  const selected = options.compileOwner ?? context?.compilation?.owner;
@@ -75,7 +80,52 @@ export function createRegexGlobals(options) {
75
80
  }
76
81
  };
77
82
  const constructor = createSandboxClosure({ guest: true, sandbox: true, name: "RegExp", length: 2, call: invoke(false), construct: invoke(true) });
83
+ Object.defineProperty(materializeFunctionProperties(constructor), "escape", {
84
+ value: createSandboxClosure({ guest: true, sandbox: true, name: "escape", length: 1,
85
+ call: (args) => {
86
+ const input = args[0];
87
+ if (typeof input !== "string")
88
+ throw new TypeError("RegExp.escape requires a string.");
89
+ const allocation = {};
90
+ let escaped = "";
91
+ try {
92
+ for (const character of input) {
93
+ options.budget.visitNode();
94
+ const point = character.codePointAt(0);
95
+ let part = character;
96
+ if (escaped.length === 0 && ((point >= 48 && point <= 57) ||
97
+ (point >= 65 && point <= 90) || (point >= 97 && point <= 122))) {
98
+ part = `\\x${point.toString(16)}`;
99
+ }
100
+ else if ("^$\\.*+?()[]{}|/".includes(character)) {
101
+ part = "\\" + character;
102
+ }
103
+ else {
104
+ const control = "\f\n\r\t\v".indexOf(character);
105
+ if (control !== -1)
106
+ part = "\\" + "fnrtv"[control];
107
+ else if (",-=<>#&!%:;@~'`\"".includes(character) || character.trim() === "" ||
108
+ (point >= 0xd800 && point <= 0xdfff)) {
109
+ part = point <= 0xff ? `\\x${point.toString(16).padStart(2, "0")}`
110
+ : `\\u${point.toString(16).padStart(4, "0")}`;
111
+ }
112
+ }
113
+ options.budget.setRetainedDataUsage(allocation, escaped.length + part.length);
114
+ escaped = options.budget.allocateString(escaped + part);
115
+ }
116
+ return escaped;
117
+ }
118
+ finally {
119
+ options.budget.setRetainedDataUsage(allocation, 0);
120
+ }
121
+ }
122
+ }), writable: true, configurable: true
123
+ });
78
124
  const prototype = Object.create(null);
125
+ Object.defineProperty(materializeFunctionProperties(constructor), Symbol.species, {
126
+ get: accessorAdapter(createSandboxClosure({ sandbox: true, name: "get [Symbol.species]", length: 0,
127
+ call: (_args, context) => context?.thisValue }), "get"), configurable: true
128
+ });
79
129
  Object.defineProperty(materializeFunctionProperties(constructor), "prototype", { value: prototype, writable: false });
80
130
  Object.defineProperty(prototype, "constructor", { value: constructor, writable: true, configurable: true });
81
131
  Object.defineProperty(prototype, Symbol.search, {
@@ -93,6 +143,62 @@ export function createRegexGlobals(options) {
93
143
  call: (args, context) => regexReplace(args[0], context?.thisValue, args[1], options.budget, (closure, values) => invokeBuiltinClosure(closure, values, options.budget, context, undefined), context) }),
94
144
  writable: true, configurable: true
95
145
  });
146
+ Object.defineProperty(prototype, Symbol.matchAll, {
147
+ value: createSandboxClosure({ guest: true, sandbox: true, name: "[Symbol.matchAll]", length: 1,
148
+ call: async (args, context) => {
149
+ const target = context?.thisValue;
150
+ if (target === null || typeof target !== "object")
151
+ throw new TypeError("RegExp matchAll requires an object receiver.");
152
+ let input = args[0];
153
+ let string;
154
+ let species;
155
+ let field;
156
+ let flags;
157
+ let matcher;
158
+ const release = retainValues(options.budget, () => [target, input, string, species, field, flags, matcher]);
159
+ const read = (value, key) => {
160
+ if (context?.getProperty !== undefined)
161
+ return context.getProperty(value, key);
162
+ const descriptor = getSandboxPropertyDescriptor(value, key, options.budget);
163
+ return descriptor === undefined ? undefined : readPropertyDescriptor(descriptor, value, context);
164
+ };
165
+ try {
166
+ string = await sandboxString(input, options.budget, context);
167
+ input = undefined;
168
+ field = await read(target, "constructor");
169
+ species = constructor;
170
+ if (field !== undefined) {
171
+ if (field === null || typeof field !== "object")
172
+ throw new TypeError("Invalid RegExp species constructor.");
173
+ species = await read(field, Symbol.species);
174
+ if (species === null || species === undefined)
175
+ species = constructor;
176
+ }
177
+ field = undefined;
178
+ if (!isSandboxClosure(species) || species.construct === undefined)
179
+ throw new TypeError("RegExp species must be a constructor.");
180
+ field = await read(target, "flags");
181
+ flags = await sandboxString(field, options.budget, context);
182
+ field = undefined;
183
+ matcher = await invokeBuiltinClosure(species, [target, flags], options.budget, context, undefined, true);
184
+ field = await read(target, "lastIndex");
185
+ const cursor = normalizeLastIndex(await sandboxNumber(field, options.budget, context));
186
+ field = undefined;
187
+ await setSandboxProperty(matcher, "lastIndex", cursor, options.budget, true, context);
188
+ return restoreSandboxRegExpIterator({ matcher, input: string, exhausted: false,
189
+ global: flags.includes("g"), unicode: flags.includes("u") || flags.includes("v") });
190
+ }
191
+ finally {
192
+ release();
193
+ }
194
+ } }),
195
+ writable: true, configurable: true
196
+ });
197
+ Object.defineProperty(prototype, Symbol.split, {
198
+ value: createSandboxClosure({ guest: true, sandbox: true, name: "[Symbol.split]", length: 2,
199
+ call: (args, context) => regexSplit(context?.thisValue, args[0], args[1], constructor, options.budget, context) }),
200
+ writable: true, configurable: true
201
+ });
96
202
  for (const name of ["exec", "test", "toString"]) {
97
203
  Object.defineProperty(prototype, name, {
98
204
  value: createSandboxClosure({ sandbox: true, name, length: name === "toString" ? 0 : 1,
@@ -5,7 +5,7 @@ import { boxedValue, isSandboxBox } from "./boxed.js";
5
5
  import { getHostObjectIterator, isGuestHostObject } from "./host-capabilities.js";
6
6
  import { isSandboxCollectionIterator, nextCollectionIterator } from "./collection-iterator.js";
7
7
  import { isSandboxRegExpIterator } from "./regexp-iterator.js";
8
- import { nextRegExpIterator } from "./methods/regexp-iterator.js";
8
+ import { nextObservableRegExpIterator, nextRegExpIterator } from "./methods/regexp-iterator.js";
9
9
  import { Budget, isFatalSandboxError } from "./budget.js";
10
10
  import { sandboxString } from "./string-coercion.js";
11
11
  import { awaitSandboxValue, awaitWithSignal } from "./cancel.js";
@@ -225,8 +225,11 @@ export function getSandboxIterator(value, budget, context) {
225
225
  }
226
226
  if (isSandboxCollectionIterator(value))
227
227
  return { next: () => nextCollectionIterator(value, budget), snapshotIndex: () => 0 };
228
- if (isSandboxRegExpIterator(value))
228
+ if (isSandboxRegExpIterator(value)) {
229
+ if (context !== undefined && budget !== undefined)
230
+ return { asynchronous: true, next: () => nextObservableRegExpIterator(value, budget, context), snapshotIndex: () => 0 };
229
231
  return { next: () => nextRegExpIterator(value, budget), snapshotIndex: () => 0 };
232
+ }
230
233
  if (isGuestHostObject(value))
231
234
  return getHostObjectIterator(value);
232
235
  if (isFloat32Array(value)) {
@@ -0,0 +1,3 @@
1
+ import { type Budget } from "../budget.js";
2
+ import { type SandboxCallContext, type SandboxClosure, type SandboxValue } from "../values.js";
3
+ export declare function regexSplit(target: SandboxValue, input: SandboxValue, limit: SandboxValue, constructor: SandboxClosure, budget: Budget, context?: SandboxCallContext): Promise<SandboxValue[]>;
@@ -0,0 +1,102 @@
1
+ import { isSandboxClosure } from "../values.js";
2
+ import { getSandboxPropertyDescriptor } from "../object-model.js";
3
+ import { readPropertyDescriptor } from "../accessors.js";
4
+ import { invokeBuiltinClosure } from "../builtin-call.js";
5
+ import { setSandboxProperty } from "../interpreter.js";
6
+ import { sandboxNumber, sandboxString } from "../string-coercion.js";
7
+ import { normalizeLastIndex } from "../regex/engine.js";
8
+ import { retainValues } from "../resources.js";
9
+ import { regexExec } from "./regex.js";
10
+ export async function regexSplit(target, input, limit, constructor, budget, context) {
11
+ if (target === null || typeof target !== "object")
12
+ throw new TypeError("RegExp split requires an object receiver.");
13
+ let string;
14
+ let species;
15
+ let field;
16
+ let flags;
17
+ let matcher;
18
+ let match;
19
+ const result = [];
20
+ const release = retainValues(budget, () => [target, input, limit, string, species, field, flags, matcher, match, result]);
21
+ const read = (value, key) => {
22
+ if (context?.getProperty !== undefined)
23
+ return context.getProperty(value, key);
24
+ const descriptor = getSandboxPropertyDescriptor(value, key, budget);
25
+ return descriptor === undefined ? undefined : readPropertyDescriptor(descriptor, value, context);
26
+ };
27
+ const append = (value) => {
28
+ budget.allocateArrayLength(result.length + 1);
29
+ result.push(value);
30
+ };
31
+ try {
32
+ string = await sandboxString(input, budget, context);
33
+ input = undefined;
34
+ field = await read(target, "constructor");
35
+ species = constructor;
36
+ if (field !== undefined) {
37
+ if (field === null || typeof field !== "object")
38
+ throw new TypeError("Invalid RegExp species constructor.");
39
+ species = await read(field, Symbol.species);
40
+ if (species === null || species === undefined)
41
+ species = constructor;
42
+ }
43
+ field = undefined;
44
+ if (!isSandboxClosure(species) || species.construct === undefined)
45
+ throw new TypeError("RegExp species must be a constructor.");
46
+ field = await read(target, "flags");
47
+ flags = await sandboxString(field, budget, context);
48
+ field = undefined;
49
+ const unicode = flags.includes("u") || flags.includes("v");
50
+ if (!flags.includes("y"))
51
+ flags = budget.allocateString(flags + "y");
52
+ matcher = await invokeBuiltinClosure(species, [target, flags], budget, context, undefined, true);
53
+ const maximum = limit === undefined ? 2 ** 32 - 1 : (await sandboxNumber(limit, budget, context)) >>> 0;
54
+ limit = undefined;
55
+ if (maximum === 0)
56
+ return result;
57
+ if (string.length === 0) {
58
+ match = await regexExec(matcher, string, budget, context);
59
+ if (match === null)
60
+ append(string);
61
+ return result;
62
+ }
63
+ let end = 0;
64
+ let index = 0;
65
+ while (index < string.length) {
66
+ budget.visitNode();
67
+ match = undefined;
68
+ await setSandboxProperty(matcher, "lastIndex", index, budget, true, context);
69
+ match = await regexExec(matcher, string, budget, context);
70
+ if (match !== null) {
71
+ field = await read(matcher, "lastIndex");
72
+ const next = Math.min(normalizeLastIndex(await sandboxNumber(field, budget, context)), string.length);
73
+ field = undefined;
74
+ if (next !== end) {
75
+ append(budget.allocateString(string.slice(end, index)));
76
+ if (result.length === maximum)
77
+ return result;
78
+ end = next;
79
+ field = await read(match, "length");
80
+ const length = normalizeLastIndex(await sandboxNumber(field, budget, context));
81
+ field = undefined;
82
+ for (let capture = 1; capture < length; capture++) {
83
+ budget.visitNode();
84
+ field = await read(match, String(capture));
85
+ append(field);
86
+ field = undefined;
87
+ if (result.length === maximum)
88
+ return result;
89
+ }
90
+ index = end;
91
+ continue;
92
+ }
93
+ }
94
+ index += unicode && (string.codePointAt(index) ?? 0) > 0xffff ? 2 : 1;
95
+ }
96
+ append(budget.allocateString(string.slice(end)));
97
+ return result;
98
+ }
99
+ finally {
100
+ release();
101
+ }
102
+ }
@@ -11,4 +11,4 @@ export declare function callRegexMethod(target: SandboxValue, methodName: RegexM
11
11
  export declare function regexExec(target: SandboxValue, input: string, budget: Budget, context?: SandboxCallContext): Promise<SandboxValue>;
12
12
  export declare function regexToString(target: SandboxValue, budget: Budget, context?: SandboxCallContext): Promise<string>;
13
13
  export declare function executeRegex(target: SandboxRegex, input: string, lastIndex: number): RegexMatch | null;
14
- export declare function toMatchArray(match: RegexMatch | null, input: string): SandboxValue;
14
+ export declare function toMatchArray(match: RegexMatch | null, input: string, budget?: Budget): SandboxValue;
@@ -1,7 +1,7 @@
1
1
  import { createSandboxClosure, getSandboxRegexPattern, getRegexProperties, isSandboxClosure, isSandboxRegex } from "../values.js";
2
2
  import { matchRegex } from "../regex/engine.js";
3
3
  import { invokeBuiltinClosure } from "../builtin-call.js";
4
- import { getSandboxPropertyDescriptor, getSandboxPrototype, hasExplicitSandboxPrototype } from "../object-model.js";
4
+ import { getSandboxPropertyDescriptor, getSandboxPrototype, hasExplicitSandboxPrototype, setSandboxPrototype } from "../object-model.js";
5
5
  import { readPropertyDescriptor } from "../accessors.js";
6
6
  import { retainValues } from "../resources.js";
7
7
  import { sandboxNumber, sandboxString } from "../string-coercion.js";
@@ -160,7 +160,7 @@ export async function callRegexMethod(target, methodName, args, budget, context)
160
160
  cursor = target.lastIndex;
161
161
  const lastIndex = await sandboxNumber(cursor, budget, context);
162
162
  const match = executeRegex(target, input, lastIndex);
163
- return toMatchArray(match, input);
163
+ return toMatchArray(match, input, budget);
164
164
  }
165
165
  finally {
166
166
  budget.setRetainedValues(retained, undefined);
@@ -218,16 +218,27 @@ export async function regexToString(target, budget, context) {
218
218
  export function executeRegex(target, input, lastIndex) {
219
219
  const pattern = getSandboxRegexPattern(target);
220
220
  const match = matchRegex(pattern, input, lastIndex);
221
- if (pattern.flags.global) {
221
+ if (pattern.flags.global || pattern.flags.sticky) {
222
222
  target.lastIndex = match === null ? 0 : match.index + match.text.length;
223
223
  }
224
224
  return match;
225
225
  }
226
- export function toMatchArray(match, input) {
226
+ export function toMatchArray(match, input, budget) {
227
227
  if (match === null) {
228
228
  return null;
229
229
  }
230
+ budget?.allocateArrayLength(match.captures.length + 1);
230
231
  const result = [match.text, ...match.captures];
231
- Object.assign(result, { index: match.index, input, groups: undefined });
232
+ if (match.groups !== undefined)
233
+ setSandboxPrototype(match.groups, null, budget);
234
+ if (match.indicesGroups !== undefined)
235
+ setSandboxPrototype(match.indicesGroups, null, budget);
236
+ Object.assign(result, { index: match.index, input, groups: match.groups });
237
+ if (match.indices !== undefined) {
238
+ budget?.allocateArrayLength(match.indices.length);
239
+ budget?.allocateArrayLength(2);
240
+ Object.assign(match.indices, { groups: match.indicesGroups });
241
+ Object.assign(result, { indices: match.indices });
242
+ }
232
243
  return result;
233
244
  }
@@ -1,8 +1,12 @@
1
1
  import type { Budget } from "../budget.js";
2
2
  import { type SandboxRegExpIterator } from "../regexp-iterator.js";
3
- import { type SandboxValue } from "../values.js";
3
+ import { type SandboxCallContext, type SandboxValue } from "../values.js";
4
4
  export declare function nextRegExpIterator(iterator: SandboxRegExpIterator, budget?: Budget): {
5
5
  value: SandboxValue;
6
6
  done: boolean;
7
7
  };
8
+ export declare function nextObservableRegExpIterator(iterator: SandboxRegExpIterator, budget: Budget, context: SandboxCallContext): Promise<{
9
+ value: SandboxValue;
10
+ done: boolean;
11
+ }>;
8
12
  export declare function getRegExpIteratorMember(property: PropertyKey, budget: Budget): SandboxValue;
@@ -1,15 +1,23 @@
1
1
  import { isSandboxRegExpIterator, regexpIteratorState } from "../regexp-iterator.js";
2
- import { createSandboxClosure } from "../values.js";
3
- import { executeRegex, toMatchArray } from "./regex.js";
2
+ import { createSandboxClosure, isSandboxRegex } from "../values.js";
3
+ import { executeRegex, regexExec, toMatchArray } from "./regex.js";
4
+ import { getSandboxPropertyDescriptor, hasRegexPropertyOverride } from "../object-model.js";
5
+ import { readPropertyDescriptor } from "../accessors.js";
6
+ import { setSandboxProperty } from "../interpreter.js";
7
+ import { sandboxNumber, sandboxString } from "../string-coercion.js";
8
+ import { normalizeLastIndex } from "../regex/engine.js";
9
+ import { retainValues } from "../resources.js";
4
10
  export function nextRegExpIterator(iterator, budget) {
5
11
  const state = regexpIteratorState(iterator);
6
12
  budget?.visitNode();
7
13
  if (state.exhausted)
8
14
  return { value: undefined, done: true };
9
15
  const matcher = state.matcher;
16
+ if (!isSandboxRegex(matcher))
17
+ throw new TypeError("Custom RegExp iterator execution requires a sandbox context.");
10
18
  const input = state.input;
11
19
  const match = executeRegex(matcher, input, Number(matcher.lastIndex));
12
- if (match === null || !matcher.flags.includes("g")) {
20
+ if (match === null || !(state.global ?? matcher.flags.includes("g"))) {
13
21
  state.exhausted = true;
14
22
  state.matcher = undefined;
15
23
  state.input = undefined;
@@ -18,12 +26,56 @@ export function nextRegExpIterator(iterator, budget) {
18
26
  return { value: undefined, done: true };
19
27
  if (!state.exhausted && match.text.length === 0) {
20
28
  const index = Number(matcher.lastIndex);
21
- const unicode = matcher.flags.includes("u") || matcher.flags.includes("v");
29
+ const unicode = state.unicode ?? (matcher.flags.includes("u") || matcher.flags.includes("v"));
22
30
  const codePoint = input.codePointAt(index);
23
31
  matcher.lastIndex = index + (unicode && codePoint !== undefined && codePoint > 0xffff ? 2 : 1);
24
32
  }
25
- budget?.allocateArrayLength(match.captures.length + 1);
26
- return { value: toMatchArray(match, input), done: false };
33
+ return { value: toMatchArray(match, input, budget), done: false };
34
+ }
35
+ export async function nextObservableRegExpIterator(iterator, budget, context) {
36
+ const state = regexpIteratorState(iterator);
37
+ if (state.exhausted)
38
+ return { value: undefined, done: true };
39
+ const matcher = state.matcher;
40
+ if (isSandboxRegex(matcher) && !hasRegexPropertyOverride(matcher, ["exec"], budget) &&
41
+ (matcher.lastIndex === null || typeof matcher.lastIndex !== "object"))
42
+ return nextRegExpIterator(iterator, budget);
43
+ const input = state.input;
44
+ let result;
45
+ let field;
46
+ const release = retainValues(budget, () => [iterator, matcher, input, result, field]);
47
+ const read = (value, key) => {
48
+ if (context.getProperty !== undefined)
49
+ return context.getProperty(value, key);
50
+ const descriptor = getSandboxPropertyDescriptor(value, key, budget);
51
+ return descriptor === undefined ? undefined : readPropertyDescriptor(descriptor, value, context);
52
+ };
53
+ try {
54
+ budget.visitNode();
55
+ result = await regexExec(matcher, input, budget, context);
56
+ const global = state.global ?? (isSandboxRegex(matcher) && matcher.flags.includes("g"));
57
+ if (result === null || !global) {
58
+ state.exhausted = true;
59
+ state.matcher = undefined;
60
+ state.input = undefined;
61
+ return result === null ? { value: undefined, done: true } : { value: result, done: false };
62
+ }
63
+ field = await read(result, "0");
64
+ const text = await sandboxString(field, budget, context);
65
+ field = undefined;
66
+ if (text.length === 0) {
67
+ field = await read(matcher, "lastIndex");
68
+ const index = normalizeLastIndex(await sandboxNumber(field, budget, context));
69
+ field = undefined;
70
+ const unicode = state.unicode ?? (isSandboxRegex(matcher) && (matcher.flags.includes("u") || matcher.flags.includes("v")));
71
+ const point = input.codePointAt(index);
72
+ await setSandboxProperty(matcher, "lastIndex", index + (unicode && point !== undefined && point > 0xffff ? 2 : 1), budget, true, context);
73
+ }
74
+ return { value: result, done: false };
75
+ }
76
+ finally {
77
+ release();
78
+ }
27
79
  }
28
80
  export function getRegExpIteratorMember(property, budget) {
29
81
  if (property === Symbol.toStringTag)
@@ -43,7 +95,7 @@ export function getRegExpIteratorMember(property, budget) {
43
95
  const receiver = context?.thisValue;
44
96
  if (!isSandboxRegExpIterator(receiver))
45
97
  throw new TypeError("RegExp string iterator next requires a matching receiver.");
46
- return nextRegExpIterator(receiver, budget);
98
+ return context === undefined ? nextRegExpIterator(receiver, budget) : nextObservableRegExpIterator(receiver, budget, context);
47
99
  }
48
100
  });
49
101
  }
@@ -1,14 +1,14 @@
1
1
  import { types } from "node:util";
2
2
  import { invokeBuiltinClosure } from "../builtin-call.js";
3
3
  import { CompileScope } from "../regex/compile-guard.js";
4
- import { normalizeLastIndex } from "../regex/engine.js";
4
+ import { advanceStringIndex, normalizeLastIndex } from "../regex/engine.js";
5
5
  import { sandboxNumber, sandboxString } from "../string-coercion.js";
6
6
  import { retainValues } from "../resources.js";
7
7
  import { getSandboxDataProperty, getSandboxPropertyDescriptor, hasRegexPropertyOverride } from "../object-model.js";
8
8
  import { readPropertyDescriptor } from "../accessors.js";
9
9
  import { createSandboxBox } from "../boxed.js";
10
10
  import { setSandboxProperty } from "../interpreter.js";
11
- import { createSandboxClosure, createSandboxRegex, deepCopyFromSandbox, isSandboxClosure, isSandboxRegex, measureSandboxData } from "../values.js";
11
+ import { createSandboxClosure, createSandboxRegex, deepCopyFromSandbox, getSandboxRegexPattern, isSandboxClosure, isSandboxRegex, measureSandboxData } from "../values.js";
12
12
  import { executeRegex, getRegexMember, regexExec, regexFlagProperties, regexSearch, toMatchArray } from "./regex.js";
13
13
  import { restoreSandboxRegExpIterator } from "../regexp-iterator.js";
14
14
  export const stringMethodNames = new Set([
@@ -356,7 +356,7 @@ function callReplaceLikeMethod(value, methodName, args, budget, callClosure, use
356
356
  return replaceWithClosure(value, search, replacement, methodName === "replaceAll", budget, callClosure, context);
357
357
  }
358
358
  async function replaceRegex(value, regex, replacement, budget, callClosure, context) {
359
- if (hasRegexPropertyOverride(regex, ["exec", "flags", ...Object.keys(regexFlagProperties)], budget))
359
+ if (getSandboxRegexPattern(regex).groups !== undefined || hasRegexPropertyOverride(regex, ["exec", "flags", ...Object.keys(regexFlagProperties)], budget))
360
360
  return regexReplace(value, regex, replacement, budget, callClosure, context);
361
361
  if (regex.flags.includes("g"))
362
362
  regex.lastIndex = 0;
@@ -410,7 +410,7 @@ export async function regexReplace(input, regex, replacementInput, budget, callC
410
410
  input = undefined;
411
411
  replacement = isSandboxClosure(replacementInput) ? replacementInput : await sandboxString(replacementInput, budget, context);
412
412
  replacementInput = undefined;
413
- if (isSandboxRegex(regex) && !hasRegexPropertyOverride(regex, ["exec", "flags", ...Object.keys(regexFlagProperties)], budget)) {
413
+ if (isSandboxRegex(regex) && getSandboxRegexPattern(regex).groups === undefined && !hasRegexPropertyOverride(regex, ["exec", "flags", ...Object.keys(regexFlagProperties)], budget)) {
414
414
  release();
415
415
  return await replaceRegex(value, regex, replacement, budget, callClosure, context);
416
416
  }
@@ -641,7 +641,7 @@ function splitNormalized(value, separator, limit, budget, parent) {
641
641
  if (match === null)
642
642
  break;
643
643
  if (match.text.length === 0)
644
- splitter.lastIndex = match.index + 1;
644
+ splitter.lastIndex = advanceStringIndex(value, match.index, splitter.flags.includes("u") || splitter.flags.includes("v"));
645
645
  endedWithZeroWidthMatch = match.text.length === 0 && match.index === value.length;
646
646
  if (match.text.length === 0 &&
647
647
  (match.index === copiedThrough || match.index === value.length))
@@ -822,7 +822,7 @@ function callMatchLikeMethod(value, methodName, args, compilation, lastIndex) {
822
822
  return match?.index ?? -1;
823
823
  }
824
824
  if (methodName === "match" && !regex.flags.includes("g"))
825
- return toMatchArray(executeRegex(regex, value, lastIndex ?? Number(regex.lastIndex)), value);
825
+ return toMatchArray(executeRegex(regex, value, lastIndex ?? Number(regex.lastIndex)), value, compilation.owner?.budget);
826
826
  if (methodName === "match")
827
827
  regex.lastIndex = 0;
828
828
  const matcher = methodName === "matchAll"
@@ -843,7 +843,7 @@ function collectRegexMatches(regex, value, all, budget, lastIndex = 0) {
843
843
  matches.push(match);
844
844
  lastIndex = match.index + match.text.length;
845
845
  if (all && match.text.length === 0)
846
- regex.lastIndex = ++lastIndex;
846
+ regex.lastIndex = lastIndex = advanceStringIndex(value, lastIndex, regex.flags.includes("u") || regex.flags.includes("v"));
847
847
  } while (all);
848
848
  return matches;
849
849
  }
@@ -16,6 +16,7 @@ export declare function isIntrinsicConstructor(value: object): boolean;
16
16
  export declare function releaseObjectPrototype(budget: Budget): void;
17
17
  export declare function getSandboxPrototype(value: object, budget?: Budget): object | null;
18
18
  export declare function hasExplicitSandboxPrototype(value: object): boolean;
19
+ export declare function hasNullObjectPrototype(value: object): boolean;
19
20
  export declare function getSandboxPropertyDescriptor(value: SandboxValue, key: PropertyKey, budget?: Budget): PropertyDescriptor | undefined;
20
21
  export declare function getSandboxDataProperty(value: SandboxValue, key: PropertyKey, budget?: Budget): SandboxValue;
21
22
  export declare function setSandboxPrototype(value: object, prototype: object | null, budget?: Budget): void;
@@ -180,6 +180,9 @@ export function getSandboxPrototype(value, budget) {
180
180
  export function hasExplicitSandboxPrototype(value) {
181
181
  return prototypes.has(value);
182
182
  }
183
+ export function hasNullObjectPrototype(value) {
184
+ return prototypes.get(value) === null && !Array.isArray(value) && !isSandboxClosure(value) && !isSandboxRegex(value);
185
+ }
183
186
  export function getSandboxPropertyDescriptor(value, key, budget) {
184
187
  const hostProperties = isSandboxClosure(value) ? value.properties : undefined;
185
188
  if (isSandboxClosure(value) && !isGuestClosure(value))
@@ -288,7 +291,7 @@ export function hasGuestObjectState(value) {
288
291
  return !intrinsicUnchanged();
289
292
  if (isLiveCapability(value))
290
293
  return true;
291
- if (functionProperties.has(value) || prototypes.has(value))
294
+ if (functionProperties.has(value) || (prototypes.has(value) && !hasNullObjectPrototype(value)))
292
295
  return true;
293
296
  if (isSandboxBox(value) || isSandboxDate(value))
294
297
  return false;
@@ -121,7 +121,7 @@ export class RegexCompileGuard {
121
121
  this.work(flags.length);
122
122
  }
123
123
  retain(pattern, valueUnits = 0) {
124
- const units = 10 + pattern.source.length + measureNode(pattern.body);
124
+ const units = measurePattern(pattern);
125
125
  if (this.ticket !== undefined) {
126
126
  this.ticket.owner.budget.resizeCompileTicket(this.ticket, units + valueUnits);
127
127
  }
@@ -139,7 +139,16 @@ export class RegexCompileGuard {
139
139
  }
140
140
  }
141
141
  export function regexCompiledData(pattern) {
142
- return (compiledData.get(pattern) ?? { units: 10 + pattern.source.length + measureNode(pattern.body) });
142
+ return (compiledData.get(pattern) ?? { units: measurePattern(pattern) });
143
+ }
144
+ function measurePattern(pattern) {
145
+ let units = 10 + pattern.source.length + measureNode(pattern.body);
146
+ if (pattern.groups !== undefined) {
147
+ units += 1;
148
+ for (const [name, indices] of Object.entries(pattern.groups))
149
+ units += name.length + 2 + indices.length;
150
+ }
151
+ return units;
143
152
  }
144
153
  function measureNode(node) {
145
154
  switch (node.type) {
@@ -148,18 +157,27 @@ function measureNode(node) {
148
157
  return 2;
149
158
  case "literal":
150
159
  return 3 + node.value.length;
160
+ case "namedBackreference":
161
+ return 3 + node.name.length;
151
162
  case "anchor":
152
163
  case "wordBoundary":
164
+ case "backreference":
153
165
  return 3;
154
166
  case "characterClass": {
155
167
  let usage = 5 + node.items.length;
168
+ if (node.operation !== undefined)
169
+ usage += 1 + node.operation.length;
170
+ if (node.strings !== undefined)
171
+ usage += 1;
156
172
  for (const item of node.items) {
157
173
  usage +=
158
174
  item.type === "character"
159
175
  ? 3 + item.value.length
160
176
  : item.type === "range"
161
177
  ? 4 + item.from.length + item.to.length
162
- : 4;
178
+ : item.type === "property" ? 4 + item.value.length + (item.strings === undefined ? 0 : 1)
179
+ : item.type === "set" ? 3 + measureNode({ type: "characterClass", ...item.value })
180
+ : item.type === "strings" ? 4 + item.values.reduce((total, value) => total + 1 + value.length, 0) : 4;
163
181
  }
164
182
  return usage;
165
183
  }
@@ -172,7 +190,11 @@ function measureNode(node) {
172
190
  return usage;
173
191
  }
174
192
  case "group":
175
- return 5 + measureNode(node.body);
193
+ return 5 + measureNode(node.body) + (node.name === undefined ? 0 : 1 + node.name.length) +
194
+ (node.modifiers === undefined ? 0 : 2 + Object.keys(node.modifiers).length);
195
+ case "lookahead":
196
+ case "lookbehind":
197
+ return 4 + measureNode(node.body);
176
198
  case "quantifier":
177
199
  return (Object.hasOwn(node, "max") ? 6 : 5) + measureNode(node.body);
178
200
  }