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.
- package/dist/index.js +511 -125
- package/dist/index.js.map +4 -4
- package/dist/metafile.json +1 -1
- package/package.json +3 -3
- package/packages/safe-js/dist/chunks/{chunk-BWVASPJX.js → chunk-MO3YD4ID.js} +479 -111
- package/packages/safe-js/dist/chunks/chunk-MO3YD4ID.js.map +7 -0
- package/packages/safe-js/dist/chunks/{chunk-ETKM3KPA.js → chunk-RXLDJ6LQ.js} +2 -2
- package/packages/safe-js/dist/cli.js +2 -2
- package/packages/safe-js/dist/core.d.ts +1 -1
- package/packages/safe-js/dist/core.js +1 -1
- package/packages/safe-js/dist/index.d.ts +1 -1
- package/packages/safe-js/dist/index.js +2 -2
- package/packages/safe-js/dist/index.js.map +1 -1
- package/packages/safe-js/dist/interp/async.js +1 -0
- package/packages/safe-js/dist/interp/globals/object-array.d.ts +2 -2
- package/packages/safe-js/dist/interp/globals/object-array.js +32 -4
- package/packages/safe-js/dist/interp/globals/object.d.ts +4 -0
- package/packages/safe-js/dist/interp/globals/object.js +153 -0
- package/packages/safe-js/dist/interp/globals.d.ts +1 -1
- package/packages/safe-js/dist/interp/host-capabilities.d.ts +21 -1
- package/packages/safe-js/dist/interp/host-capabilities.js +191 -4
- package/packages/safe-js/dist/interp/interpreter.js +27 -9
- package/packages/safe-js/dist/interp/iteration.js +3 -0
- package/packages/safe-js/dist/interp/object-model.d.ts +3 -1
- package/packages/safe-js/dist/interp/object-model.js +39 -9
- package/packages/safe-js/dist/interp/values.d.ts +2 -0
- package/packages/safe-js/dist/interp/values.js +13 -6
- package/packages/safe-js/dist/realm.js +15 -2
- package/packages/safe-js/dist/run.js +2 -0
- package/packages/safe-js/dist/chunks/chunk-BWVASPJX.js.map +0 -7
- /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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|