poe-code 14.0.65 → 14.0.66

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 (42) hide show
  1. package/dist/index.js +1723 -797
  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-4VT2M7WL.js → chunk-MKTUVVEB.js} +2 -2
  6. package/packages/safe-js/dist/chunks/{chunk-WBWD7ZCM.js → chunk-YKISHTWT.js} +1675 -772
  7. package/packages/safe-js/dist/chunks/chunk-YKISHTWT.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/accessors.d.ts +9 -0
  12. package/packages/safe-js/dist/interp/accessors.js +68 -0
  13. package/packages/safe-js/dist/interp/async.js +3 -3
  14. package/packages/safe-js/dist/interp/classes.js +10 -4
  15. package/packages/safe-js/dist/interp/exceptions.d.ts +1 -1
  16. package/packages/safe-js/dist/interp/exceptions.js +47 -8
  17. package/packages/safe-js/dist/interp/globals/collections.js +53 -16
  18. package/packages/safe-js/dist/interp/globals/console-json.js +41 -26
  19. package/packages/safe-js/dist/interp/globals/object-array.js +229 -68
  20. package/packages/safe-js/dist/interp/globals/object.js +6 -3
  21. package/packages/safe-js/dist/interp/globals/primitives.js +11 -5
  22. package/packages/safe-js/dist/interp/interpreter.d.ts +2 -2
  23. package/packages/safe-js/dist/interp/interpreter.js +83 -44
  24. package/packages/safe-js/dist/interp/iteration.js +13 -0
  25. package/packages/safe-js/dist/interp/methods/array.d.ts +1 -1
  26. package/packages/safe-js/dist/interp/methods/array.js +368 -109
  27. package/packages/safe-js/dist/interp/methods/function.js +56 -20
  28. package/packages/safe-js/dist/interp/methods/regex.js +1 -1
  29. package/packages/safe-js/dist/interp/object-model.d.ts +1 -0
  30. package/packages/safe-js/dist/interp/object-model.js +48 -10
  31. package/packages/safe-js/dist/interp/patterns.d.ts +2 -2
  32. package/packages/safe-js/dist/interp/patterns.js +35 -16
  33. package/packages/safe-js/dist/interp/promise.d.ts +1 -1
  34. package/packages/safe-js/dist/interp/promise.js +103 -63
  35. package/packages/safe-js/dist/interp/string-coercion.d.ts +1 -0
  36. package/packages/safe-js/dist/interp/string-coercion.js +47 -37
  37. package/packages/safe-js/dist/interp/values.d.ts +2 -1
  38. package/packages/safe-js/dist/interp/values.js +26 -1
  39. package/packages/safe-js/dist/parse/parser.d.ts +1 -1
  40. package/packages/safe-js/dist/parse/parser.js +37 -8
  41. package/packages/safe-js/dist/chunks/chunk-WBWD7ZCM.js.map +0 -7
  42. /package/packages/safe-js/dist/chunks/{chunk-4VT2M7WL.js.map → chunk-MKTUVVEB.js.map} +0 -0
@@ -22,7 +22,7 @@ import {
22
22
  resolveFsConfig,
23
23
  splitFrontmatter,
24
24
  withBrokenPipeGuard
25
- } from "./chunks/chunk-4VT2M7WL.js";
25
+ } from "./chunks/chunk-MKTUVVEB.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-WBWD7ZCM.js";
37
+ } from "./chunks/chunk-YKISHTWT.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-WBWD7ZCM.js";
8
+ } from "./chunks/chunk-YKISHTWT.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-4VT2M7WL.js";
29
+ } from "./chunks/chunk-MKTUVVEB.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-WBWD7ZCM.js";
53
+ } from "./chunks/chunk-YKISHTWT.js";
54
54
  import "./chunks/chunk-UXMKEUH3.js";
55
55
  import "./chunks/chunk-5DYEOCT5.js";
56
56
  import "./chunks/chunk-4YIDX44G.js";
@@ -0,0 +1,9 @@
1
+ import type { SandboxCallContext, SandboxClosure, SandboxValue } from "./values.js";
2
+ type NativeAccessor = (...args: never[]) => unknown;
3
+ /** Native descriptors store identity only. Guest code runs through invokeClosure. */
4
+ export declare function accessorAdapter(closure: SandboxClosure, kind: "get" | "set"): NativeAccessor;
5
+ export declare function accessorClosure(adapter: NativeAccessor | undefined): SandboxClosure | undefined;
6
+ export declare function retainedAccessorClosures(descriptor: PropertyDescriptor): SandboxClosure[];
7
+ export declare function readPropertyDescriptor(descriptor: PropertyDescriptor, receiver: SandboxValue, context?: SandboxCallContext, allowNativeGetter?: boolean): SandboxValue | Promise<SandboxValue>;
8
+ export declare function writePropertyDescriptor(descriptor: PropertyDescriptor, receiver: SandboxValue, value: SandboxValue, context?: SandboxCallContext): Promise<void>;
9
+ export {};
@@ -0,0 +1,68 @@
1
+ const accessorClosures = new WeakMap();
2
+ const getterAdapters = new WeakMap();
3
+ const setterAdapters = new WeakMap();
4
+ /** Native descriptors store identity only. Guest code runs through invokeClosure. */
5
+ export function accessorAdapter(closure, kind) {
6
+ if (kind === "get") {
7
+ let adapter = getterAdapters.get(closure);
8
+ if (adapter === undefined) {
9
+ // In particular, native Promise transport must not execute a guest `then`
10
+ // getter when carrying an ordinary interpreter result between async frames.
11
+ adapter = () => undefined;
12
+ getterAdapters.set(closure, adapter);
13
+ accessorClosures.set(adapter, closure);
14
+ }
15
+ return adapter;
16
+ }
17
+ let adapter = setterAdapters.get(closure);
18
+ if (adapter === undefined) {
19
+ adapter = () => {
20
+ throw new TypeError("Accessor writes require sandbox execution.");
21
+ };
22
+ setterAdapters.set(closure, adapter);
23
+ accessorClosures.set(adapter, closure);
24
+ }
25
+ return adapter;
26
+ }
27
+ export function accessorClosure(adapter) {
28
+ if (adapter === undefined)
29
+ return undefined;
30
+ const closure = accessorClosures.get(adapter);
31
+ if (closure === undefined)
32
+ throw new TypeError("Native accessors cannot execute in the sandbox.");
33
+ return closure;
34
+ }
35
+ export function retainedAccessorClosures(descriptor) {
36
+ const closures = [];
37
+ for (const adapter of [descriptor.get, descriptor.set]) {
38
+ const closure = adapter === undefined ? undefined : accessorClosures.get(adapter);
39
+ if (closure !== undefined)
40
+ closures.push(closure);
41
+ }
42
+ return closures;
43
+ }
44
+ export function readPropertyDescriptor(descriptor, receiver, context, allowNativeGetter = false) {
45
+ if ("value" in descriptor)
46
+ return descriptor.value;
47
+ if (descriptor.get === undefined)
48
+ return undefined;
49
+ const getter = accessorClosures.get(descriptor.get);
50
+ // Low-level interpreter callers can supply trusted native getters. Public
51
+ // host admission still rejects them; preserve that existing internal route.
52
+ if (getter === undefined) {
53
+ if (!allowNativeGetter)
54
+ throw new TypeError("Native accessors cannot execute in the sandbox.");
55
+ return Reflect.apply(descriptor.get, receiver, []);
56
+ }
57
+ if (context?.invokeClosure === undefined)
58
+ throw new TypeError("Accessor reads require sandbox execution.");
59
+ return context.invokeClosure(getter, [], receiver);
60
+ }
61
+ export function writePropertyDescriptor(descriptor, receiver, value, context) {
62
+ const setter = accessorClosure(descriptor.set);
63
+ if (setter === undefined)
64
+ throw new TypeError("Cannot assign to a getter-only property.");
65
+ if (context?.invokeClosure === undefined)
66
+ throw new TypeError("Accessor writes require sandbox execution.");
67
+ return context.invokeClosure(setter, [value], receiver).then(() => undefined);
68
+ }
@@ -1,7 +1,7 @@
1
1
  import { createGeneratorChannel } from "./generator.js";
2
2
  import { getBoundOtelSpan } from "../observability/otel.js";
3
3
  import { bindPattern } from "./patterns.js";
4
- import { getThenable, resolveSandboxValue } from "./promise.js";
4
+ import { requiresPromiseResolution, resolveSandboxValue } from "./promise.js";
5
5
  import { runAsyncPrefix, suspendJob } from "./jobs.js";
6
6
  import { CompileScope } from "./regex/compile-guard.js";
7
7
  import { awaitSandboxValue } from "./cancel.js";
@@ -29,7 +29,7 @@ export async function evaluateFunctionExpression(node, context, evaluateNode) {
29
29
  return {
30
30
  kind: "normal",
31
31
  hasValue: true,
32
- value: createInterpretedClosure(node, context, evaluateNode)
32
+ value: createInterpretedClosure(node, context, evaluateNode, node.method === true ? context.functionEnvironment?.homeObject : undefined)
33
33
  };
34
34
  }
35
35
  const wrapperScope = context.scope.child();
@@ -102,7 +102,7 @@ export function executeAsyncFunction(execute, budget, signal) {
102
102
  runAsyncPrefix(async () => {
103
103
  try {
104
104
  const value = await execute(completePrefix);
105
- resolve(isSandboxPromise(value) || getThenable(value) !== undefined
105
+ resolve(requiresPromiseResolution(value, budget)
106
106
  ? awaitSandboxValue(createSandboxPromise(resolveSandboxValue(value, { budget }), {
107
107
  trackReplay: false
108
108
  }), signal, budget)
@@ -1,5 +1,6 @@
1
1
  import { getFunctionLength } from "../parse/bindings.js";
2
2
  import { functionSources } from "../parse/function-source.js";
3
+ import { accessorAdapter } from "./accessors.js";
3
4
  import { createInterpretedClosure, executeClosure } from "./async.js";
4
5
  import { retainValues } from "./resources.js";
5
6
  import { getSandboxPrototype, materializeFunctionProperties, setSandboxPrototype } from "./object-model.js";
@@ -26,7 +27,7 @@ export async function evaluateClass(node, context, evaluateNode, callContext) {
26
27
  parent = result.value;
27
28
  if (parent !== null && (!isSandboxClosure(parent) || parent.construct === undefined))
28
29
  throw new TypeError("Class extends value is not a constructor or null.");
29
- prototypeParent = parent === null ? null : callContext.getProperty(parent, "prototype");
30
+ prototypeParent = parent === null ? null : await callContext.getProperty(parent, "prototype");
30
31
  if (prototypeParent !== null && typeof prototypeParent !== "object")
31
32
  throw new TypeError("Class extends value has an invalid prototype.");
32
33
  }
@@ -66,7 +67,7 @@ export async function evaluateClass(node, context, evaluateNode, callContext) {
66
67
  };
67
68
  if (!derived) {
68
69
  thisValue = {};
69
- const targetPrototype = invocation.getProperty(newTarget, "prototype");
70
+ const targetPrototype = await invocation.getProperty(newTarget, "prototype");
70
71
  if (typeof targetPrototype === "object" && targetPrototype !== null)
71
72
  setSandboxPrototype(thisValue, targetPrototype, context.budget);
72
73
  }
@@ -136,8 +137,13 @@ export async function evaluateClass(node, context, evaluateNode, callContext) {
136
137
  else {
137
138
  const home = element.static ? constructor : prototype;
138
139
  const method = createInterpretedClosure(element.value, classContext, evaluateNode, home);
139
- Object.defineProperty(materializeFunctionProperties(method), "name", { value: key });
140
- Object.defineProperty(element.static ? properties : prototype, key, { value: method, configurable: true, writable: true, enumerable: false });
140
+ const accessor = element.kind === "get" || element.kind === "set" ? element.kind : undefined;
141
+ Object.defineProperty(materializeFunctionProperties(method), "name", {
142
+ value: accessor === undefined ? key : `${accessor} ${key}`
143
+ });
144
+ Object.defineProperty(element.static ? properties : prototype, key, accessor === undefined
145
+ ? { value: method, configurable: true, writable: true, enumerable: false }
146
+ : { [accessor]: accessorAdapter(method, accessor), configurable: true, enumerable: false });
141
147
  }
142
148
  }
143
149
  if (node.id !== undefined)
@@ -31,7 +31,7 @@ type ExceptionContext = {
31
31
  callStack: readonly string[];
32
32
  scope: Scope;
33
33
  toPropertyKey?: (value: SandboxValue) => Promise<string>;
34
- getProperty?: (value: SandboxValue, key: string | number) => SandboxValue;
34
+ getProperty?: (value: SandboxValue, key: string | number) => SandboxValue | Promise<SandboxValue>;
35
35
  };
36
36
  type EvaluateExceptionNode<TContext, TError> = (node: ParseResult, context: TContext) => Promise<EvaluationResult<TError>>;
37
37
  export declare function evaluateThrowStatement<TContext extends ExceptionContext, TError>(node: ThrowStatement, context: TContext, evaluateNode: EvaluateExceptionNode<TContext, TError>): Promise<EvaluationResult<TError>>;
@@ -2,7 +2,9 @@ import { attachErrorSpan, attachWrappedErrorCause, describeThrownValue, formatEr
2
2
  import { isFatalSandboxError, SandboxError } from "./budget.js";
3
3
  import { HostCallResumabilityError } from "./host-call.js";
4
4
  import { withFatalPromiseCleanup } from "./promise-tracker.js";
5
- import { deepCopyToSandbox, ownEnumerableSandboxEntries } from "./values.js";
5
+ import { deepCopyToSandbox, ownEnumerableSandboxKeys } from "./values.js";
6
+ import { hasOwnSandboxProperty } from "./globals/object.js";
7
+ import { retainValues } from "./resources.js";
6
8
  import { getSandboxDataProperty } from "./object-model.js";
7
9
  import { toPropertyKey } from "./property-key.js";
8
10
  const capturedExceptionBrand = Symbol("CapturedException");
@@ -389,12 +391,38 @@ async function bindArrayPattern(pattern, value, context, evaluateNode) {
389
391
  if (!Array.isArray(value)) {
390
392
  throw new TypeError("Array catch bindings require an array value.");
391
393
  }
394
+ let cursor = 0;
395
+ let done = false;
396
+ const next = async () => {
397
+ if (done || cursor >= value.length) {
398
+ done = true;
399
+ return { done: true, value: undefined };
400
+ }
401
+ const key = cursor++;
402
+ return {
403
+ done: false,
404
+ value: context.getProperty === undefined
405
+ ? getSandboxDataProperty(value, key, context.budget)
406
+ : await context.getProperty(value, key)
407
+ };
408
+ };
392
409
  for (let index = 0; index < pattern.elements.length; index += 1) {
393
410
  const element = pattern.elements[index];
394
411
  if (element === null) {
412
+ await next();
395
413
  continue;
396
414
  }
397
- const elementValue = element.type === "RestElement" ? value.slice(index) : value[index];
415
+ let elementValue;
416
+ if (element.type === "RestElement") {
417
+ const rest = [];
418
+ for (let entry = await next(); !entry.done; entry = await next()) {
419
+ context.budget.allocateArrayLength(rest.length + 1);
420
+ rest.push(entry.value);
421
+ }
422
+ elementValue = rest;
423
+ }
424
+ else
425
+ elementValue = (await next()).value;
398
426
  const binding = await bindPattern(element, elementValue, context, evaluateNode);
399
427
  if (!binding.ok) {
400
428
  return binding;
@@ -409,7 +437,7 @@ async function bindObjectPattern(pattern, value, context, evaluateNode) {
409
437
  const excludedKeys = new Set();
410
438
  for (const property of pattern.properties) {
411
439
  if (property.type === "RestElement") {
412
- const restValue = copyObjectRest(value, excludedKeys);
440
+ const restValue = await copyObjectRest(value, excludedKeys, context);
413
441
  const binding = await bindPattern(property, restValue, context, evaluateNode);
414
442
  if (!binding.ok) {
415
443
  return binding;
@@ -423,7 +451,7 @@ async function bindObjectPattern(pattern, value, context, evaluateNode) {
423
451
  excludedKeys.add(String(key.value));
424
452
  const binding = await bindPattern(property.value, context.getProperty === undefined
425
453
  ? getSandboxDataProperty(value, key.value, context.budget)
426
- : context.getProperty(value, key.value), context, evaluateNode);
454
+ : await context.getProperty(value, key.value), context, evaluateNode);
427
455
  if (!binding.ok) {
428
456
  return binding;
429
457
  }
@@ -460,10 +488,21 @@ function getStaticPropertyKey(property) {
460
488
  throw new TypeError(`Unsupported catch binding property key '${property.type}'.`);
461
489
  }
462
490
  }
463
- function copyObjectRest(value, excludedKeys) {
491
+ async function copyObjectRest(value, excludedKeys, context) {
464
492
  const rest = Object.create(null);
465
- for (const [key, entryValue] of ownEnumerableSandboxEntries(value, excludedKeys)) {
466
- rest[key] = entryValue;
493
+ const release = retainValues(context.budget, () => [value, rest]);
494
+ try {
495
+ for (const key of ownEnumerableSandboxKeys(value)) {
496
+ if (excludedKeys.has(key) || !hasOwnSandboxProperty(value, key, true))
497
+ continue;
498
+ rest[key] =
499
+ context.getProperty === undefined
500
+ ? getSandboxDataProperty(value, key, context.budget)
501
+ : await context.getProperty(value, key);
502
+ }
503
+ return rest;
504
+ }
505
+ finally {
506
+ release();
467
507
  }
468
- return rest;
469
508
  }
@@ -17,20 +17,34 @@ export function createCollectionGlobals(options) {
17
17
  let key;
18
18
  let value;
19
19
  return populateCollection(source, map, {
20
- name: "Map", budget: options.budget, context,
20
+ name: "Map",
21
+ budget: options.budget,
22
+ context,
21
23
  retainedValues: () => [key, value],
22
24
  append: (entry) => {
23
25
  if (typeof entry !== "object" || entry === null)
24
26
  throw new TypeError("Map constructor requires entry objects.");
25
- key = context?.getProperty !== undefined ? context.getProperty(entry, 0) : getSandboxDataProperty(entry, 0, options.budget);
26
- value = context?.getProperty !== undefined ? context.getProperty(entry, 1) : getSandboxDataProperty(entry, 1, options.budget);
27
- const added = map.entries.has(key) ? 0 : 1;
28
- const growth = added +
29
- (options.budget.limits.dataSize === undefined ? 0 : measureSandboxData([key, value]));
30
- options.budget.allocateCollectionEntries(map.entries.size + added);
31
- map.entries.set(key, value);
32
- key = value = undefined;
33
- return growth;
27
+ const store = (entryValue) => {
28
+ value = entryValue;
29
+ const added = map.entries.has(key) ? 0 : 1;
30
+ const growth = added +
31
+ (options.budget.limits.dataSize === undefined ? 0 : measureSandboxData([key, value]));
32
+ options.budget.allocateCollectionEntries(map.entries.size + added);
33
+ map.entries.set(key, value);
34
+ key = value = undefined;
35
+ return growth;
36
+ };
37
+ const readValue = (entryKey) => {
38
+ key = entryKey;
39
+ const result = context?.getProperty !== undefined
40
+ ? context.getProperty(entry, 1)
41
+ : getSandboxDataProperty(entry, 1, options.budget);
42
+ return result instanceof Promise ? result.then(store) : store(result);
43
+ };
44
+ const result = context?.getProperty !== undefined
45
+ ? context.getProperty(entry, 0)
46
+ : getSandboxDataProperty(entry, 0, options.budget);
47
+ return result instanceof Promise ? result.then(readValue) : readValue(result);
34
48
  }
35
49
  });
36
50
  },
@@ -44,7 +58,9 @@ export function createCollectionGlobals(options) {
44
58
  construct: ([source], context) => {
45
59
  const set = createSandboxSet([]);
46
60
  return populateCollection(source, set, {
47
- name: "Set", budget: options.budget, context,
61
+ name: "Set",
62
+ budget: options.budget,
63
+ context,
48
64
  append: (value) => {
49
65
  const added = set.values.has(value) ? 0 : 1;
50
66
  const growth = added +
@@ -77,7 +93,14 @@ function populateCollection(source, collection, { name, budget, context, append,
77
93
  let entry;
78
94
  let failure;
79
95
  const retained = {};
80
- budget.setRetainedValues(retained, () => [source, iterator.retainedValue, collection, entry, failure, ...(retainedValues?.() ?? [])]);
96
+ budget.setRetainedValues(retained, () => [
97
+ source,
98
+ iterator.retainedValue,
99
+ collection,
100
+ entry,
101
+ failure,
102
+ ...(retainedValues?.() ?? [])
103
+ ]);
81
104
  const checkData = createDataCheckpoint(budget, context);
82
105
  const closeOnThrow = (error) => {
83
106
  failure = isCapturedException(error) ? error.reason : error;
@@ -89,7 +112,9 @@ function populateCollection(source, collection, { name, budget, context, append,
89
112
  try {
90
113
  const closing = iterator.return?.();
91
114
  if (iterator.generator || iterator.asynchronous)
92
- return Promise.resolve(closing).then(() => { throw error; }, rethrow);
115
+ return Promise.resolve(closing).then(() => {
116
+ throw error;
117
+ }, rethrow);
93
118
  }
94
119
  catch (closeError) {
95
120
  return rethrow(closeError);
@@ -105,6 +130,14 @@ function populateCollection(source, collection, { name, budget, context, append,
105
130
  try {
106
131
  budget.visitNode();
107
132
  const growth = append(entry);
133
+ if (growth instanceof Promise)
134
+ return growth
135
+ .then((size) => {
136
+ entry = undefined;
137
+ checkData(collection, size);
138
+ return false;
139
+ })
140
+ .catch(closeOnThrow);
108
141
  entry = undefined;
109
142
  checkData(collection, growth);
110
143
  }
@@ -113,11 +146,13 @@ function populateCollection(source, collection, { name, budget, context, append,
113
146
  }
114
147
  return false;
115
148
  };
116
- if (iterator.generator || iterator.asynchronous) {
149
+ if (iterator.generator || iterator.asynchronous || context !== undefined) {
117
150
  return (async () => {
118
151
  try {
119
152
  checkData(collection, 0, true);
120
- while (!(await consume(await iterator.next()))) { /* Consume each entry before advancing. */ }
153
+ while (!(await consume(await iterator.next()))) {
154
+ /* Consume each entry before advancing. */
155
+ }
121
156
  checkData(collection, 0, true);
122
157
  return collection;
123
158
  }
@@ -128,7 +163,9 @@ function populateCollection(source, collection, { name, budget, context, append,
128
163
  }
129
164
  try {
130
165
  checkData(collection, 0, true);
131
- while (!consume(iterator.next())) { /* Synchronous inputs stay synchronous. */ }
166
+ while (!consume(iterator.next())) {
167
+ /* Synchronous inputs stay synchronous. */
168
+ }
132
169
  checkData(collection, 0, true);
133
170
  return collection;
134
171
  }
@@ -1,3 +1,6 @@
1
+ import { retainValues } from "../resources.js";
2
+ import { readPropertyDescriptor } from "../accessors.js";
3
+ import { getSandboxPropertyDescriptor } from "../object-model.js";
1
4
  import { dateMethods, isSandboxDate } from "../date.js";
2
5
  import { boxedValue, isSandboxBox } from "../boxed.js";
3
6
  import { sandboxNumber, sandboxString } from "../string-coercion.js";
@@ -96,27 +99,36 @@ async function stringifyJson(value, replacer, indent, budget, context) {
96
99
  return budget.allocateString(output);
97
100
  }
98
101
  async function stringifyProperty(key, holder, state, indent = "") {
99
- let value = getOwnDataValue(holder, key);
100
- if (isSandboxDate(value)) {
101
- value = dateMethods.get("toJSON").invoke(value, []);
102
- }
103
- else if (isStringifyContainer(value)) {
104
- const toJSON = getOwnDataValue(value, "toJSON");
105
- if (isSandboxClosure(toJSON)) {
106
- value = await callStringifyClosure(toJSON, [key], value, state);
102
+ let value = await getStringifyProperty(holder, key, state);
103
+ const release = retainValues(state.budget, () => [holder, value]);
104
+ try {
105
+ if (isSandboxDate(value)) {
106
+ value = dateMethods.get("toJSON").invoke(value, []);
107
+ }
108
+ else if (isStringifyContainer(value)) {
109
+ const toJSON = await getStringifyProperty(value, "toJSON", state);
110
+ if (isSandboxClosure(toJSON)) {
111
+ value = await callStringifyClosure(toJSON, [key], value, state);
112
+ }
113
+ }
114
+ if (state.replacer !== undefined) {
115
+ value = await callStringifyClosure(state.replacer, [key, toSandboxValue(value)], holder, state);
107
116
  }
117
+ return await stringifyValue(value, state, indent);
108
118
  }
109
- if (state.replacer !== undefined) {
110
- value = await callStringifyClosure(state.replacer, [key, toSandboxValue(value)], holder, state);
119
+ finally {
120
+ release();
111
121
  }
112
- return stringifyValue(value, state, indent);
113
122
  }
114
123
  async function stringifyValue(value, state, indent) {
115
124
  if (isSandboxBox(value)) {
116
125
  const primitive = boxedValue(value);
117
- value = typeof primitive === "number" ? await sandboxNumber(value, state.budget, state.context)
118
- : typeof primitive === "string" ? await sandboxString(value, state.budget, state.context)
119
- : primitive;
126
+ value =
127
+ typeof primitive === "number"
128
+ ? await sandboxNumber(value, state.budget, state.context)
129
+ : typeof primitive === "string"
130
+ ? await sandboxString(value, state.budget, state.context)
131
+ : primitive;
120
132
  }
121
133
  if (value === null) {
122
134
  return "null";
@@ -148,10 +160,12 @@ async function stringifyValue(value, state, indent) {
148
160
  }
149
161
  async function stringifyArray(value, state, indent) {
150
162
  enterStringifyObject(value, state);
163
+ const entries = [];
164
+ const release = retainValues(state.budget, () => entries);
151
165
  try {
152
166
  const nextIndent = indent + state.gap;
153
- const entries = [];
154
- for (let index = 0; index < value.length; index += 1) {
167
+ const length = value.length;
168
+ for (let index = 0; index < length; index += 1) {
155
169
  entries.push((await stringifyProperty(String(index), value, state, nextIndent)) ?? "null");
156
170
  }
157
171
  if (entries.length === 0) {
@@ -163,14 +177,16 @@ async function stringifyArray(value, state, indent) {
163
177
  return `[\n${nextIndent}${entries.join(`,\n${nextIndent}`)}\n${indent}]`;
164
178
  }
165
179
  finally {
180
+ release();
166
181
  leaveStringifyObject(value, state);
167
182
  }
168
183
  }
169
184
  async function stringifyObject(value, state, indent) {
170
185
  enterStringifyObject(value, state);
186
+ const entries = [];
187
+ const release = retainValues(state.budget, () => entries);
171
188
  try {
172
189
  const nextIndent = indent + state.gap;
173
- const entries = [];
174
190
  for (const key of Object.keys(value)) {
175
191
  const serialized = await stringifyProperty(key, value, state, nextIndent);
176
192
  if (serialized !== undefined) {
@@ -186,6 +202,7 @@ async function stringifyObject(value, state, indent) {
186
202
  return `{\n${nextIndent}${entries.join(`,\n${nextIndent}`)}\n${indent}}`;
187
203
  }
188
204
  finally {
205
+ release();
189
206
  leaveStringifyObject(value, state);
190
207
  }
191
208
  }
@@ -250,15 +267,13 @@ function toSandboxValue(value) {
250
267
  }
251
268
  throw new TypeError(`JSON.stringify(value) produced an unsupported value of type ${typeof value}.`);
252
269
  }
253
- function getOwnDataValue(target, key) {
254
- const descriptor = Object.getOwnPropertyDescriptor(target, key);
255
- if (descriptor === undefined) {
256
- return undefined;
257
- }
258
- if ("get" in descriptor || "set" in descriptor) {
259
- throw new TypeError(`JSON.stringify(value) cannot serialize accessor property ${key}.`);
260
- }
261
- return descriptor.value;
270
+ function getStringifyProperty(target, key, state) {
271
+ if (state.context?.getProperty !== undefined)
272
+ return state.context.getProperty(target, key);
273
+ const descriptor = getSandboxPropertyDescriptor(target, key, state.budget);
274
+ return descriptor === undefined
275
+ ? undefined
276
+ : readPropertyDescriptor(descriptor, target, state.context);
262
277
  }
263
278
  function copyJsonToSandbox(value, budget) {
264
279
  if (value === null ||