poe-code 14.0.61 → 14.0.63

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 (67) hide show
  1. package/dist/index.js +1147 -574
  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-VAYQP2VZ.js → chunk-KMTTD3BL.js} +7 -3
  6. package/packages/safe-js/dist/chunks/chunk-KMTTD3BL.js.map +7 -0
  7. package/packages/safe-js/dist/chunks/{chunk-ZTGKRKQH.js → chunk-RU2BCN4G.js} +1069 -577
  8. package/packages/safe-js/dist/chunks/chunk-RU2BCN4G.js.map +7 -0
  9. package/packages/safe-js/dist/cli.js +2 -2
  10. package/packages/safe-js/dist/core.js +1 -1
  11. package/packages/safe-js/dist/graph-depth.js +3 -0
  12. package/packages/safe-js/dist/index.js +2 -2
  13. package/packages/safe-js/dist/interp/async.js +3 -0
  14. package/packages/safe-js/dist/interp/boxed.d.ts +14 -0
  15. package/packages/safe-js/dist/interp/boxed.js +50 -0
  16. package/packages/safe-js/dist/interp/function-string.d.ts +2 -0
  17. package/packages/safe-js/dist/interp/function-string.js +21 -0
  18. package/packages/safe-js/dist/interp/globals/collections.js +4 -4
  19. package/packages/safe-js/dist/interp/globals/console-json.js +15 -18
  20. package/packages/safe-js/dist/interp/globals/misc.js +12 -13
  21. package/packages/safe-js/dist/interp/globals/numeric-parsers.d.ts +5 -0
  22. package/packages/safe-js/dist/interp/globals/numeric-parsers.js +47 -0
  23. package/packages/safe-js/dist/interp/globals/object-array.js +21 -24
  24. package/packages/safe-js/dist/interp/globals/object.js +10 -5
  25. package/packages/safe-js/dist/interp/globals/primitives.d.ts +8 -0
  26. package/packages/safe-js/dist/interp/globals/primitives.js +69 -0
  27. package/packages/safe-js/dist/interp/host-bridge.js +22 -0
  28. package/packages/safe-js/dist/interp/interpreter.js +173 -120
  29. package/packages/safe-js/dist/interp/iteration.d.ts +5 -3
  30. package/packages/safe-js/dist/interp/iteration.js +22 -1
  31. package/packages/safe-js/dist/interp/methods/array.js +6 -3
  32. package/packages/safe-js/dist/interp/methods/function.d.ts +2 -0
  33. package/packages/safe-js/dist/interp/methods/function.js +10 -1
  34. package/packages/safe-js/dist/interp/methods/number.d.ts +1 -0
  35. package/packages/safe-js/dist/interp/methods/number.js +4 -1
  36. package/packages/safe-js/dist/interp/methods/string.d.ts +2 -0
  37. package/packages/safe-js/dist/interp/methods/string.js +2 -2
  38. package/packages/safe-js/dist/interp/object-model.d.ts +5 -0
  39. package/packages/safe-js/dist/interp/object-model.js +41 -3
  40. package/packages/safe-js/dist/interp/promise.js +68 -61
  41. package/packages/safe-js/dist/interp/resources.d.ts +1 -0
  42. package/packages/safe-js/dist/interp/string-coercion.js +7 -1
  43. package/packages/safe-js/dist/interp/values.d.ts +4 -0
  44. package/packages/safe-js/dist/interp/values.js +81 -3
  45. package/packages/safe-js/dist/migrate.d.ts +2 -2
  46. package/packages/safe-js/dist/migrate.js +1 -1
  47. package/packages/safe-js/dist/migration-file.d.ts +4 -4
  48. package/packages/safe-js/dist/parse/function-source.d.ts +7 -0
  49. package/packages/safe-js/dist/parse/function-source.js +1 -0
  50. package/packages/safe-js/dist/parse/hash.d.ts +2 -2
  51. package/packages/safe-js/dist/parse/hash.js +23 -9
  52. package/packages/safe-js/dist/parse/parser.js +36 -23
  53. package/packages/safe-js/dist/restore.js +2 -1
  54. package/packages/safe-js/dist/run.js +16 -5
  55. package/packages/safe-js/dist/snapshot/boxed.d.ts +16 -0
  56. package/packages/safe-js/dist/snapshot/boxed.js +52 -0
  57. package/packages/safe-js/dist/snapshot/dump-format.d.ts +1 -1
  58. package/packages/safe-js/dist/snapshot/dump-format.js +19 -6
  59. package/packages/safe-js/dist/snapshot/migration.js +2 -2
  60. package/packages/safe-js/dist/snapshot/replay-data.d.ts +5 -0
  61. package/packages/safe-js/dist/snapshot/replay-data.js +24 -0
  62. package/packages/safe-js/dist/snapshot/restore.js +10 -0
  63. package/packages/safe-js/dist/snapshot/serialize.d.ts +4 -2
  64. package/packages/safe-js/dist/snapshot/serialize.js +18 -10
  65. package/packages/safe-js/dist/snapshot/validation.js +22 -1
  66. package/packages/safe-js/dist/chunks/chunk-VAYQP2VZ.js.map +0 -7
  67. package/packages/safe-js/dist/chunks/chunk-ZTGKRKQH.js.map +0 -7
@@ -1,11 +1,15 @@
1
1
  import { assertSandboxDataDepth } from "../graph-depth.js";
2
2
  import { getHostObjectMember, isGuestHostObject, isLiveCapability } from "./host-capabilities.js";
3
+ import { boxedValue, isSandboxBox } from "./boxed.js";
3
4
  import { isSandboxClosure, isSandboxGenerator, isSandboxMap, isSandboxPromise, isSandboxRegex, isSandboxSet } from "./values.js";
4
5
  const guestClosures = new WeakSet();
5
6
  const functionProperties = new WeakMap();
6
7
  const prototypes = new WeakMap();
7
8
  const intrinsicPrototypes = new WeakMap();
9
+ const boxedPrototypes = new WeakMap();
10
+ const intrinsicPrototypeRoots = new WeakMap();
8
11
  const intrinsicConstructors = new WeakMap();
12
+ const initialBoxedMethods = new WeakMap();
9
13
  const descriptorObjects = new WeakSet();
10
14
  export function registerGuestClosure(closure) {
11
15
  guestClosures.add(closure);
@@ -61,10 +65,36 @@ export function getGuestFunctionProperty(closure, key) {
61
65
  export function installObjectPrototype(budget, prototype, constructor) {
62
66
  prototypes.set(prototype, null);
63
67
  intrinsicPrototypes.set(budget, prototype);
68
+ registerIntrinsicPrototype(budget, prototype, constructor);
69
+ }
70
+ export function installBoxedPrototype(budget, prototype, constructor) {
71
+ let state = boxedPrototypes.get(budget);
72
+ if (state === undefined)
73
+ boxedPrototypes.set(budget, state = new Map());
74
+ state.set(typeof boxedValue(prototype), prototype);
75
+ initialBoxedMethods.set(prototype, new Map(Object.entries(Object.getOwnPropertyDescriptors(prototype)).map(([key, descriptor]) => [key, descriptor.value])));
76
+ registerIntrinsicPrototype(budget, prototype, constructor);
77
+ }
78
+ export function getBoxedPrototype(value, budget) {
79
+ return boxedPrototypes.get(budget)?.get(typeof value);
80
+ }
81
+ export function isDefaultBoxedMethod(value, key, budget) {
82
+ const prototype = getBoxedPrototype(value, budget);
83
+ return prototype !== undefined && initialBoxedMethods.get(prototype)?.has(key) === true &&
84
+ Object.getOwnPropertyDescriptor(prototype, key)?.value === initialBoxedMethods.get(prototype)?.get(key);
85
+ }
86
+ export function isIntrinsicConstructor(value) {
87
+ return intrinsicConstructors.has(value);
88
+ }
89
+ function registerIntrinsicPrototype(budget, prototype, constructor) {
90
+ let roots = intrinsicPrototypeRoots.get(budget);
91
+ if (roots === undefined)
92
+ intrinsicPrototypeRoots.set(budget, roots = new Set());
93
+ roots.add(prototype);
64
94
  const records = [prototype, materializeFunctionProperties(constructor)].map(value => ({
65
95
  value, descriptors: new Map(Object.entries(Object.getOwnPropertyDescriptors(value)))
66
96
  }));
67
- const unchanged = (before, after) => before !== undefined && after !== undefined && before.value === after.value &&
97
+ const unchanged = (before, after) => before !== undefined && after !== undefined && Object.is(before.value, after.value) &&
68
98
  before.writable === after.writable && before.configurable === after.configurable && before.enumerable === after.enumerable;
69
99
  intrinsicConstructors.set(constructor, () => records.every(({ value, descriptors }) => {
70
100
  const current = Object.getOwnPropertyDescriptors(value);
@@ -74,14 +104,20 @@ export function installObjectPrototype(budget, prototype, constructor) {
74
104
  budget.setRetainedValues(prototype, () => records.flatMap(({ value, descriptors }) => Object.entries(Object.getOwnPropertyDescriptors(value)).flatMap(([key, descriptor]) => unchanged(descriptors.get(key), descriptor) ? [] : [key, descriptor.value])));
75
105
  }
76
106
  export function releaseObjectPrototype(budget) {
77
- const prototype = intrinsicPrototypes.get(budget);
78
- if (prototype !== undefined)
107
+ for (const prototype of intrinsicPrototypeRoots.get(budget) ?? [])
79
108
  budget.setRetainedValues(prototype, undefined);
109
+ intrinsicPrototypeRoots.delete(budget);
110
+ boxedPrototypes.delete(budget);
80
111
  intrinsicPrototypes.delete(budget);
81
112
  }
82
113
  export function getSandboxPrototype(value, budget) {
83
114
  if (prototypes.has(value))
84
115
  return prototypes.get(value) ?? null;
116
+ if (budget !== undefined && isSandboxBox(value)) {
117
+ const prototype = getBoxedPrototype(boxedValue(value), budget);
118
+ if (prototype !== undefined && prototype !== value)
119
+ return prototype;
120
+ }
85
121
  return budget !== undefined && isPrototypeRecord(value)
86
122
  ? intrinsicPrototypes.get(budget) ?? null
87
123
  : null;
@@ -167,6 +203,8 @@ export function hasGuestObjectState(value) {
167
203
  return true;
168
204
  if (functionProperties.has(value) || prototypes.has(value))
169
205
  return true;
206
+ if (isSandboxBox(value))
207
+ return false;
170
208
  return (descriptorObjects.has(value) &&
171
209
  Object.values(Object.getOwnPropertyDescriptors(value)).some((descriptor) => !descriptor.enumerable || !descriptor.configurable || !descriptor.writable));
172
210
  }
@@ -1,6 +1,7 @@
1
1
  import { SandboxError } from "./budget.js";
2
2
  import { coerceThrownValue, createSubsetErrorValue } from "./exceptions.js";
3
3
  import { getSandboxIterator } from "./iteration.js";
4
+ import { retainValues } from "./resources.js";
4
5
  import { runPromiseJob } from "./jobs.js";
5
6
  import { observeSandboxPromise } from "./promise-tracker.js";
6
7
  import { createSandboxClosure, createSandboxPromise, isSandboxClosure, isSandboxPromise } from "./values.js";
@@ -337,76 +338,82 @@ async function settleIterable(iterable, method, budget, constructor, context) {
337
338
  const promiseResolve = readPromiseReceiverProperty(constructor, "resolve", prototype);
338
339
  if (!isSandboxClosure(promiseResolve))
339
340
  throw new TypeError("Promise constructor requires a callable resolve.");
340
- const iterator = getSandboxIterator(iterable, budget);
341
+ const iterator = getSandboxIterator(iterable, budget, context);
341
342
  if (iterator === undefined)
342
343
  throw new TypeError("Promise helpers require an iterable.");
343
- let index = 0;
344
- while (true) {
345
- budget.visitNode();
346
- const next = iterator.generator
347
- ? await iterator.next()
348
- : iterator.next();
349
- if (typeof next !== "object" || next === null)
350
- throw new TypeError("Iterator result must be an object.");
351
- if (next.done)
352
- break;
353
- const value = next.value;
354
- try {
355
- budget.allocateArrayLength(index + 1);
356
- const entryIndex = index++;
357
- if (method !== "race")
358
- values.push(undefined);
359
- const entry = await callPromiseClosure(promiseResolve, [value], constructor, budget, context);
360
- let called = false;
361
- const handlers = ["fulfilled", "rejected"].map((state) => {
362
- if (method === "race" || (method === "any" && state === "fulfilled"))
363
- return state === "fulfilled" ? capability.resolve : capability.reject;
364
- if (method === "all" && state === "rejected")
365
- return capability.reject;
366
- return createSandboxClosure({
367
- sandbox: true,
368
- retainedValues: () => [
369
- capability.promise,
370
- capability.resolve,
371
- capability.reject,
372
- values
373
- ],
374
- call: async ([settlement]) => {
375
- if (called)
344
+ const releaseIterator = retainValues(budget, () => [iterator.retainedValue]);
345
+ try {
346
+ let index = 0;
347
+ while (true) {
348
+ budget.visitNode();
349
+ const next = iterator.generator || iterator.asynchronous
350
+ ? await iterator.next()
351
+ : iterator.next();
352
+ if (typeof next !== "object" || next === null)
353
+ throw new TypeError("Iterator result must be an object.");
354
+ if (next.done)
355
+ break;
356
+ const value = next.value;
357
+ try {
358
+ budget.allocateArrayLength(index + 1);
359
+ const entryIndex = index++;
360
+ if (method !== "race")
361
+ values.push(undefined);
362
+ const entry = await callPromiseClosure(promiseResolve, [value], constructor, budget, context);
363
+ let called = false;
364
+ const handlers = ["fulfilled", "rejected"].map((state) => {
365
+ if (method === "race" || (method === "any" && state === "fulfilled"))
366
+ return state === "fulfilled" ? capability.resolve : capability.reject;
367
+ if (method === "all" && state === "rejected")
368
+ return capability.reject;
369
+ return createSandboxClosure({
370
+ sandbox: true,
371
+ retainedValues: () => [
372
+ capability.promise,
373
+ capability.resolve,
374
+ capability.reject,
375
+ values
376
+ ],
377
+ call: async ([settlement]) => {
378
+ if (called)
379
+ return undefined;
380
+ called = true;
381
+ values[entryIndex] =
382
+ method === "allSettled"
383
+ ? state === "fulfilled"
384
+ ? { status: state, value: settlement }
385
+ : { status: state, reason: settlement }
386
+ : settlement;
387
+ remaining--;
388
+ await complete();
376
389
  return undefined;
377
- called = true;
378
- values[entryIndex] =
379
- method === "allSettled"
380
- ? state === "fulfilled"
381
- ? { status: state, value: settlement }
382
- : { status: state, reason: settlement }
383
- : settlement;
384
- remaining--;
385
- await complete();
386
- return undefined;
387
- }
390
+ }
391
+ });
388
392
  });
389
- });
390
- remaining++;
391
- const then = readPromiseReceiverProperty(entry, "then", prototype);
392
- if (!isSandboxClosure(then))
393
- throw new TypeError("Promise resolver result requires a callable then.");
394
- await callPromiseClosure(then, handlers, entry, budget, context);
395
- }
396
- catch (error) {
397
- try {
398
- const closed = iterator.return?.();
399
- if (iterator.generator)
400
- await closed;
393
+ remaining++;
394
+ const then = readPromiseReceiverProperty(entry, "then", prototype);
395
+ if (!isSandboxClosure(then))
396
+ throw new TypeError("Promise resolver result requires a callable then.");
397
+ await callPromiseClosure(then, handlers, entry, budget, context);
401
398
  }
402
- catch {
399
+ catch (error) {
400
+ try {
401
+ const closed = iterator.return?.();
402
+ if (iterator.generator || iterator.asynchronous)
403
+ await closed;
404
+ }
405
+ catch {
406
+ throw error;
407
+ }
403
408
  throw error;
404
409
  }
405
- throw error;
406
410
  }
411
+ remaining--;
412
+ await complete();
413
+ }
414
+ finally {
415
+ releaseIterator();
407
416
  }
408
- remaining--;
409
- await complete();
410
417
  }
411
418
  catch (error) {
412
419
  if (error instanceof SandboxError &&
@@ -2,6 +2,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import type { Budget } from "./budget.js";
3
3
  export type RunResources = {
4
4
  signal: AbortSignal;
5
+ functionSourceText?: boolean;
5
6
  referenceReleases: Set<() => void>;
6
7
  add(close: () => Promise<void>): void;
7
8
  };
@@ -8,6 +8,8 @@ import { collectionIteratorState, isSandboxCollectionIterator } from "./collecti
8
8
  import { getGuestFunctionProperties, getSandboxPrototype, hasExplicitSandboxPrototype, isGuestClosure } from "./object-model.js";
9
9
  import { getRegexMember } from "./methods/regex.js";
10
10
  import { retainValues } from "./resources.js";
11
+ import { functionString } from "./function-string.js";
12
+ import { boxedValue, isSandboxBox } from "./boxed.js";
11
13
  import { isSandboxClosure, isSandboxPromise, isSandboxMap, isSandboxSet, isSandboxRegex, isSandboxGenerator } from "./values.js";
12
14
  const defaultStringHook = Symbol("defaultStringHook");
13
15
  const defaultValueHook = Symbol("defaultValueHook");
@@ -39,7 +41,7 @@ async function objectToPrimitive(value, budget, context, joining, hint) {
39
41
  result = await defaultToString(value, budget, context, joining);
40
42
  }
41
43
  else if (hook === defaultValueHook) {
42
- result = isSandboxDate(value) ? dateTime(value) : value;
44
+ result = isSandboxDate(value) ? dateTime(value) : isSandboxBox(value) ? boxedValue(value) : value;
43
45
  }
44
46
  else {
45
47
  if (!isSandboxClosure(hook))
@@ -96,6 +98,10 @@ function conversionHook(value, name, budget) {
96
98
  return undefined;
97
99
  }
98
100
  async function defaultToString(value, budget, context, joining) {
101
+ if (isSandboxBox(value))
102
+ return budget.allocateString(String(boxedValue(value)));
103
+ if (isSandboxClosure(value))
104
+ return budget.allocateString(functionString(value));
99
105
  if (isSandboxMap(value))
100
106
  return "[object Map]";
101
107
  if (isSandboxSet(value))
@@ -4,6 +4,7 @@ import type { Budget, CompileTicket } from "./budget.js";
4
4
  import { CompileScope } from "./regex/compile-guard.js";
5
5
  import type { GeneratorChannel } from "./generator.js";
6
6
  import { type RegexPattern } from "./regex/parse.js";
7
+ import type { FunctionSource } from "../parse/function-source.js";
7
8
  export { createSandboxArguments, isSandboxArguments } from "./arguments.js";
8
9
  export { isSandboxMap, isSandboxSet } from "./collection-brands.js";
9
10
  declare const sandboxClosureBrand: unique symbol;
@@ -57,6 +58,7 @@ export type SandboxCallContext = {
57
58
  readonly invokeClosure?: (closure: SandboxClosure, args: readonly SandboxValue[], thisValue: SandboxValue, construct?: boolean) => Promise<SandboxValue>;
58
59
  };
59
60
  export type SandboxClosure = {
61
+ readonly sourceRange?: FunctionSource;
60
62
  readonly async?: true;
61
63
  readonly generator?: true;
62
64
  readonly sandbox?: true;
@@ -94,6 +96,7 @@ type CopyFromSandboxOptions = {
94
96
  compilation?: CompileScope;
95
97
  };
96
98
  export declare function createSandboxClosure(input: {
99
+ sourceRange?: FunctionSource;
97
100
  guest?: boolean;
98
101
  async?: boolean;
99
102
  generator?: boolean;
@@ -131,6 +134,7 @@ export declare function deepCopyToSandbox(value: unknown): SandboxValue;
131
134
  export declare function cloneSandboxValue(value: SandboxValue, options?: {
132
135
  compilation?: CompileScope;
133
136
  resetRegexLastIndex?: boolean;
137
+ structuredClone?: boolean;
134
138
  }): SandboxValue;
135
139
  export declare function allocateProducedSandboxValue(value: SandboxValue, budget: Budget): SandboxValue;
136
140
  export declare function measureSandboxData(values: Iterable<unknown>, options?: {
@@ -2,6 +2,7 @@ import { bindOtelSpan, getBoundOtelSpan } from "../observability/otel.js";
2
2
  import { isSandboxMap, isSandboxSet, sandboxMapBrand, sandboxSetBrand } from "./collection-brands.js";
3
3
  import { collectionIteratorState, isSandboxCollectionIterator, restoreSandboxCollectionIterator, snapshotCollectionIterator } from "./collection-iterator.js";
4
4
  import { copyNativeDate, exportDate, isSandboxDate } from "./date.js";
5
+ import { boxedDataProperties, boxedValue, createSandboxBox, isSandboxBox, nativeBoxedValue } from "./boxed.js";
5
6
  import { getHostObjectKeys, getHostObjectMember, hasHostObjectMember, measureHostObjectData, isGuestHostObject, isLiveCapability } from "./host-capabilities.js";
6
7
  import { types as nodeTypes } from "node:util";
7
8
  import { CompileScope, RegexCompileGuard, regexCompiledData } from "./regex/compile-guard.js";
@@ -13,7 +14,7 @@ import { invokeCancelableClosure, readPromiseCancellation, registerPromiseCancel
13
14
  import { parseRegex } from "./regex/parse.js";
14
15
  import { assertSandboxDataDepth } from "../graph-depth.js";
15
16
  import { sandboxErrorTypes } from "../error/shape.js";
16
- import { getGuestFunctionProperties, getSandboxPrototype, hasGuestObjectState, hasManagedDescriptors, isGuestClosure, registerGuestClosure } from "./object-model.js";
17
+ import { getGuestFunctionProperties, getSandboxPrototype, hasGuestObjectState, hasManagedDescriptors, isGuestClosure, isIntrinsicConstructor, registerGuestClosure } from "./object-model.js";
17
18
  import { copySandboxArgumentProperties, createSandboxArguments, getSandboxArgumentEntries, isSandboxArguments } from "./arguments.js";
18
19
  export { createSandboxArguments, isSandboxArguments } from "./arguments.js";
19
20
  export { isSandboxMap, isSandboxSet } from "./collection-brands.js";
@@ -39,6 +40,9 @@ export function createSandboxClosure(input) {
39
40
  enumerable: false,
40
41
  value: true
41
42
  });
43
+ if (input.sourceRange !== undefined) {
44
+ Object.defineProperty(closure, "sourceRange", { value: input.sourceRange });
45
+ }
42
46
  if (input.sandbox === true) {
43
47
  Object.defineProperty(closure, "sandbox", { value: true });
44
48
  }
@@ -240,6 +244,19 @@ export function measureSandboxData(values, options = {}) {
240
244
  assertSandboxDataDepth(depth);
241
245
  seen.add(value);
242
246
  usage += 1;
247
+ if (isSandboxBox(value)) {
248
+ const primitive = boxedValue(value);
249
+ usage += typeof primitive === "string" ? primitive.length : 8;
250
+ const prototype = getSandboxPrototype(value);
251
+ if (prototype !== null)
252
+ visit(prototype, depth + 1);
253
+ for (const [key, descriptor] of boxedDataProperties(value)) {
254
+ usage += key.length + 1;
255
+ if ("value" in descriptor)
256
+ visit(descriptor.value, depth + 1);
257
+ }
258
+ return;
259
+ }
243
260
  if (isSandboxDate(value)) {
244
261
  usage += 8;
245
262
  return;
@@ -308,8 +325,19 @@ export function measureSandboxData(values, options = {}) {
308
325
  if (isSandboxClosure(value)) {
309
326
  if (options.ignoreClosures)
310
327
  return;
311
- if (value.properties !== undefined)
312
- visit(value.properties, depth + 1);
328
+ if (value.properties !== undefined) {
329
+ if (isIntrinsicConstructor(value)) {
330
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value.properties))) {
331
+ if (key === "prototype" || key === "name" || key === "length")
332
+ continue;
333
+ usage += key.length + 1;
334
+ if ("value" in descriptor)
335
+ visit(descriptor.value, depth + 1);
336
+ }
337
+ }
338
+ else
339
+ visit(value.properties, depth + 1);
340
+ }
313
341
  if (!options.ignoreClosureCaptures)
314
342
  for (const retained of value[sandboxRetainedValues]?.() ?? [])
315
343
  visit(retained, depth + 1);
@@ -454,6 +482,26 @@ function copyToSandbox(value, state, path = "<root>", cloneSandboxCollections =
454
482
  if (typeof value === "object" && value !== null && hasGuestObjectState(value)) {
455
483
  throw new TypeError("Guest prototype links and custom descriptors cannot be copied as data.");
456
484
  }
485
+ const primitive = nativeBoxedValue(value);
486
+ if (primitive !== undefined) {
487
+ const original = value;
488
+ const existing = state.seen.get(original);
489
+ if (existing !== undefined)
490
+ return existing;
491
+ const copy = createSandboxBox(primitive);
492
+ state.seen.set(original, copy);
493
+ if (!state.structuredClone) {
494
+ for (const [key, descriptor] of boxedDataProperties(original)) {
495
+ if (!("value" in descriptor))
496
+ throw new TypeError("Boxed data cannot contain accessors.");
497
+ Object.defineProperty(copy, key, {
498
+ ...descriptor,
499
+ value: copyToSandbox(descriptor.value, state, joinPath(path, key), cloneSandboxCollections, depth + 1)
500
+ });
501
+ }
502
+ }
503
+ return copy;
504
+ }
457
505
  if (isSandboxMap(value)) {
458
506
  if (!cloneSandboxCollections)
459
507
  return value;
@@ -595,6 +643,22 @@ function copyFromSandbox(value, state, path = "<root>", options, depth = 0) {
595
643
  if (!isSandboxClosure(value) && hasGuestObjectState(value)) {
596
644
  throw new TypeError("Guest prototype links and custom descriptors cannot be copied as data.");
597
645
  }
646
+ if (isSandboxBox(value)) {
647
+ const existing = state.seen.get(value);
648
+ if (existing !== undefined)
649
+ return existing;
650
+ const copy = Object(boxedValue(value));
651
+ state.seen.set(value, copy);
652
+ for (const [key, descriptor] of boxedDataProperties(value)) {
653
+ if (!("value" in descriptor))
654
+ throw new TypeError("Boxed data cannot contain accessors.");
655
+ Object.defineProperty(copy, key, {
656
+ ...descriptor,
657
+ value: copyFromSandbox(descriptor.value, state, joinPath(path, key), options, depth + 1)
658
+ });
659
+ }
660
+ return copy;
661
+ }
598
662
  const regexBrand = Object.getOwnPropertyDescriptor(value, sandboxRegexBrand);
599
663
  if (regexBrand !== undefined) {
600
664
  if (!("value" in regexBrand) || regexBrand.value !== true) {
@@ -756,6 +820,20 @@ function allocateSandboxValue(value, budget, seen) {
756
820
  budget.allocateString(value);
757
821
  return;
758
822
  }
823
+ if (isSandboxBox(value)) {
824
+ if (seen.has(value))
825
+ return;
826
+ seen.add(value);
827
+ const primitive = boxedValue(value);
828
+ if (typeof primitive === "string")
829
+ budget.allocateString(primitive);
830
+ for (const [key, descriptor] of boxedDataProperties(value)) {
831
+ budget.allocateString(key);
832
+ if ("value" in descriptor)
833
+ allocateSandboxValue(descriptor.value, budget, seen);
834
+ }
835
+ return;
836
+ }
759
837
  if (isFloat32Array(value)) {
760
838
  if (seen.has(value))
761
839
  return;
@@ -13,14 +13,14 @@ export declare function inspectSnapshotMigration(snapshot: SafeJSSnapshot, optio
13
13
  checkpointDigest: string;
14
14
  sourceHash: string;
15
15
  executionSemantics: string;
16
- calls: (Omit<import("./index.js").HostCallRecord, "outcome" | "asynchronous"> & {
16
+ calls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
17
17
  asynchronous: boolean;
18
18
  outcome?: {
19
19
  status: "fulfilled" | "rejected";
20
20
  data: import("./snapshot/replay-data.js").ReplayData;
21
21
  };
22
22
  })[];
23
- unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "outcome" | "asynchronous"> & {
23
+ unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
24
24
  asynchronous: boolean;
25
25
  outcome?: {
26
26
  status: "fulfilled" | "rejected";
@@ -12,7 +12,7 @@ export function inspectSnapshotMigration(snapshot, options) {
12
12
  validateSnapshotData(snapshot);
13
13
  validateDumpEnvelope(snapshot);
14
14
  validateMigrationSemantics(snapshot.executionSemantics);
15
- const sourceHash = hashSource(options.source);
15
+ const sourceHash = hashSource(options.source, undefined, snapshot.executionSemantics === EXECUTION_SEMANTICS);
16
16
  if (snapshot.sourceHash !== sourceHash)
17
17
  throw new TypeError(`Migration requires the original executable source (checkpoint hash ${snapshot.sourceHash}, supplied ${sourceHash}). Preserve the checkpoint; do not replace its hash.`);
18
18
  validateSnapshotMigration(snapshot.migration, sourceHash);
@@ -13,14 +13,14 @@ export declare function migrateSnapshotFile(options: SnapshotMigrationFileOption
13
13
  checkpointDigest: string;
14
14
  sourceHash: string;
15
15
  executionSemantics: string;
16
- calls: (Omit<import("./index.js").HostCallRecord, "outcome" | "asynchronous"> & {
16
+ calls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
17
17
  asynchronous: boolean;
18
18
  outcome?: {
19
19
  status: "fulfilled" | "rejected";
20
20
  data: import("./snapshot/replay-data.js").ReplayData;
21
21
  };
22
22
  })[];
23
- unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "outcome" | "asynchronous"> & {
23
+ unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
24
24
  asynchronous: boolean;
25
25
  outcome?: {
26
26
  status: "fulfilled" | "rejected";
@@ -35,14 +35,14 @@ export declare function migrateSnapshotFile(options: SnapshotMigrationFileOption
35
35
  checkpointDigest: string;
36
36
  sourceHash: string;
37
37
  executionSemantics: string;
38
- calls: (Omit<import("./index.js").HostCallRecord, "outcome" | "asynchronous"> & {
38
+ calls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
39
39
  asynchronous: boolean;
40
40
  outcome?: {
41
41
  status: "fulfilled" | "rejected";
42
42
  data: import("./snapshot/replay-data.js").ReplayData;
43
43
  };
44
44
  })[];
45
- unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "outcome" | "asynchronous"> & {
45
+ unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
46
46
  asynchronous: boolean;
47
47
  outcome?: {
48
48
  status: "fulfilled" | "rejected";
@@ -0,0 +1,7 @@
1
+ import type { FunctionNode } from "./parser.js";
2
+ export type FunctionSource = {
3
+ readonly text: string;
4
+ readonly start: number;
5
+ readonly end: number;
6
+ };
7
+ export declare const functionSources: WeakMap<FunctionNode, FunctionSource>;
@@ -0,0 +1 @@
1
+ export const functionSources = new WeakMap();
@@ -1,4 +1,4 @@
1
1
  import { type Module, type ParseResult } from "./parser.js";
2
2
  import { type CompileOwner } from "../interp/budget.js";
3
- export declare function hashSource(source: string, owner?: CompileOwner): string;
4
- export declare function hashParsedAst(ast: Module | ParseResult): string;
3
+ export declare function hashSource(source: string, owner?: CompileOwner, includeFunctionSource?: boolean): string;
4
+ export declare function hashParsedAst(ast: Module | ParseResult, includeFunctionSource?: boolean): string;
@@ -1,23 +1,24 @@
1
1
  import { parse, parseModule } from "./parser.js";
2
2
  import { SandboxError } from "../interp/budget.js";
3
+ import { functionSources } from "./function-source.js";
3
4
  const FNV_OFFSET_BASIS = 0x811c9dc5;
4
5
  const FNV_PRIME = 0x01000193;
5
6
  const IGNORED_KEYS = new Set(["nodeId", "raw", "span"]);
6
- export function hashSource(source, owner) {
7
+ export function hashSource(source, owner, includeFunctionSource = true) {
7
8
  try {
8
- return hashParsedAst(parse(source, "<input>", owner));
9
+ return hashParsedAst(parse(source, "<input>", owner), includeFunctionSource);
9
10
  }
10
11
  catch (error) {
11
12
  if (error instanceof SandboxError)
12
13
  throw error;
13
- return hashParsedAst(parseModule(source, "<input>", owner));
14
+ return hashParsedAst(parseModule(source, "<input>", owner), includeFunctionSource);
14
15
  }
15
16
  }
16
- export function hashParsedAst(ast) {
17
+ export function hashParsedAst(ast, includeFunctionSource = true) {
17
18
  let hash = FNV_OFFSET_BASIS;
18
19
  visit(ast);
19
20
  return hash.toString(16).padStart(8, "0");
20
- function visit(value, includeTemplateRaw = false) {
21
+ function visit(value, includeTemplateRaw = false, enclosingSource) {
21
22
  if (value === null) {
22
23
  write("null");
23
24
  return;
@@ -29,7 +30,7 @@ export function hashParsedAst(ast) {
29
30
  if (Array.isArray(value)) {
30
31
  write("[");
31
32
  for (const entry of value) {
32
- visit(entry, includeTemplateRaw);
33
+ visit(entry, includeTemplateRaw, enclosingSource);
33
34
  write(",");
34
35
  }
35
36
  write("]");
@@ -47,6 +48,19 @@ export function hashParsedAst(ast) {
47
48
  return;
48
49
  case "object": {
49
50
  const record = value;
51
+ const source = includeFunctionSource
52
+ ? functionSources.get(value)
53
+ : undefined;
54
+ if (source !== undefined &&
55
+ !(enclosingSource !== undefined &&
56
+ source.text === enclosingSource.text &&
57
+ source.start >= enclosingSource.start &&
58
+ source.end <= enclosingSource.end)) {
59
+ write("function-source:");
60
+ write(String(source.end - source.start));
61
+ write(":");
62
+ write(source.text, source.start, source.end);
63
+ }
50
64
  const keys = Object.keys(value)
51
65
  .filter((key) => shouldHashKey(record, key, includeTemplateRaw))
52
66
  .sort();
@@ -54,7 +68,7 @@ export function hashParsedAst(ast) {
54
68
  for (const key of keys) {
55
69
  write(JSON.stringify(key));
56
70
  write(":");
57
- visit(record[key], shouldIncludeTemplateRaw(record, key, includeTemplateRaw));
71
+ visit(record[key], shouldIncludeTemplateRaw(record, key, includeTemplateRaw), source ?? enclosingSource);
58
72
  write(",");
59
73
  }
60
74
  write("}");
@@ -64,8 +78,8 @@ export function hashParsedAst(ast) {
64
78
  throw new TypeError(`Unsupported AST value type: ${typeof value}`);
65
79
  }
66
80
  }
67
- function write(chunk) {
68
- for (let index = 0; index < chunk.length; index += 1) {
81
+ function write(chunk, start = 0, end = chunk.length) {
82
+ for (let index = start; index < end; index += 1) {
69
83
  hash ^= chunk.charCodeAt(index);
70
84
  hash = Math.imul(hash, FNV_PRIME) >>> 0;
71
85
  }