poe-code 14.0.51 → 14.0.53

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 (57) hide show
  1. package/dist/index.js +451 -181
  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-bash/dist/browser.js +35 -15
  6. package/packages/safe-bash/dist/browser.js.map +3 -3
  7. package/packages/safe-bash/dist/shell/arrays/state.d.ts +2 -1
  8. package/packages/safe-bash/dist/shell/arrays/state.d.ts.map +1 -1
  9. package/packages/safe-bash/dist/shell/arrays/state.js +23 -5
  10. package/packages/safe-bash/dist/shell/arrays/state.js.map +1 -1
  11. package/packages/safe-bash/dist/shell/cleanup.d.ts.map +1 -1
  12. package/packages/safe-bash/dist/shell/cleanup.js +11 -5
  13. package/packages/safe-bash/dist/shell/cleanup.js.map +1 -1
  14. package/packages/safe-bash/dist/shell/runtime.js +5 -5
  15. package/packages/safe-bash/dist/shell/runtime.js.map +1 -1
  16. package/packages/safe-js/dist/chunks/{chunk-MQU3AETK.js → chunk-4XYGVCOQ.js} +2 -2
  17. package/packages/safe-js/dist/chunks/{chunk-7AIY5C3F.js → chunk-VDFW3EKS.js} +368 -138
  18. package/packages/safe-js/dist/chunks/chunk-VDFW3EKS.js.map +7 -0
  19. package/packages/safe-js/dist/cli.js +2 -2
  20. package/packages/safe-js/dist/core.js +1 -1
  21. package/packages/safe-js/dist/graph-depth.js +10 -4
  22. package/packages/safe-js/dist/index.js +2 -2
  23. package/packages/safe-js/dist/interp/cancel.js +3 -0
  24. package/packages/safe-js/dist/interp/collection-brands.d.ts +5 -0
  25. package/packages/safe-js/dist/interp/collection-brands.js +8 -0
  26. package/packages/safe-js/dist/interp/collection-iterator.d.ts +27 -0
  27. package/packages/safe-js/dist/interp/collection-iterator.js +61 -0
  28. package/packages/safe-js/dist/interp/globals/collections.js +1 -1
  29. package/packages/safe-js/dist/interp/globals/misc.js +3 -2
  30. package/packages/safe-js/dist/interp/globals/object-array.js +2 -2
  31. package/packages/safe-js/dist/interp/globals/object.js +3 -0
  32. package/packages/safe-js/dist/interp/interpreter.js +20 -11
  33. package/packages/safe-js/dist/interp/iteration.d.ts +2 -1
  34. package/packages/safe-js/dist/interp/iteration.js +4 -1
  35. package/packages/safe-js/dist/interp/methods/array.d.ts +12 -3
  36. package/packages/safe-js/dist/interp/methods/array.js +164 -46
  37. package/packages/safe-js/dist/interp/methods/collection-iterator.d.ts +4 -0
  38. package/packages/safe-js/dist/interp/methods/collection-iterator.js +19 -0
  39. package/packages/safe-js/dist/interp/methods/map.js +3 -4
  40. package/packages/safe-js/dist/interp/methods/number.d.ts +2 -2
  41. package/packages/safe-js/dist/interp/methods/number.js +27 -46
  42. package/packages/safe-js/dist/interp/methods/set.js +3 -3
  43. package/packages/safe-js/dist/interp/patterns.d.ts +2 -0
  44. package/packages/safe-js/dist/interp/patterns.js +20 -2
  45. package/packages/safe-js/dist/interp/promise.js +1 -1
  46. package/packages/safe-js/dist/interp/string-coercion.js +4 -0
  47. package/packages/safe-js/dist/interp/values.d.ts +4 -5
  48. package/packages/safe-js/dist/interp/values.js +42 -10
  49. package/packages/safe-js/dist/snapshot/replay-data.d.ts +10 -0
  50. package/packages/safe-js/dist/snapshot/replay-data.js +32 -0
  51. package/packages/safe-js/dist/snapshot/replay-inputs.js +15 -0
  52. package/packages/safe-js/dist/snapshot/restore.js +16 -0
  53. package/packages/safe-js/dist/snapshot/serialize.d.ts +10 -1
  54. package/packages/safe-js/dist/snapshot/serialize.js +25 -9
  55. package/packages/safe-js/dist/snapshot/validation.js +14 -1
  56. package/packages/safe-js/dist/chunks/chunk-7AIY5C3F.js.map +0 -7
  57. /package/packages/safe-js/dist/chunks/{chunk-MQU3AETK.js.map → chunk-4XYGVCOQ.js.map} +0 -0
@@ -1,5 +1,8 @@
1
1
  import { createSandboxClosure, isSandboxClosure, isSandboxMap, isSandboxPromise, isSandboxSet } from "../values.js";
2
2
  import { assertCollectionMutable, enterRunningState } from "../running-state.js";
3
+ import { getSandboxDataProperty, getSandboxPrototype } from "../object-model.js";
4
+ import { sandboxNumber } from "../string-coercion.js";
5
+ const arrayLikeSources = new WeakMap();
3
6
  const activeArrayCallbacks = new WeakMap();
4
7
  const arrayMethodNames = new Set([
5
8
  "map",
@@ -51,7 +54,7 @@ export function getArrayMember(value, property, options) {
51
54
  return createSandboxClosure({
52
55
  sandbox: true,
53
56
  name: `Array#${property}`,
54
- call: (args, context) => callArrayMethod(value, property, args, options, context?.stack ?? [])
57
+ call: (args, context) => callArrayMethod(context?.thisValue, property, args, { ...options, context }, context?.stack ?? [])
55
58
  });
56
59
  }
57
60
  return undefined;
@@ -69,29 +72,94 @@ function getArrayIndex(property) {
69
72
  export function isArrayMethodName(property) {
70
73
  return typeof property === "string" && arrayMethodNames.has(property);
71
74
  }
72
- export async function callArrayMethod(value, methodName, args, options, stack = []) {
73
- if (isMutatingArrayMethod(methodName)) {
74
- assertCollectionMutable(value);
75
- }
76
- if (isCallbackArrayMethod(methodName)) {
77
- let callbackState = activeArrayCallbacks.get(value);
78
- if (callbackState === undefined) {
79
- callbackState = { depth: 0, leave: enterRunningState(value) };
80
- activeArrayCallbacks.set(value, callbackState);
81
- }
82
- callbackState.depth += 1;
83
- try {
84
- return await callArrayMethodUnlocked(value, methodName, args, options, stack);
85
- }
86
- finally {
87
- callbackState.depth -= 1;
88
- if (callbackState.depth === 0) {
89
- activeArrayCallbacks.delete(value);
90
- callbackState.leave();
75
+ export async function callArrayMethod(receiver, methodName, args, options, stack = []) {
76
+ if (receiver === null || receiver === undefined)
77
+ throw new TypeError("Array method requires a receiver.");
78
+ if (typeof receiver !== "object")
79
+ throw new TypeError("Object primitive boxing is not supported.");
80
+ const retainedReceiver = {};
81
+ options.budget.setRetainedValues(retainedReceiver, () => [receiver]);
82
+ try {
83
+ const value = Array.isArray(receiver) || methodName === "concat"
84
+ ? receiver
85
+ : await arrayLikeView(receiver, options);
86
+ if (isMutatingArrayMethod(methodName)) {
87
+ assertCollectionMutable(receiver);
88
+ }
89
+ if (isCallbackArrayMethod(methodName)) {
90
+ let callbackState = activeArrayCallbacks.get(receiver);
91
+ if (callbackState === undefined) {
92
+ callbackState = { depth: 0, leave: enterRunningState(receiver) };
93
+ activeArrayCallbacks.set(receiver, callbackState);
94
+ }
95
+ callbackState.depth += 1;
96
+ try {
97
+ const result = await callArrayMethodUnlocked(value, methodName, args, options, stack);
98
+ return result === value ? receiver : result;
99
+ }
100
+ finally {
101
+ callbackState.depth -= 1;
102
+ if (callbackState.depth === 0) {
103
+ activeArrayCallbacks.delete(receiver);
104
+ callbackState.leave();
105
+ }
91
106
  }
92
107
  }
108
+ const result = await callArrayMethodUnlocked(value, methodName, args, options, stack);
109
+ return result === value ? receiver : result;
93
110
  }
94
- return callArrayMethodUnlocked(value, methodName, args, options, stack);
111
+ finally {
112
+ options.budget.setRetainedValues(retainedReceiver, undefined);
113
+ }
114
+ }
115
+ async function arrayLikeView(receiver, options) {
116
+ const rawLength = options.context?.getProperty !== undefined
117
+ ? options.context.getProperty(receiver, "length")
118
+ : getSandboxDataProperty(receiver, "length", options.budget);
119
+ const number = await sandboxNumber(rawLength, options.budget, options.context);
120
+ const length = Number.isNaN(number) || number <= 0 ? 0 : Math.min(Math.trunc(number), Number.MAX_SAFE_INTEGER);
121
+ // The view is internal only: callbacks, results, and accounting keep the guest receiver.
122
+ const view = new Proxy(Object.create(null), {
123
+ get: (_target, key) => {
124
+ options.budget.visitNode();
125
+ if (key === "length")
126
+ return length;
127
+ if (typeof key !== "string")
128
+ return undefined;
129
+ return options.context?.getProperty !== undefined
130
+ ? options.context.getProperty(receiver, key)
131
+ : getSandboxDataProperty(receiver, key, options.budget);
132
+ },
133
+ has: (_target, key) => {
134
+ options.budget.visitNode();
135
+ if (typeof key === "string" && options.hasProperty !== undefined)
136
+ return options.hasProperty(receiver, key);
137
+ for (let current = receiver; current !== null; current = getSandboxPrototype(current, options.budget)) {
138
+ options.budget.visitNode();
139
+ if (Object.hasOwn(current, key))
140
+ return true;
141
+ }
142
+ return false;
143
+ },
144
+ set: (_target, key, entry) => {
145
+ options.budget.visitNode();
146
+ if (typeof key !== "string")
147
+ throw new TypeError("Array indices must be string keys.");
148
+ if (options.setProperty !== undefined)
149
+ options.setProperty(receiver, key, entry);
150
+ else if (!Reflect.set(receiver, key, entry))
151
+ throw new TypeError(`Cannot assign property '${key}'.`);
152
+ return true;
153
+ },
154
+ deleteProperty: (_target, key) => {
155
+ options.budget.visitNode();
156
+ if (typeof key === "string" && options.deleteProperty !== undefined)
157
+ return options.deleteProperty(receiver, key);
158
+ return Reflect.deleteProperty(receiver, key);
159
+ }
160
+ });
161
+ arrayLikeSources.set(view, receiver);
162
+ return view;
95
163
  }
96
164
  async function callArrayMethodUnlocked(value, methodName, args, options, stack) {
97
165
  switch (methodName) {
@@ -130,8 +198,22 @@ async function callArrayMethodUnlocked(value, methodName, args, options, stack)
130
198
  return Reflect.apply(Array.prototype.lastIndexOf, value, [...args]);
131
199
  case "join":
132
200
  return options.budget.allocateString(Reflect.apply(Array.prototype.join, value, [...args]));
133
- case "slice":
134
- return budgetProducedValue(Reflect.apply(Array.prototype.slice, value, [...args]), options.budget);
201
+ case "slice": {
202
+ const length = value.length;
203
+ const start = toIntegerOrInfinity(await sandboxNumber(args[0], options.budget, options.context));
204
+ const end = args[1] === undefined ? length : toIntegerOrInfinity(await sandboxNumber(args[1], options.budget, options.context));
205
+ const first = start < 0 ? Math.max(length + start, 0) : Math.min(start, length);
206
+ const final = end < 0 ? Math.max(length + end, 0) : Math.min(end, length);
207
+ const count = Math.max(final - first, 0);
208
+ options.budget.allocateArrayLength(count);
209
+ const result = new Array(count);
210
+ for (let index = 0; index < count; index += 1) {
211
+ options.budget.visitNode();
212
+ if (first + index in value)
213
+ result[index] = value[first + index];
214
+ }
215
+ return budgetProducedValue(result, options.budget);
216
+ }
135
217
  case "concat":
136
218
  return budgetProducedValue(Reflect.apply(Array.prototype.concat, value, [...args]), options.budget);
137
219
  case "splice": {
@@ -152,7 +234,7 @@ async function callArrayMethodUnlocked(value, methodName, args, options, stack)
152
234
  return budgetProducedValue(Reflect.apply(Array.prototype.at, value, [...args]), options.budget);
153
235
  case "sort":
154
236
  if (args[0] === undefined) {
155
- value.sort();
237
+ Reflect.apply(Array.prototype.sort, value, []);
156
238
  budgetProducedValue(value, options.budget);
157
239
  return value;
158
240
  }
@@ -160,11 +242,17 @@ async function callArrayMethodUnlocked(value, methodName, args, options, stack)
160
242
  budgetProducedValue(value, options.budget);
161
243
  return value;
162
244
  case "reverse":
163
- value.reverse();
245
+ Reflect.apply(Array.prototype.reverse, value, []);
164
246
  budgetProducedValue(value, options.budget);
165
247
  return value;
166
248
  case "toSorted": {
167
- const result = Array.from(value);
249
+ const length = value.length;
250
+ options.budget.allocateArrayLength(length);
251
+ const result = new Array(length);
252
+ for (let index = 0; index < length; index += 1) {
253
+ options.budget.visitNode();
254
+ result[index] = value[index];
255
+ }
168
256
  if (args[0] === undefined) {
169
257
  result.sort();
170
258
  }
@@ -174,13 +262,33 @@ async function callArrayMethodUnlocked(value, methodName, args, options, stack)
174
262
  return budgetProducedValue(result, options.budget);
175
263
  }
176
264
  case "toReversed": {
177
- const result = Array.from(value);
178
- result.reverse();
265
+ const length = value.length;
266
+ options.budget.allocateArrayLength(length);
267
+ const result = new Array(length);
268
+ for (let index = 0; index < length; index += 1) {
269
+ options.budget.visitNode();
270
+ result[index] = value[length - index - 1];
271
+ }
179
272
  return budgetProducedValue(result, options.budget);
180
273
  }
181
274
  case "toSpliced": {
182
- const result = Array.from(value);
183
- Reflect.apply(Array.prototype.splice, result, [...args]);
275
+ const length = value.length;
276
+ const start = toIntegerOrInfinity(await sandboxNumber(args[0], options.budget, options.context));
277
+ const first = start < 0 ? Math.max(length + start, 0) : Math.min(start, length);
278
+ const inserted = Math.max(args.length - 2, 0);
279
+ const deleted = args.length === 0 ? 0 : args.length === 1 ? length - first
280
+ : Math.min(Math.max(toIntegerOrInfinity(await sandboxNumber(args[1], options.budget, options.context)), 0), length - first);
281
+ const resultLength = length + inserted - deleted;
282
+ if (resultLength > Number.MAX_SAFE_INTEGER)
283
+ throw new TypeError("Array-like length exceeds the safe integer limit.");
284
+ options.budget.allocateArrayLength(resultLength);
285
+ const result = new Array(resultLength);
286
+ for (let index = 0; index < resultLength; index += 1) {
287
+ options.budget.visitNode();
288
+ result[index] = index < first ? value[index]
289
+ : index < first + inserted ? args[index - first + 2]
290
+ : value[index - inserted + deleted];
291
+ }
184
292
  return budgetProducedValue(result, options.budget);
185
293
  }
186
294
  case "with": {
@@ -197,41 +305,46 @@ async function callArrayMethodUnlocked(value, methodName, args, options, stack)
197
305
  result[position] =
198
306
  position === actualIndex
199
307
  ? args[1]
200
- : Object.hasOwn(value, position)
201
- ? value[position]
202
- : undefined;
308
+ : Array.isArray(value) && !Object.hasOwn(value, position)
309
+ ? undefined
310
+ : value[position];
203
311
  }
204
312
  return budgetProducedValue(result, options.budget);
205
313
  }
206
314
  case "push": {
207
- appendArrayValues(value, args);
208
- const nextLength = value.length;
315
+ const nextLength = appendArrayValues(value, args);
209
316
  budgetProducedValue(value, options.budget);
210
317
  return nextLength;
211
318
  }
212
319
  case "pop":
213
- return budgetProducedValue(value.pop(), options.budget);
320
+ return budgetProducedValue(Reflect.apply(Array.prototype.pop, value, []), options.budget);
214
321
  case "shift":
215
- return budgetProducedValue(value.shift(), options.budget);
322
+ return budgetProducedValue(Reflect.apply(Array.prototype.shift, value, []), options.budget);
216
323
  case "unshift": {
217
- prependArrayValues(value, args);
218
- const nextLength = value.length;
324
+ const nextLength = prependArrayValues(value, args);
219
325
  budgetProducedValue(value, options.budget);
220
326
  return nextLength;
221
327
  }
222
328
  }
223
329
  }
224
330
  function appendArrayValues(target, values) {
331
+ let length = target.length;
332
+ if (length + values.length > Number.MAX_SAFE_INTEGER)
333
+ throw new TypeError("Array-like length exceeds the safe integer limit.");
225
334
  for (const value of values) {
226
- target.push(value);
335
+ target[length++] = value;
227
336
  }
337
+ target.length = length;
338
+ return length;
228
339
  }
229
340
  function prependArrayValues(target, values) {
230
341
  const originalLength = target.length;
231
- target.length = originalLength + values.length;
232
- for (let index = originalLength - 1; index >= 0; index -= 1) {
342
+ const length = originalLength + values.length;
343
+ if (length > Number.MAX_SAFE_INTEGER)
344
+ throw new TypeError("Array-like length exceeds the safe integer limit.");
345
+ for (let index = values.length === 0 ? -1 : originalLength - 1; index >= 0; index -= 1) {
233
346
  const targetIndex = index + values.length;
234
- if (Object.hasOwn(target, index)) {
347
+ if (index in target) {
235
348
  target[targetIndex] = target[index];
236
349
  }
237
350
  else {
@@ -241,6 +354,8 @@ function prependArrayValues(target, values) {
241
354
  for (let index = 0; index < values.length; index += 1) {
242
355
  target[index] = values[index];
243
356
  }
357
+ target.length = length;
358
+ return length;
244
359
  }
245
360
  function isCallbackArrayMethod(methodName) {
246
361
  return (methodName === "map" ||
@@ -276,6 +391,7 @@ function getRequiredCallback(methodName, value) {
276
391
  }
277
392
  async function mapArray(value, callback, options, stack, thisValue) {
278
393
  const length = value.length;
394
+ options.budget.allocateArrayLength(length);
279
395
  const result = new Array(length);
280
396
  options.budget.setRetainedValues(result, () => [result]);
281
397
  try {
@@ -415,7 +531,7 @@ async function reduceFromLeft(value, callback, accumulator, startIndex, length,
415
531
  if (!(index in value)) {
416
532
  continue;
417
533
  }
418
- current = await options.callClosure(callback, [current, value[index], index, value], stack);
534
+ current = await options.callClosure(callback, [current, value[index], index, arrayLikeSources.get(value) ?? value], stack);
419
535
  }
420
536
  return current;
421
537
  }
@@ -433,7 +549,7 @@ async function reduceFromRight(value, callback, accumulator, startIndex, length,
433
549
  if (!(index in value)) {
434
550
  continue;
435
551
  }
436
- current = await options.callClosure(callback, [current, value[index], index, value], stack);
552
+ current = await options.callClosure(callback, [current, value[index], index, arrayLikeSources.get(value) ?? value], stack);
437
553
  }
438
554
  return current;
439
555
  }
@@ -552,7 +668,7 @@ async function compareEntries(left, right, comparator, options, stack) {
552
668
  return Number.isNaN(result) ? 0 : result;
553
669
  }
554
670
  async function callArrayCallback(callback, value, index, array, options, stack, thisValue) {
555
- return options.callClosure(callback, [value, index, array], stack, thisValue);
671
+ return options.callClosure(callback, [value, index, arrayLikeSources.get(array) ?? array], stack, thisValue);
556
672
  }
557
673
  function findNextDefinedIndex(value, startIndex, direction, length, budget) {
558
674
  for (let index = startIndex; direction > 0 ? index < length : index >= 0; index += direction) {
@@ -564,6 +680,8 @@ function findNextDefinedIndex(value, startIndex, direction, length, budget) {
564
680
  return -1;
565
681
  }
566
682
  function budgetProducedValue(value, budget) {
683
+ if (typeof value === "object" && value !== null)
684
+ value = arrayLikeSources.get(value) ?? value;
567
685
  allocateProducedValue(value, budget, new WeakSet());
568
686
  return value;
569
687
  }
@@ -0,0 +1,4 @@
1
+ import type { Budget } from "../budget.js";
2
+ import { type SandboxCollectionIterator } from "../collection-iterator.js";
3
+ import { type SandboxValue } from "../values.js";
4
+ export declare function getCollectionIteratorMember(target: SandboxCollectionIterator, property: string | number, budget: Budget): SandboxValue;
@@ -0,0 +1,19 @@
1
+ import { collectionIteratorState, isSandboxCollectionIterator, nextCollectionIterator } from "../collection-iterator.js";
2
+ import { createSandboxClosure } from "../values.js";
3
+ export function getCollectionIteratorMember(target, property, budget) {
4
+ if (Object.hasOwn(target, property))
5
+ return target[property];
6
+ if (property !== "next")
7
+ return undefined;
8
+ const { collectionKind } = collectionIteratorState(target);
9
+ return createSandboxClosure({
10
+ sandbox: true,
11
+ name: "next",
12
+ call: (_args, context) => {
13
+ const receiver = context?.thisValue;
14
+ if (!isSandboxCollectionIterator(receiver) || collectionIteratorState(receiver).collectionKind !== collectionKind)
15
+ throw new TypeError("Iterator next requires a matching collection iterator receiver.");
16
+ return nextCollectionIterator(receiver, budget);
17
+ }
18
+ });
19
+ }
@@ -1,6 +1,7 @@
1
- import { allocateProducedSandboxValue, createSandboxClosure, isSandboxClosure, isSandboxMap } from "../values.js";
1
+ import { createSandboxClosure, isSandboxClosure, isSandboxMap } from "../values.js";
2
2
  import { assertCollectionMutable } from "../running-state.js";
3
3
  import { enterKeyedCollectionCallback, updateKeyedCollectionCallbacks } from "./collection-callback.js";
4
+ import { createSandboxCollectionIterator } from "../collection-iterator.js";
4
5
  const mapMethodNames = new Set([
5
6
  "get",
6
7
  "set",
@@ -80,10 +81,8 @@ export async function callMapMethod(target, methodName, args, options, stack = [
80
81
  return undefined;
81
82
  }
82
83
  case "keys":
83
- return allocateProducedSandboxValue([...target.entries.keys()], options.budget);
84
84
  case "values":
85
- return allocateProducedSandboxValue([...target.entries.values()], options.budget);
86
85
  case "entries":
87
- return allocateProducedSandboxValue([...target.entries].map(([key, value]) => [key, value]), options.budget);
86
+ return createSandboxCollectionIterator(target, methodName);
88
87
  }
89
88
  }
@@ -1,6 +1,6 @@
1
1
  import { Budget } from "../budget.js";
2
- import { type SandboxValue } from "../values.js";
2
+ import { type SandboxCallContext, type SandboxValue } from "../values.js";
3
3
  export type NumberMethodName = "toExponential" | "toFixed" | "toPrecision" | "toString";
4
4
  export declare function getNumberMember(property: string | number, budget: Budget): SandboxValue | undefined;
5
5
  export declare function isNumberMethodName(property: string | number): property is NumberMethodName;
6
- export declare function callNumberMethod(value: SandboxValue, methodName: NumberMethodName, args: readonly SandboxValue[], budget: Budget): string;
6
+ export declare function callNumberMethod(value: SandboxValue, methodName: NumberMethodName, args: readonly SandboxValue[], budget: Budget, context?: SandboxCallContext): string | Promise<string>;
@@ -1,3 +1,4 @@
1
+ import { sandboxNumber } from "../string-coercion.js";
1
2
  import { createSandboxClosure } from "../values.js";
2
3
  const numberMethodNames = new Set([
3
4
  "toExponential",
@@ -12,65 +13,45 @@ export function getNumberMember(property, budget) {
12
13
  return createSandboxClosure({
13
14
  sandbox: true,
14
15
  name: `Number#${property}`,
15
- call: (args, context) => callNumberMethod(context?.thisValue, property, args, budget)
16
+ call: (args, context) => callNumberMethod(context?.thisValue, property, args, budget, context)
16
17
  });
17
18
  }
18
19
  export function isNumberMethodName(property) {
19
20
  return typeof property === "string" && numberMethodNames.has(property);
20
21
  }
21
- export function callNumberMethod(value, methodName, args, budget) {
22
+ export function callNumberMethod(value, methodName, args, budget, context) {
22
23
  if (typeof value !== "number") {
23
24
  throw new TypeError(`Number#${methodName} requires a number receiver.`);
24
25
  }
25
- return budget.allocateString(callNativeNumberMethod(value, methodName, args));
26
- }
27
- function callNativeNumberMethod(value, methodName, args) {
28
- switch (methodName) {
29
- case "toString":
30
- return value.toString(asValidatedRadix(args[0]));
31
- case "toExponential":
32
- return args[0] === undefined
33
- ? value.toExponential()
34
- : value.toExponential(asValidatedFractionDigits(args[0], methodName));
35
- case "toFixed":
36
- return value.toFixed(asValidatedFractionDigits(args[0], methodName));
37
- case "toPrecision":
38
- return args[0] === undefined
39
- ? value.toPrecision()
40
- : value.toPrecision(asValidatedPrecision(args[0]));
41
- }
42
- }
43
- function asValidatedRadix(value) {
44
- if (value === undefined) {
45
- return undefined;
46
- }
47
- const radix = toIntegerOrInfinity(value);
48
- if (radix < 2 || radix > 36) {
49
- throw new RangeError("Number#toString radix must be between 2 and 36.");
26
+ const argument = args[0];
27
+ if (argument !== null && typeof argument === "object") {
28
+ return formatObjectArgument(value, methodName, argument, budget, context);
50
29
  }
51
- return radix;
30
+ return formatNumber(value, methodName, argument === undefined ? undefined : Number(argument), budget);
52
31
  }
53
- function asValidatedFractionDigits(value, methodName) {
54
- const digits = toIntegerOrInfinity(value);
55
- if (digits < 0 || digits > 100) {
56
- throw new RangeError(`Number#${methodName} digits must be between 0 and 100.`);
32
+ async function formatObjectArgument(value, methodName, argument, budget, context) {
33
+ const retainedArgument = {};
34
+ budget.setRetainedValues(retainedArgument, () => [argument]);
35
+ try {
36
+ const number = await sandboxNumber(argument, budget, context);
37
+ return formatNumber(value, methodName, number, budget);
57
38
  }
58
- return digits;
59
- }
60
- function asValidatedPrecision(value) {
61
- const precision = toIntegerOrInfinity(value);
62
- if (precision < 1 || precision > 100) {
63
- throw new RangeError("Number#toPrecision precision must be between 1 and 100.");
39
+ finally {
40
+ budget.setRetainedValues(retainedArgument, undefined);
64
41
  }
65
- return precision;
66
42
  }
67
- function toIntegerOrInfinity(value) {
68
- const number = Number(value);
69
- if (Number.isNaN(number) || Object.is(number, 0) || Object.is(number, -0)) {
70
- return 0;
43
+ function formatNumber(value, methodName, argument, budget) {
44
+ let result;
45
+ try {
46
+ result = value[methodName](argument);
71
47
  }
72
- if (!Number.isFinite(number)) {
73
- return number;
48
+ catch (error) {
49
+ if (!(error instanceof RangeError))
50
+ throw error;
51
+ const detail = methodName === "toString" ? "radix must be between 2 and 36."
52
+ : methodName === "toPrecision" ? "precision must be between 1 and 100."
53
+ : "digits must be between 0 and 100.";
54
+ throw new RangeError(`Number#${methodName} ${detail}`);
74
55
  }
75
- return Math.trunc(number);
56
+ return budget.allocateString(result);
76
57
  }
@@ -1,6 +1,7 @@
1
- import { allocateProducedSandboxValue, createSandboxClosure, isSandboxClosure, isSandboxSet } from "../values.js";
1
+ import { createSandboxClosure, isSandboxClosure, isSandboxSet } from "../values.js";
2
2
  import { assertCollectionMutable } from "../running-state.js";
3
3
  import { enterKeyedCollectionCallback, updateKeyedCollectionCallbacks } from "./collection-callback.js";
4
+ import { createSandboxCollectionIterator } from "../collection-iterator.js";
4
5
  const setMethodNames = new Set([
5
6
  "add",
6
7
  "has",
@@ -77,8 +78,7 @@ export async function callSetMethod(target, methodName, args, options, stack = [
77
78
  }
78
79
  case "keys":
79
80
  case "values":
80
- return allocateProducedSandboxValue([...target.values], options.budget);
81
81
  case "entries":
82
- return allocateProducedSandboxValue([...target.values].map((value) => [value, value]), options.budget);
82
+ return createSandboxCollectionIterator(target, methodName);
83
83
  }
84
84
  }
@@ -1,6 +1,7 @@
1
1
  import type { AssignmentPattern, MemberExpression, ParseResult, RestElement, VariableDeclarationKind, VariableDeclarator } from "../parse.js";
2
2
  import type { AsyncEvaluationResult } from "./async.js";
3
3
  import type { Scope } from "./scope.js";
4
+ import type { Budget } from "./budget.js";
4
5
  import { type SandboxValue } from "./values.js";
5
6
  type Pattern = VariableDeclarator["id"] | AssignmentPattern | MemberExpression | RestElement;
6
7
  export type PatternTarget = {
@@ -10,6 +11,7 @@ export type PatternTarget = {
10
11
  assign: true;
11
12
  };
12
13
  export type PatternContext = {
14
+ budget?: Budget;
13
15
  evaluate(node: ParseResult): Promise<AsyncEvaluationResult>;
14
16
  toPropertyKey(value: SandboxValue): Promise<string>;
15
17
  getProperty(value: SandboxValue, key: string | number): SandboxValue;
@@ -1,3 +1,4 @@
1
+ import { isSandboxCollectionIterator, nextCollectionIterator } from "./collection-iterator.js";
1
2
  import { isSandboxMap, isSandboxSet, ownEnumerableSandboxEntries } from "./values.js";
2
3
  export async function bindPattern(pattern, value, target, scope, context) {
3
4
  switch (pattern.type) {
@@ -46,13 +47,30 @@ async function bindAssignmentPattern(pattern, value, target, scope, context) {
46
47
  return bindPattern(pattern.left, defaultValue.value, target, scope, context);
47
48
  }
48
49
  async function bindArrayPattern(pattern, value, target, scope, context) {
49
- const values = getArrayPatternValues(value);
50
+ const iterator = isSandboxCollectionIterator(value) ? value : undefined;
51
+ const values = iterator === undefined ? getArrayPatternValues(value) : undefined;
50
52
  for (let index = 0; index < pattern.elements.length; index += 1) {
51
53
  const element = pattern.elements[index];
52
54
  if (element === null) {
55
+ if (iterator !== undefined)
56
+ nextCollectionIterator(iterator, context.budget);
53
57
  continue;
54
58
  }
55
- const elementValue = element.type === "RestElement" ? values.slice(index) : values[index];
59
+ let elementValue;
60
+ if (iterator === undefined) {
61
+ elementValue = element.type === "RestElement" ? values.slice(index) : values[index];
62
+ }
63
+ else if (element.type === "RestElement") {
64
+ const rest = [];
65
+ for (let entry = nextCollectionIterator(iterator, context.budget); !entry.done; entry = nextCollectionIterator(iterator, context.budget)) {
66
+ context.budget?.allocateArrayLength(rest.length + 1);
67
+ rest.push(entry.value);
68
+ }
69
+ elementValue = rest;
70
+ }
71
+ else {
72
+ elementValue = nextCollectionIterator(iterator, context.budget).value;
73
+ }
56
74
  const binding = await bindPattern(element, elementValue, target, scope, context);
57
75
  if (!binding.ok) {
58
76
  return binding;
@@ -337,7 +337,7 @@ async function settleIterable(iterable, method, budget, constructor, context) {
337
337
  const promiseResolve = readPromiseReceiverProperty(constructor, "resolve", prototype);
338
338
  if (!isSandboxClosure(promiseResolve))
339
339
  throw new TypeError("Promise constructor requires a callable resolve.");
340
- const iterator = getSandboxIterator(iterable);
340
+ const iterator = getSandboxIterator(iterable, budget);
341
341
  if (iterator === undefined)
342
342
  throw new TypeError("Promise helpers require an iterable.");
343
343
  let index = 0;
@@ -4,6 +4,7 @@ import { invokeBuiltinClosure } from "./builtin-call.js";
4
4
  import { float32Storage, isFloat32Array } from "./float32.js";
5
5
  import { assertSandboxDataDepth } from "../graph-depth.js";
6
6
  import { isGuestHostObject } from "./host-capabilities.js";
7
+ import { collectionIteratorState, isSandboxCollectionIterator } from "./collection-iterator.js";
7
8
  import { getGuestFunctionProperties, getSandboxPrototype, hasExplicitSandboxPrototype, isGuestClosure } from "./object-model.js";
8
9
  import { getRegexMember } from "./methods/regex.js";
9
10
  import { isSandboxClosure, isSandboxPromise, isSandboxMap, isSandboxSet, isSandboxRegex, isSandboxGenerator } from "./values.js";
@@ -65,6 +66,7 @@ function conversionHook(value, name, budget) {
65
66
  isSandboxClosure(value) ||
66
67
  isSandboxMap(value) ||
67
68
  isSandboxSet(value) ||
69
+ isSandboxCollectionIterator(value) ||
68
70
  isSandboxPromise(value) ||
69
71
  isSandboxRegex(value) ||
70
72
  isSandboxGenerator(value) ||
@@ -97,6 +99,8 @@ async function defaultToString(value, budget, context, joining) {
97
99
  return "[object Map]";
98
100
  if (isSandboxSet(value))
99
101
  return "[object Set]";
102
+ if (isSandboxCollectionIterator(value))
103
+ return collectionIteratorState(value).collectionKind === "map" ? "[object Map Iterator]" : "[object Set Iterator]";
100
104
  if (isSandboxGenerator(value))
101
105
  return "[object Generator]";
102
106
  if (isSandboxRegex(value)) {
@@ -1,18 +1,19 @@
1
+ import { sandboxMapBrand, sandboxSetBrand } from "./collection-brands.js";
2
+ import { type SandboxCollectionIterator } from "./collection-iterator.js";
1
3
  import type { Budget, CompileTicket } from "./budget.js";
2
4
  import { CompileScope } from "./regex/compile-guard.js";
3
5
  import type { GeneratorChannel } from "./generator.js";
4
6
  import { type RegexPattern } from "./regex/parse.js";
5
7
  export { createSandboxArguments, isSandboxArguments } from "./arguments.js";
8
+ export { isSandboxMap, isSandboxSet } from "./collection-brands.js";
6
9
  declare const sandboxClosureBrand: unique symbol;
7
10
  declare const sandboxGeneratorBrand: unique symbol;
8
- declare const sandboxMapBrand: unique symbol;
9
11
  declare const sandboxPromiseBrand: unique symbol;
10
12
  declare const sandboxRegexBrand: unique symbol;
11
13
  declare const sandboxRegexPattern: unique symbol;
12
- declare const sandboxSetBrand: unique symbol;
13
14
  declare const sandboxRetainedValues: unique symbol;
14
15
  export type SandboxPrimitive = string | number | boolean | null | undefined;
15
- export type SandboxValue = SandboxPrimitive | Date | Float32Array | SandboxObject | SandboxArray | SandboxClosure | SandboxGenerator | SandboxMap | SandboxSet | SandboxPromise | SandboxRegex;
16
+ export type SandboxValue = SandboxPrimitive | Date | Float32Array | SandboxObject | SandboxArray | SandboxClosure | SandboxGenerator | SandboxCollectionIterator | SandboxMap | SandboxSet | SandboxPromise | SandboxRegex;
16
17
  export type SandboxObject = {
17
18
  [key: string]: SandboxValue;
18
19
  };
@@ -123,9 +124,7 @@ export declare function createSandboxSet(values?: Iterable<SandboxValue>): Sandb
123
124
  export declare function createSandboxRegex(source: string, flags?: string, lastIndex?: number, compilation?: CompileScope): SandboxRegex;
124
125
  export declare function getSandboxRegexPattern(regex: SandboxRegex): RegexPattern;
125
126
  export declare function isSandboxClosure(value: unknown): value is SandboxClosure;
126
- export declare function isSandboxMap(value: unknown): value is SandboxMap;
127
127
  export declare function isSandboxPromise(value: unknown): value is SandboxPromise;
128
- export declare function isSandboxSet(value: unknown): value is SandboxSet;
129
128
  export declare function isSandboxGenerator(value: unknown): value is SandboxGenerator;
130
129
  export declare function isSandboxRegex(value: unknown): value is SandboxRegex;
131
130
  export declare function deepCopyToSandbox(value: unknown): SandboxValue;