poe-code 14.0.7 → 14.0.9

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 (31) hide show
  1. package/dist/index.js +511 -125
  2. package/dist/index.js.map +4 -4
  3. package/dist/metafile.json +1 -1
  4. package/package.json +3 -3
  5. package/packages/safe-js/dist/chunks/{chunk-BWVASPJX.js → chunk-MO3YD4ID.js} +479 -111
  6. package/packages/safe-js/dist/chunks/chunk-MO3YD4ID.js.map +7 -0
  7. package/packages/safe-js/dist/chunks/{chunk-ETKM3KPA.js → chunk-RXLDJ6LQ.js} +2 -2
  8. package/packages/safe-js/dist/cli.js +2 -2
  9. package/packages/safe-js/dist/core.d.ts +1 -1
  10. package/packages/safe-js/dist/core.js +1 -1
  11. package/packages/safe-js/dist/index.d.ts +1 -1
  12. package/packages/safe-js/dist/index.js +2 -2
  13. package/packages/safe-js/dist/index.js.map +1 -1
  14. package/packages/safe-js/dist/interp/async.js +1 -0
  15. package/packages/safe-js/dist/interp/globals/object-array.d.ts +2 -2
  16. package/packages/safe-js/dist/interp/globals/object-array.js +32 -4
  17. package/packages/safe-js/dist/interp/globals/object.d.ts +4 -0
  18. package/packages/safe-js/dist/interp/globals/object.js +153 -0
  19. package/packages/safe-js/dist/interp/globals.d.ts +1 -1
  20. package/packages/safe-js/dist/interp/host-capabilities.d.ts +21 -1
  21. package/packages/safe-js/dist/interp/host-capabilities.js +191 -4
  22. package/packages/safe-js/dist/interp/interpreter.js +27 -9
  23. package/packages/safe-js/dist/interp/iteration.js +3 -0
  24. package/packages/safe-js/dist/interp/object-model.d.ts +3 -1
  25. package/packages/safe-js/dist/interp/object-model.js +39 -9
  26. package/packages/safe-js/dist/interp/values.d.ts +2 -0
  27. package/packages/safe-js/dist/interp/values.js +13 -6
  28. package/packages/safe-js/dist/realm.js +15 -2
  29. package/packages/safe-js/dist/run.js +2 -0
  30. package/packages/safe-js/dist/chunks/chunk-BWVASPJX.js.map +0 -7
  31. /package/packages/safe-js/dist/chunks/{chunk-ETKM3KPA.js.map → chunk-RXLDJ6LQ.js.map} +0 -0
@@ -1,4 +1,8 @@
1
1
  import { readDataRecord } from "../extensions.js";
2
+ import { types } from "node:util";
3
+ const MAX_INDEXED_LENGTH = 65_536;
4
+ const MAX_NAMED_KEYS = 65_536;
5
+ const MAX_NAMED_KEY_CODE_UNITS = 1_048_576;
2
6
  const hostObjects = new WeakMap();
3
7
  const guestObjects = new WeakMap();
4
8
  const guestCallbacks = new WeakMap();
@@ -27,8 +31,53 @@ export function revokeGuestReference(reference, owner) {
27
31
  }
28
32
  export function createLiveHostObject(definition, controller) {
29
33
  const input = readDataRecord(definition, "Host object definition");
30
- if (Object.keys(input).some((key) => key !== "properties" && key !== "methods"))
34
+ if (Object.keys(input).some((key) => !["properties", "methods", "indexed", "named"].includes(key)))
31
35
  throw new TypeError("Unknown host object definition field.");
36
+ let indexed;
37
+ if (input.indexed !== undefined) {
38
+ const data = readDataRecord(input.indexed, "Indexed host capability");
39
+ if (Object.keys(data).some((key) => !["length", "get", "maxLength"].includes(key)))
40
+ throw new TypeError("Unknown indexed host capability field.");
41
+ if (typeof data.length !== "function" || typeof data.get !== "function")
42
+ throw new TypeError("Indexed length and get must be synchronous functions.");
43
+ if (typeof data.maxLength !== "number" ||
44
+ !Number.isInteger(data.maxLength) ||
45
+ data.maxLength < 1 ||
46
+ data.maxLength > MAX_INDEXED_LENGTH)
47
+ throw new RangeError(`Indexed maxLength must be an integer from 1 to ${MAX_INDEXED_LENGTH}.`);
48
+ indexed = {
49
+ length: data.length,
50
+ get: data.get,
51
+ maxLength: data.maxLength
52
+ };
53
+ }
54
+ let named;
55
+ if (input.named !== undefined) {
56
+ const data = readDataRecord(input.named, "Named host capability");
57
+ if (Object.keys(data).some((key) => !["keys", "get", "maxKeys", "maxKeyCodeUnits", "enumerable"].includes(key)))
58
+ throw new TypeError("Unknown named host capability field.");
59
+ if (typeof data.keys !== "function" || typeof data.get !== "function")
60
+ throw new TypeError("Named keys and get must be synchronous functions.");
61
+ if (typeof data.maxKeys !== "number" ||
62
+ !Number.isInteger(data.maxKeys) ||
63
+ data.maxKeys < 1 ||
64
+ data.maxKeys > MAX_NAMED_KEYS)
65
+ throw new RangeError(`Named maxKeys must be an integer from 1 to ${MAX_NAMED_KEYS}.`);
66
+ if (typeof data.maxKeyCodeUnits !== "number" ||
67
+ !Number.isInteger(data.maxKeyCodeUnits) ||
68
+ data.maxKeyCodeUnits < 1 ||
69
+ data.maxKeyCodeUnits > MAX_NAMED_KEY_CODE_UNITS)
70
+ throw new RangeError(`Named maxKeyCodeUnits must be an integer from 1 to ${MAX_NAMED_KEY_CODE_UNITS}.`);
71
+ if (data.enumerable !== undefined && typeof data.enumerable !== "boolean")
72
+ throw new TypeError("Named enumerable must be a boolean.");
73
+ named = {
74
+ keys: data.keys,
75
+ get: data.get,
76
+ maxKeys: data.maxKeys,
77
+ maxKeyCodeUnits: data.maxKeyCodeUnits,
78
+ enumerable: data.enumerable
79
+ };
80
+ }
32
81
  const properties = new Map();
33
82
  for (const [name, inputProperty] of Object.entries(readDataRecord(input.properties ?? {}, "Host properties"))) {
34
83
  const property = readDataRecord(inputProperty, `Host property '${name}'`);
@@ -49,6 +98,8 @@ export function createLiveHostObject(definition, controller) {
49
98
  for (const name of [...properties.keys(), ...Object.keys(operations)]) {
50
99
  if (["constructor", "prototype", "__proto__"].includes(name))
51
100
  throw new TypeError(`Reserved host member '${name}'.`);
101
+ if (indexed !== undefined && (name === "length" || canonicalIndex(name) !== undefined))
102
+ throw new TypeError(`Conflicting indexed host member '${name}'.`);
52
103
  }
53
104
  controller.assertActive();
54
105
  controller.chargeWork(properties.size + Object.keys(operations).length + 1);
@@ -58,7 +109,7 @@ export function createLiveHostObject(definition, controller) {
58
109
  name,
59
110
  controller.method(operation)
60
111
  ]));
61
- const state = { host, guest, controller, properties, methods };
112
+ const state = { host, guest, controller, properties, methods, indexed, named };
62
113
  hostObjects.set(host, state);
63
114
  guestObjects.set(guest, state);
64
115
  return host;
@@ -125,15 +176,32 @@ export function revokeHostObject(value, owner) {
125
176
  throw new TypeError("Foreign host object.");
126
177
  state.properties.clear();
127
178
  state.methods.clear();
179
+ state.indexed = undefined;
180
+ state.named = undefined;
128
181
  }
129
182
  export function getHostObjectMember(value, key) {
130
183
  const state = guestObjects.get(value);
131
184
  state.controller.assertActive();
132
185
  state.controller.chargeWork();
186
+ if (state.indexed !== undefined) {
187
+ if (key === "length")
188
+ return indexedLength(state);
189
+ const index = canonicalIndex(key);
190
+ if (index !== undefined) {
191
+ if (index >= state.indexed.maxLength || index >= indexedLength(state))
192
+ return undefined;
193
+ return state.controller.read(() => state.indexed.get(index));
194
+ }
195
+ }
133
196
  const property = state.properties.get(key);
134
197
  if (property !== undefined)
135
198
  return property.get === undefined ? undefined : state.controller.read(property.get);
136
- return state.methods.get(key);
199
+ const method = state.methods.get(key);
200
+ if (method !== undefined)
201
+ return method;
202
+ if (state.named !== undefined && namedKeys(state).includes(key))
203
+ return state.controller.read(() => state.named.get(key));
204
+ return undefined;
137
205
  }
138
206
  export function setHostObjectMember(value, key, entry) {
139
207
  const state = guestObjects.get(value);
@@ -147,5 +215,124 @@ export function setHostObjectMember(value, key, entry) {
147
215
  export function getHostObjectKeys(value) {
148
216
  const state = guestObjects.get(value);
149
217
  state.controller.assertActive();
150
- return [...state.properties.keys(), ...state.methods.keys()];
218
+ const length = state.indexed === undefined ? 0 : indexedLength(state);
219
+ const names = state.named === undefined || state.named.enumerable === false
220
+ ? []
221
+ : namedKeys(state).filter((key) => !state.properties.has(key) &&
222
+ !state.methods.has(key) &&
223
+ !(state.indexed !== undefined &&
224
+ (key === "length" || canonicalIndex(key) !== undefined)));
225
+ const size = length + state.properties.size + state.methods.size + names.length;
226
+ state.controller.checkLength(size);
227
+ state.controller.chargeWork(size + 1);
228
+ return [
229
+ ...Array.from({ length }, (_entry, index) => String(index)),
230
+ ...state.properties.keys(),
231
+ ...state.methods.keys(),
232
+ ...names
233
+ ];
234
+ }
235
+ export function hasHostObjectMember(value, key, enumerableOnly = false) {
236
+ const state = guestObjects.get(value);
237
+ state.controller.assertActive();
238
+ state.controller.chargeWork();
239
+ if (state.indexed !== undefined) {
240
+ if (key === "length")
241
+ return !enumerableOnly;
242
+ const index = canonicalIndex(key);
243
+ if (index !== undefined)
244
+ return index < state.indexed.maxLength && index < indexedLength(state);
245
+ }
246
+ if (state.properties.has(key) || state.methods.has(key))
247
+ return true;
248
+ return (state.named !== undefined &&
249
+ !(enumerableOnly && state.named.enumerable === false) &&
250
+ namedKeys(state).includes(key));
251
+ }
252
+ export function measureHostObjectData(value) {
253
+ const state = guestObjects.get(value);
254
+ let size = state.indexed === undefined ? 0 : 16;
255
+ if (state.named !== undefined)
256
+ size += 24;
257
+ for (const key of state.properties.keys())
258
+ size += key.length + 1;
259
+ for (const key of state.methods.keys())
260
+ size += key.length + 1;
261
+ return size;
262
+ }
263
+ export function getHostObjectIterator(value) {
264
+ const state = guestObjects.get(value);
265
+ state.controller.assertActive();
266
+ if (state.indexed === undefined)
267
+ return undefined;
268
+ let index = 0;
269
+ let exhausted = false;
270
+ return {
271
+ next: () => {
272
+ state.controller.assertActive();
273
+ state.controller.chargeWork();
274
+ if (exhausted)
275
+ return { done: true, value: undefined };
276
+ if (index >= indexedLength(state)) {
277
+ exhausted = true;
278
+ return { done: true, value: undefined };
279
+ }
280
+ const position = index++;
281
+ return { done: false, value: state.controller.read(() => state.indexed.get(position)) };
282
+ }
283
+ };
284
+ }
285
+ function indexedLength(state) {
286
+ state.controller.chargeWork();
287
+ const length = state.controller.read(state.indexed.length);
288
+ if (typeof length !== "number" ||
289
+ !Number.isInteger(length) ||
290
+ length < 0 ||
291
+ length > state.indexed.maxLength)
292
+ throw new RangeError("Indexed length must be a non-negative integer within maxLength.");
293
+ state.controller.checkLength(length);
294
+ return length;
295
+ }
296
+ function canonicalIndex(key) {
297
+ const index = Number(key);
298
+ return Number.isInteger(index) && index >= 0 && index < 0xffffffff && String(index) === key
299
+ ? index
300
+ : undefined;
301
+ }
302
+ function namedKeys(state) {
303
+ const named = state.named;
304
+ return state.controller.read(named.keys, (value) => {
305
+ if (!Array.isArray(value) || types.isProxy(value))
306
+ throw new TypeError("Named keys must be a dense own-data array of strings, not a proxy.");
307
+ const length = Object.getOwnPropertyDescriptor(value, "length").value;
308
+ if (length > named.maxKeys)
309
+ throw new RangeError("Named keys exceed maxKeys.");
310
+ state.controller.checkLength(length);
311
+ state.controller.chargeWork(length + 1);
312
+ if (Reflect.ownKeys(value).length !== length + 1)
313
+ throw new TypeError("Named keys must contain only dense own-data indices and length.");
314
+ const result = [];
315
+ const seen = new Set();
316
+ let units = 0;
317
+ for (let index = 0; index < length; index++) {
318
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
319
+ if (descriptor === undefined ||
320
+ !("value" in descriptor) ||
321
+ typeof descriptor.value !== "string")
322
+ throw new TypeError("Named keys require dense own string data, not accessors or sparse arrays.");
323
+ const key = descriptor.value;
324
+ units += key.length;
325
+ if (units > named.maxKeyCodeUnits)
326
+ throw new RangeError("Named keys exceed maximum UTF-16 code units.");
327
+ state.controller.chargeWork(key.length);
328
+ if (seen.has(key))
329
+ throw new TypeError("Named keys must be distinct.");
330
+ if (["constructor", "prototype", "__proto__"].includes(key))
331
+ throw new TypeError(`Reserved named host member '${key}'.`);
332
+ seen.add(key);
333
+ result.push(key);
334
+ }
335
+ state.controller.checkTemporaryDataSize(1 + length + units);
336
+ return result;
337
+ });
151
338
  }
@@ -1,5 +1,6 @@
1
1
  import { promiseReplayContext } from "./promise-replay.js";
2
- import { getHostObjectKeys, getHostObjectMember, isGuestHostObject, setHostObjectMember } from "./host-capabilities.js";
2
+ import { getHostObjectKeys, getHostObjectMember, hasHostObjectMember, isGuestHostObject, setHostObjectMember } from "./host-capabilities.js";
3
+ import { sandboxString } from "./string-coercion.js";
3
4
  import { assertPromiseExecutionAllowed } from "./promise-tracker.js";
4
5
  import { SandboxJobQueue, runAsyncPrefix, suspendJob } from "./jobs.js";
5
6
  import { withCancellationSignal } from "./cancel.js";
@@ -11,7 +12,7 @@ import { CompileScope, RegexCompileGuard } from "./regex/compile-guard.js";
11
12
  import { coerceThrownValue, createCapturedException, evaluateThrowStatement as evaluateThrowStatementResult, evaluateTryStatement as evaluateTryStatementResult, isCapturedException, surfaceThrownValue } from "./exceptions.js";
12
13
  import { callArrayMethod, getArrayMember, isArrayMethodName } from "./methods/array.js";
13
14
  import { getFunctionMember } from "./methods/function.js";
14
- import { getGuestFunctionProperty, getSandboxPrototype, isGuestClosure, materializeFunctionProperties } from "./object-model.js";
15
+ import { getGuestFunctionProperty, getSandboxPrototype, isGuestClosure, materializeFunctionProperties, setSandboxPrototype } from "./object-model.js";
15
16
  import { assertSandboxDataDepth } from "../graph-depth.js";
16
17
  import { callMapMethod, getMapMember, isMapMethodName } from "./methods/map.js";
17
18
  import { callNumberMethod, getNumberMember, isNumberMethodName } from "./methods/number.js";
@@ -325,6 +326,9 @@ async function evaluateObjectExpression(node, context) {
325
326
  return value;
326
327
  }
327
328
  if (isObjectPrototypeSetterProperty(property, key.value)) {
329
+ if (value.value === null || typeof value.value === "object") {
330
+ setSandboxPrototype(object, value.value, context.budget);
331
+ }
328
332
  continue;
329
333
  }
330
334
  defineSandboxProperty(object, String(key.value), value.value);
@@ -433,7 +437,13 @@ async function evaluateBinaryExpression(node, context) {
433
437
  if (right.kind !== "normal") {
434
438
  return right;
435
439
  }
436
- const value = applyBinaryOperator(node, left.value, right.value, context);
440
+ const value = node.operator === "in" && isGuestHostObject(right.value)
441
+ ? hasHostObjectMember(right.value, await sandboxString(left.value, context.budget, {
442
+ stack: context.callStack,
443
+ thisValue: undefined,
444
+ invokeClosure: (closure, args, thisValue) => invokeSandboxClosure(closure, args, context, context.callStack, undefined, thisValue)
445
+ }))
446
+ : applyBinaryOperator(node, left.value, right.value, context);
437
447
  return {
438
448
  kind: "normal",
439
449
  hasValue: true,
@@ -1130,7 +1140,7 @@ function forInKeys(object, budget) {
1130
1140
  const keys = [];
1131
1141
  const seen = new Set();
1132
1142
  let depth = 0;
1133
- for (let current = object; current !== null; current = getSandboxPrototype(current)) {
1143
+ for (let current = object; current !== null; current = getSandboxPrototype(current, budget)) {
1134
1144
  if (depth > 0)
1135
1145
  budget.visitNode();
1136
1146
  assertSandboxDataDepth(depth++);
@@ -1149,9 +1159,9 @@ function forInKeys(object, budget) {
1149
1159
  }
1150
1160
  function hasForInProperty(object, key, budget) {
1151
1161
  if (isGuestHostObject(object))
1152
- return getHostObjectKeys(object).includes(key);
1162
+ return hasHostObjectMember(object, key, true);
1153
1163
  let depth = 0;
1154
- for (let current = object; current !== null; current = getSandboxPrototype(current)) {
1164
+ for (let current = object; current !== null; current = getSandboxPrototype(current, budget)) {
1155
1165
  if (depth > 0)
1156
1166
  budget.visitNode();
1157
1167
  assertSandboxDataDepth(depth++);
@@ -2099,7 +2109,7 @@ function applyBinaryOperator(node, left, right, context) {
2099
2109
  throw new TypeError("Function has a non-object prototype in instanceof check.");
2100
2110
  }
2101
2111
  let depth = 0;
2102
- for (let current = getSandboxPrototype(left); current !== null; current = getSandboxPrototype(current)) {
2112
+ for (let current = getSandboxPrototype(left, context.budget); current !== null; current = getSandboxPrototype(current, context.budget)) {
2103
2113
  context.budget.visitNode();
2104
2114
  assertSandboxDataDepth(depth++);
2105
2115
  if (current === prototype)
@@ -2323,7 +2333,7 @@ function getMemberValue(target, property, context) {
2323
2333
  }
2324
2334
  if (Object.hasOwn(current, String(property)))
2325
2335
  return current[String(property)];
2326
- current = getSandboxPrototype(current);
2336
+ current = getSandboxPrototype(current, context.budget);
2327
2337
  if (current !== null) {
2328
2338
  context.budget.visitNode();
2329
2339
  assertSandboxDataDepth(++depth);
@@ -2376,7 +2386,7 @@ export function setSandboxProperty(target, property, value, budget) {
2376
2386
  else {
2377
2387
  if (typeof prototypeOwner === "object" && prototypeOwner !== null) {
2378
2388
  let depth = 0;
2379
- for (let prototype = getSandboxPrototype(prototypeOwner); prototype !== null; prototype = getSandboxPrototype(prototype)) {
2389
+ for (let prototype = getSandboxPrototype(prototypeOwner, budget); prototype !== null; prototype = getSandboxPrototype(prototype, budget)) {
2380
2390
  budget.visitNode();
2381
2391
  assertSandboxDataDepth(depth++);
2382
2392
  const properties = isSandboxClosure(prototype) ? prototype.properties : prototype;
@@ -2570,6 +2580,14 @@ async function evaluateObjectSpread(node, context) {
2570
2580
  value: []
2571
2581
  };
2572
2582
  }
2583
+ if (isGuestHostObject(value.value)) {
2584
+ const entries = [];
2585
+ for (const key of getHostObjectKeys(value.value)) {
2586
+ if (hasHostObjectMember(value.value, key, true))
2587
+ entries.push([key, getHostObjectMember(value.value, key)]);
2588
+ }
2589
+ return { ok: true, value: entries };
2590
+ }
2573
2591
  if ((isSandboxClosure(value.value) && !isGuestClosure(value.value)) || isSandboxPromise(value.value)) {
2574
2592
  throw new TypeError(`Cannot spread ${describeObjectSpreadValue(value.value)} into object literal.`);
2575
2593
  }
@@ -1,7 +1,10 @@
1
1
  import { isSandboxGenerator, isSandboxMap, isSandboxSet } from "./values.js";
2
2
  import { enterRunningState } from "./running-state.js";
3
3
  import { isFloat32Array } from "./float32.js";
4
+ import { getHostObjectIterator, isGuestHostObject } from "./host-capabilities.js";
4
5
  export function getSandboxIterator(value) {
6
+ if (isGuestHostObject(value))
7
+ return getHostObjectIterator(value);
5
8
  if (isFloat32Array(value)) {
6
9
  return syncIterator(Float32Array.prototype.values.call(value));
7
10
  }
@@ -5,7 +5,9 @@ export declare function isGuestClosure(value: unknown): value is SandboxClosure;
5
5
  export declare function getGuestFunctionProperties(closure: SandboxClosure): SandboxObject | undefined;
6
6
  export declare function materializeFunctionProperties(closure: SandboxClosure): SandboxObject;
7
7
  export declare function getGuestFunctionProperty(closure: SandboxClosure, key: string): SandboxValue;
8
- export declare function getSandboxPrototype(value: object): object | null;
8
+ export declare function installObjectPrototype(budget: Budget, prototype: SandboxObject, constructor: SandboxClosure): void;
9
+ export declare function releaseObjectPrototype(budget: Budget): void;
10
+ export declare function getSandboxPrototype(value: object, budget?: Budget): object | null;
9
11
  export declare function getSandboxDataProperty(value: SandboxValue, key: string | number, budget?: Budget): SandboxValue;
10
12
  export declare function setSandboxPrototype(value: object, prototype: object | null, budget?: Budget): void;
11
13
  export declare function markDescriptorObject(value: object): void;
@@ -4,6 +4,8 @@ import { isSandboxClosure, isSandboxGenerator, isSandboxMap, isSandboxPromise, i
4
4
  const guestClosures = new WeakSet();
5
5
  const functionProperties = new WeakMap();
6
6
  const prototypes = new WeakMap();
7
+ const intrinsicPrototypes = new WeakMap();
8
+ const intrinsicConstructors = new WeakMap();
7
9
  const descriptorObjects = new WeakSet();
8
10
  export function registerGuestClosure(closure) {
9
11
  guestClosures.add(closure);
@@ -56,8 +58,33 @@ export function getGuestFunctionProperty(closure, key) {
56
58
  ? undefined
57
59
  : Object.getOwnPropertyDescriptor(properties, key)?.value;
58
60
  }
59
- export function getSandboxPrototype(value) {
60
- return prototypes.get(value) ?? null;
61
+ export function installObjectPrototype(budget, prototype, constructor) {
62
+ prototypes.set(prototype, null);
63
+ intrinsicPrototypes.set(budget, prototype);
64
+ const records = [prototype, materializeFunctionProperties(constructor)].map(value => ({
65
+ value, descriptors: new Map(Object.entries(Object.getOwnPropertyDescriptors(value)))
66
+ }));
67
+ const unchanged = (before, after) => before !== undefined && after !== undefined && before.value === after.value &&
68
+ before.writable === after.writable && before.configurable === after.configurable && before.enumerable === after.enumerable;
69
+ intrinsicConstructors.set(constructor, () => records.every(({ value, descriptors }) => {
70
+ const current = Object.getOwnPropertyDescriptors(value);
71
+ return Object.keys(current).length === descriptors.size &&
72
+ Object.keys(current).every(key => unchanged(descriptors.get(key), current[key]));
73
+ }));
74
+ budget.setRetainedValues(prototype, () => records.flatMap(({ value, descriptors }) => Object.entries(Object.getOwnPropertyDescriptors(value)).flatMap(([key, descriptor]) => unchanged(descriptors.get(key), descriptor) ? [] : [key, descriptor.value])));
75
+ }
76
+ export function releaseObjectPrototype(budget) {
77
+ const prototype = intrinsicPrototypes.get(budget);
78
+ if (prototype !== undefined)
79
+ budget.setRetainedValues(prototype, undefined);
80
+ intrinsicPrototypes.delete(budget);
81
+ }
82
+ export function getSandboxPrototype(value, budget) {
83
+ if (prototypes.has(value))
84
+ return prototypes.get(value) ?? null;
85
+ return budget !== undefined && isPrototypeRecord(value)
86
+ ? intrinsicPrototypes.get(budget) ?? null
87
+ : null;
61
88
  }
62
89
  export function getSandboxDataProperty(value, key, budget) {
63
90
  let current = value;
@@ -81,7 +108,7 @@ export function getSandboxDataProperty(value, key, budget) {
81
108
  return undefined;
82
109
  if (Object.hasOwn(current, String(key)))
83
110
  return current[String(key)];
84
- current = getSandboxPrototype(current);
111
+ current = getSandboxPrototype(current, budget);
85
112
  if (current !== null) {
86
113
  budget?.visitNode();
87
114
  assertSandboxDataDepth(++depth);
@@ -90,25 +117,25 @@ export function getSandboxDataProperty(value, key, budget) {
90
117
  return undefined;
91
118
  }
92
119
  export function setSandboxPrototype(value, prototype, budget) {
120
+ if (budget !== undefined && intrinsicPrototypes.get(budget) === value && prototype !== null) {
121
+ throw new TypeError("Object.prototype has an immutable null prototype.");
122
+ }
93
123
  if (!isPrototypeRecord(value) || (prototype !== null && !isPrototypeRecord(prototype))) {
94
124
  throw new TypeError("Prototype links require ordinary sandbox objects; callable and exotic prototype chains are not supported.");
95
125
  }
96
- if (getSandboxPrototype(value) === prototype)
126
+ if (prototypes.has(value) && getSandboxPrototype(value, budget) === prototype)
97
127
  return;
98
128
  if (!Object.isExtensible(isGuestClosure(value) ? materializeFunctionProperties(value) : value)) {
99
129
  throw new TypeError("Cannot change the prototype of a non-extensible object.");
100
130
  }
101
131
  let depth = 0;
102
- for (let current = prototype; current !== null; current = getSandboxPrototype(current)) {
132
+ for (let current = prototype; current !== null; current = getSandboxPrototype(current, budget)) {
103
133
  budget?.visitNode();
104
134
  assertSandboxDataDepth(depth++);
105
135
  if (current === value)
106
136
  throw new TypeError("Cyclic prototype value.");
107
137
  }
108
- if (prototype === null)
109
- prototypes.delete(value);
110
- else
111
- prototypes.set(value, prototype);
138
+ prototypes.set(value, prototype);
112
139
  }
113
140
  function isPrototypeRecord(value) {
114
141
  if (isGuestHostObject(value))
@@ -130,6 +157,9 @@ export function hasManagedDescriptors(value) {
130
157
  return descriptorObjects.has(value);
131
158
  }
132
159
  export function hasGuestObjectState(value) {
160
+ const intrinsicUnchanged = intrinsicConstructors.get(value);
161
+ if (intrinsicUnchanged !== undefined)
162
+ return !intrinsicUnchanged();
133
163
  if (isLiveCapability(value))
134
164
  return true;
135
165
  if (functionProperties.has(value) || prototypes.has(value))
@@ -55,6 +55,7 @@ export type SandboxCallContext = {
55
55
  };
56
56
  export type SandboxClosure = {
57
57
  readonly async?: true;
58
+ readonly generator?: true;
58
59
  readonly sandbox?: true;
59
60
  readonly boundTarget?: SandboxClosure;
60
61
  readonly cancellationSignal?: AbortSignal;
@@ -92,6 +93,7 @@ type CopyFromSandboxOptions = {
92
93
  export declare function createSandboxClosure(input: {
93
94
  guest?: boolean;
94
95
  async?: boolean;
96
+ generator?: boolean;
95
97
  sandbox?: boolean;
96
98
  boundTarget?: SandboxClosure;
97
99
  cancellationSignal?: AbortSignal;
@@ -1,6 +1,6 @@
1
1
  import { bindOtelSpan, getBoundOtelSpan } from "../observability/otel.js";
2
2
  import { copyNativeDate, exportDate, isSandboxDate } from "./date.js";
3
- import { getHostObjectKeys, getHostObjectMember, isGuestHostObject, isLiveCapability } from "./host-capabilities.js";
3
+ import { getHostObjectKeys, getHostObjectMember, hasHostObjectMember, measureHostObjectData, isGuestHostObject, isLiveCapability } from "./host-capabilities.js";
4
4
  import { types as nodeTypes } from "node:util";
5
5
  import { CompileScope, RegexCompileGuard, regexCompiledData } from "./regex/compile-guard.js";
6
6
  import { copyFloat32Storage, float32DataProperties, float32Storage, isFloat32Array } from "./float32.js";
@@ -41,6 +41,8 @@ export function createSandboxClosure(input) {
41
41
  if (input.sandbox === true) {
42
42
  Object.defineProperty(closure, "sandbox", { value: true });
43
43
  }
44
+ if (input.generator === true)
45
+ Object.defineProperty(closure, "generator", { value: true });
44
46
  if (input.length !== undefined) {
45
47
  Object.defineProperty(closure, "length", { value: input.length });
46
48
  }
@@ -72,8 +74,14 @@ export function createSandboxClosure(input) {
72
74
  return Object.freeze(closure);
73
75
  }
74
76
  export function ownEnumerableSandboxEntries(value) {
75
- if (isGuestHostObject(value))
76
- return getHostObjectKeys(value).map(key => [key, getHostObjectMember(value, key)]);
77
+ if (isGuestHostObject(value)) {
78
+ const entries = [];
79
+ for (const key of getHostObjectKeys(value)) {
80
+ if (hasHostObjectMember(value, key, true))
81
+ entries.push([key, getHostObjectMember(value, key)]);
82
+ }
83
+ return entries;
84
+ }
77
85
  if (value === null || value === undefined)
78
86
  throw new TypeError("Cannot convert undefined or null to object.");
79
87
  if (isGuestClosure(value))
@@ -231,8 +239,7 @@ export function measureSandboxData(values, options = {}) {
231
239
  return;
232
240
  }
233
241
  if (isGuestHostObject(value)) {
234
- for (const key of getHostObjectKeys(value))
235
- usage += key.length + 1;
242
+ usage += measureHostObjectData(value);
236
243
  return;
237
244
  }
238
245
  const prototype = getSandboxPrototype(value);
@@ -346,7 +353,7 @@ export function reconcileCompiledValues(budget, values, compilation, parent, esc
346
353
  while (parent?.closed)
347
354
  parent = parent.parent;
348
355
  const included = new Set();
349
- const usage = measureSandboxData(values, { compileTickets: included });
356
+ const usage = measureSandboxData([...values, ...budget.retainedValues()], { compileTickets: included });
350
357
  const kept = new Set();
351
358
  if (parent !== undefined)
352
359
  measureSandboxData(escaping, { compileTickets: kept });
@@ -3,6 +3,7 @@ import { types } from "node:util";
3
3
  import { Budget, SandboxError } from "./interp/budget.js";
4
4
  import { CompileScope } from "./interp/regex/compile-guard.js";
5
5
  import { createBuiltinBindings } from "./interp/globals.js";
6
+ import { releaseObjectPrototype } from "./interp/object-model.js";
6
7
  import { interpret, Scope } from "./interp/interpreter.js";
7
8
  import { SandboxJobQueue, runAsyncPrefix, suspendJob } from "./interp/jobs.js";
8
9
  import { withCancellationSignal, awaitSandboxValue } from "./interp/cancel.js";
@@ -143,6 +144,7 @@ class RealmState {
143
144
  this.tracker.onFatalRejection((error) => this.poison(error));
144
145
  }
145
146
  catch (error) {
147
+ releaseObjectPrototype(this.budget);
146
148
  this.compilation.dispose();
147
149
  this.lease.release();
148
150
  throw error;
@@ -310,13 +312,23 @@ class RealmState {
310
312
  owner: this,
311
313
  assertActive: this.assertOpen,
312
314
  chargeWork: this.chargeWork,
313
- read: (operation) => {
315
+ checkLength: (length) => this.budget.allocateArrayLength(length),
316
+ checkTemporaryDataSize: (size) => {
317
+ const temporary = {};
318
+ try {
319
+ this.budget.setRetainedDataUsage(temporary, size);
320
+ }
321
+ finally {
322
+ this.budget.setRetainedDataUsage(temporary, 0);
323
+ }
324
+ },
325
+ read: (operation, validate) => {
314
326
  const value = this.invokeHost(operation, operation);
315
327
  if (types.isPromise(value)) {
316
328
  void Promise.resolve(value).catch(() => undefined);
317
329
  throw new TypeError("Live property getters must be synchronous.");
318
330
  }
319
- return this.importValue(value);
331
+ return this.importValue(validate === undefined ? value : validate(value));
320
332
  },
321
333
  write: (operation, value) => {
322
334
  const result = this.invokeHost(operation, () => operation(this.exportValue(value)));
@@ -656,6 +668,7 @@ class RealmState {
656
668
  revokeGuestReference(reference, this);
657
669
  this.guestReferences.clear();
658
670
  this.budget.setRetainedValues(this, undefined);
671
+ releaseObjectPrototype(this.budget);
659
672
  this.disposal = (async () => {
660
673
  const errors = [];
661
674
  for (const cleanup of this.cleanups.splice(0).reverse()) {
@@ -14,6 +14,7 @@ import { enterSnapshotRun } from "./interp/running-state.js";
14
14
  import { createSandboxPromiseRejectionTracker, observeSandboxPromise, withSandboxPromiseRejectionTracker } from "./interp/promise-tracker.js";
15
15
  import { HostCallJournal } from "./interp/host-call.js";
16
16
  import { createBuiltinBindings } from "./interp/globals.js";
17
+ import { releaseObjectPrototype } from "./interp/object-model.js";
17
18
  import { declareHostOperation, wrapCallerInjectedBindings } from "./interp/host-bridge.js";
18
19
  import { interpret, Scope } from "./interp/interpreter.js";
19
20
  import { consumeSettledHostCall } from "./interp/promise.js";
@@ -349,6 +350,7 @@ export function run(source, options = {}) {
349
350
  });
350
351
  }
351
352
  finally {
353
+ releaseObjectPrototype(budget);
352
354
  compilation.dispose();
353
355
  operation.release();
354
356
  }