poe-code 14.0.52 → 14.0.54

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 (28) hide show
  1. package/dist/index.js +187 -99
  2. package/dist/index.js.map +3 -3
  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-DVWTNFTV.js → chunk-L7PHF62H.js} +182 -99
  17. package/packages/safe-js/dist/chunks/{chunk-DVWTNFTV.js.map → chunk-L7PHF62H.js.map} +3 -3
  18. package/packages/safe-js/dist/chunks/{chunk-XIBH4BH2.js → chunk-Z55VACOY.js} +2 -2
  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/index.js +2 -2
  22. package/packages/safe-js/dist/interp/interpreter.js +5 -1
  23. package/packages/safe-js/dist/interp/methods/array.d.ts +12 -3
  24. package/packages/safe-js/dist/interp/methods/array.js +164 -46
  25. package/packages/safe-js/dist/interp/methods/number.d.ts +2 -2
  26. package/packages/safe-js/dist/interp/methods/number.js +27 -46
  27. package/packages/safe-js/dist/interp/methods/string.js +17 -3
  28. /package/packages/safe-js/dist/chunks/{chunk-XIBH4BH2.js.map → chunk-Z55VACOY.js.map} +0 -0
@@ -27,7 +27,7 @@ import {
27
27
  validateMigrationSemantics,
28
28
  validateSnapshotData,
29
29
  validateSnapshotMigration
30
- } from "./chunk-DVWTNFTV.js";
30
+ } from "./chunk-L7PHF62H.js";
31
31
  import {
32
32
  createFileSystem,
33
33
  createNodeFileSystemAdapterRegistry,
@@ -8245,4 +8245,4 @@ export {
8245
8245
  parseMcpConfig,
8246
8246
  makeMcpModule
8247
8247
  };
8248
- //# sourceMappingURL=chunk-XIBH4BH2.js.map
8248
+ //# sourceMappingURL=chunk-Z55VACOY.js.map
@@ -22,7 +22,7 @@ import {
22
22
  resolveFsConfig,
23
23
  splitFrontmatter,
24
24
  withBrokenPipeGuard
25
- } from "./chunks/chunk-XIBH4BH2.js";
25
+ } from "./chunks/chunk-Z55VACOY.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-DVWTNFTV.js";
37
+ } from "./chunks/chunk-L7PHF62H.js";
38
38
  import "./chunks/chunk-UXMKEUH3.js";
39
39
  import "./chunks/chunk-5DYEOCT5.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-DVWTNFTV.js";
8
+ } from "./chunks/chunk-L7PHF62H.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-XIBH4BH2.js";
29
+ } from "./chunks/chunk-Z55VACOY.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-DVWTNFTV.js";
53
+ } from "./chunks/chunk-L7PHF62H.js";
54
54
  import "./chunks/chunk-UXMKEUH3.js";
55
55
  import "./chunks/chunk-5DYEOCT5.js";
56
56
  import "./chunks/chunk-4YIDX44G.js";
@@ -2483,7 +2483,7 @@ async function evaluateNumberMethodCall(node, target, methodName, context) {
2483
2483
  return {
2484
2484
  kind: "normal",
2485
2485
  hasValue: true,
2486
- value: callNumberMethod(target, methodName, args.value, context.budget)
2486
+ value: await callNumberMethod(target, methodName, args.value, context.budget, createCoercionContext(context))
2487
2487
  };
2488
2488
  }
2489
2489
  catch (error) {
@@ -2496,6 +2496,10 @@ async function evaluateNumberMethodCall(node, target, methodName, context) {
2496
2496
  function createArrayMethodOptions(context) {
2497
2497
  return {
2498
2498
  budget: context.budget,
2499
+ context: createCoercionContext(context),
2500
+ hasProperty: (value, property) => hasSandboxProperty(value, property, context),
2501
+ setProperty: (value, property, entry) => setSandboxProperty(value, property, entry, context.budget),
2502
+ deleteProperty: deleteSandboxProperty,
2499
2503
  callClosure: (closure, args, stack, thisValue) => invokeSandboxClosure(closure, args, context, stack, undefined, thisValue)
2500
2504
  };
2501
2505
  }
@@ -1,10 +1,19 @@
1
1
  import { Budget } from "../budget.js";
2
- import { type SandboxArray, type SandboxClosure, type SandboxValue } from "../values.js";
2
+ import { type SandboxArray, type SandboxCallContext, type SandboxClosure, type SandboxObject, type SandboxValue } from "../values.js";
3
+ type ArrayLikeValue = SandboxArray | (SandboxObject & {
4
+ [index: number]: SandboxValue;
5
+ length: number;
6
+ });
3
7
  export type ArrayMethodName = "map" | "filter" | "find" | "findIndex" | "findLast" | "findLastIndex" | "some" | "every" | "reduce" | "reduceRight" | "forEach" | "flatMap" | "flat" | "includes" | "indexOf" | "lastIndexOf" | "join" | "slice" | "concat" | "splice" | "fill" | "copyWithin" | "at" | "sort" | "reverse" | "toSorted" | "toReversed" | "toSpliced" | "with" | "push" | "pop" | "shift" | "unshift";
4
8
  export type ArrayMethodOptions = {
5
9
  budget: Budget;
10
+ context?: SandboxCallContext;
11
+ hasProperty?: (value: SandboxValue, property: string) => boolean;
12
+ setProperty?: (value: SandboxValue, property: string, entry: SandboxValue) => void;
13
+ deleteProperty?: (value: SandboxValue, property: string | number) => boolean;
6
14
  callClosure: (closure: SandboxClosure, args: readonly SandboxValue[], stack: readonly string[], thisValue?: SandboxValue) => Promise<SandboxValue>;
7
15
  };
8
- export declare function getArrayMember(value: SandboxArray, property: string | number, options: ArrayMethodOptions): SandboxValue | undefined;
16
+ export declare function getArrayMember(value: ArrayLikeValue, property: string | number, options: ArrayMethodOptions): SandboxValue | undefined;
9
17
  export declare function isArrayMethodName(property: string | number): property is ArrayMethodName;
10
- export declare function callArrayMethod(value: SandboxArray, methodName: ArrayMethodName, args: readonly SandboxValue[], options: ArrayMethodOptions, stack?: readonly string[]): Promise<SandboxValue>;
18
+ export declare function callArrayMethod(receiver: SandboxValue, methodName: ArrayMethodName, args: readonly SandboxValue[], options: ArrayMethodOptions, stack?: readonly string[]): Promise<SandboxValue>;
19
+ export {};
@@ -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
  }
@@ -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,4 +1,6 @@
1
+ import { invokeBuiltinClosure } from "../builtin-call.js";
1
2
  import { CompileScope } from "../regex/compile-guard.js";
3
+ import { sandboxString } from "../string-coercion.js";
2
4
  import { createSandboxClosure, createSandboxRegex, deepCopyFromSandbox, isSandboxClosure, isSandboxRegex } from "../values.js";
3
5
  import { executeRegex, toMatchArray } from "./regex.js";
4
6
  const SPLIT_STRING_MESSAGE = "String#split only supports string separator values.";
@@ -51,9 +53,21 @@ export function getStringMember(value, property, budget) {
51
53
  name: `String#${property}`,
52
54
  ...(property === "localeCompare" ? { length: 1 } : {}),
53
55
  ...(property === "isWellFormed" || property === "toWellFormed" ? { length: 0 } : {}),
54
- call: (args, context) => callStringMethod(value, property, args, budget, context === undefined
55
- ? undefined
56
- : async (closure, closureArgs) => await closure.call(closureArgs, { ...context, thisValue: undefined }), context?.compilation)
56
+ call: async (args, context) => {
57
+ const receiver = context?.thisValue;
58
+ if (receiver === null || receiver === undefined) {
59
+ throw new TypeError(`String#${property} requires a non-null receiver.`);
60
+ }
61
+ const retainedReceiver = {};
62
+ budget.setRetainedValues(retainedReceiver, () => [receiver]);
63
+ try {
64
+ const string = await sandboxString(receiver, budget, context);
65
+ return await callStringMethod(string, property, args, budget, (closure, closureArgs) => invokeBuiltinClosure(closure, closureArgs, budget, context, undefined), context?.compilation);
66
+ }
67
+ finally {
68
+ budget.setRetainedValues(retainedReceiver, undefined);
69
+ }
70
+ }
57
71
  });
58
72
  }
59
73
  function getStringIndex(property) {