poe-code 14.0.62 → 14.0.64
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 +977 -528
- package/dist/index.js.map +4 -4
- package/dist/metafile.json +1 -1
- package/package.json +1 -1
- package/packages/safe-js/dist/chunks/{chunk-F7TJSPDZ.js → chunk-3EXDJDQM.js} +2 -2
- package/packages/safe-js/dist/chunks/{chunk-VKGWGZBH.js → chunk-XO7B4P27.js} +945 -545
- package/packages/safe-js/dist/chunks/chunk-XO7B4P27.js.map +7 -0
- package/packages/safe-js/dist/cli.js +2 -2
- package/packages/safe-js/dist/core.js +1 -1
- package/packages/safe-js/dist/graph-depth.js +3 -0
- package/packages/safe-js/dist/index.js +2 -2
- package/packages/safe-js/dist/interp/boxed.d.ts +14 -0
- package/packages/safe-js/dist/interp/boxed.js +50 -0
- package/packages/safe-js/dist/interp/globals/collections.js +4 -4
- package/packages/safe-js/dist/interp/globals/console-json.js +15 -18
- package/packages/safe-js/dist/interp/globals/misc.js +1 -1
- package/packages/safe-js/dist/interp/globals/object-array.js +19 -14
- package/packages/safe-js/dist/interp/globals/object.js +10 -5
- package/packages/safe-js/dist/interp/globals/primitives.d.ts +8 -0
- package/packages/safe-js/dist/interp/globals/primitives.js +69 -0
- package/packages/safe-js/dist/interp/host-bridge.js +22 -0
- package/packages/safe-js/dist/interp/interpreter.js +175 -122
- package/packages/safe-js/dist/interp/iteration.d.ts +5 -3
- package/packages/safe-js/dist/interp/iteration.js +22 -1
- package/packages/safe-js/dist/interp/methods/array.js +6 -3
- package/packages/safe-js/dist/interp/methods/function.js +32 -15
- package/packages/safe-js/dist/interp/methods/number.d.ts +1 -0
- package/packages/safe-js/dist/interp/methods/number.js +4 -1
- package/packages/safe-js/dist/interp/methods/string.d.ts +2 -0
- package/packages/safe-js/dist/interp/methods/string.js +2 -2
- package/packages/safe-js/dist/interp/object-model.d.ts +5 -0
- package/packages/safe-js/dist/interp/object-model.js +66 -14
- package/packages/safe-js/dist/interp/promise.js +68 -61
- package/packages/safe-js/dist/interp/string-coercion.js +4 -1
- package/packages/safe-js/dist/interp/values.d.ts +1 -0
- package/packages/safe-js/dist/interp/values.js +78 -3
- package/packages/safe-js/dist/migrate.d.ts +2 -2
- package/packages/safe-js/dist/migration-file.d.ts +4 -4
- package/packages/safe-js/dist/snapshot/boxed.d.ts +16 -0
- package/packages/safe-js/dist/snapshot/boxed.js +52 -0
- package/packages/safe-js/dist/snapshot/dump-format.js +18 -5
- package/packages/safe-js/dist/snapshot/replay-data.d.ts +5 -0
- package/packages/safe-js/dist/snapshot/replay-data.js +24 -0
- package/packages/safe-js/dist/snapshot/restore.js +8 -0
- package/packages/safe-js/dist/snapshot/serialize.d.ts +4 -2
- package/packages/safe-js/dist/snapshot/serialize.js +18 -10
- package/packages/safe-js/dist/snapshot/validation.js +22 -1
- package/packages/safe-js/dist/chunks/chunk-VKGWGZBH.js.map +0 -7
- /package/packages/safe-js/dist/chunks/{chunk-F7TJSPDZ.js.map → chunk-3EXDJDQM.js.map} +0 -0
|
@@ -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,28 +65,69 @@ 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);
|
|
64
|
-
|
|
65
|
-
|
|
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);
|
|
94
|
+
const records = [prototype, constructor].map(target => ({
|
|
95
|
+
target,
|
|
96
|
+
value: isGuestClosure(target) ? materializeFunctionProperties(target) : target,
|
|
97
|
+
prototype: getSandboxPrototype(target),
|
|
98
|
+
explicit: hasExplicitSandboxPrototype(target)
|
|
99
|
+
})).map(record => ({
|
|
100
|
+
...record, descriptors: new Map(Object.entries(Object.getOwnPropertyDescriptors(record.value)))
|
|
66
101
|
}));
|
|
67
|
-
const unchanged = (before, after) => before !== undefined && after !== undefined && before.value
|
|
102
|
+
const unchanged = (before, after) => before !== undefined && after !== undefined && Object.is(before.value, after.value) &&
|
|
68
103
|
before.writable === after.writable && before.configurable === after.configurable && before.enumerable === after.enumerable;
|
|
69
|
-
intrinsicConstructors.set(constructor, () => records.every(({ value, descriptors }) => {
|
|
104
|
+
intrinsicConstructors.set(constructor, () => records.every(({ target, value, descriptors, prototype: parent, explicit }) => {
|
|
70
105
|
const current = Object.getOwnPropertyDescriptors(value);
|
|
71
|
-
return
|
|
106
|
+
return getSandboxPrototype(target) === parent && hasExplicitSandboxPrototype(target) === explicit &&
|
|
107
|
+
Object.keys(current).length === descriptors.size &&
|
|
72
108
|
Object.keys(current).every(key => unchanged(descriptors.get(key), current[key]));
|
|
73
109
|
}));
|
|
74
|
-
budget.setRetainedValues(prototype, () => records.flatMap(({
|
|
110
|
+
budget.setRetainedValues(prototype, () => records.flatMap(({ target, value, descriptors, prototype: parent }) => [
|
|
111
|
+
...(getSandboxPrototype(target) === parent ? [] : [getSandboxPrototype(target)]),
|
|
112
|
+
...Object.entries(Object.getOwnPropertyDescriptors(value)).flatMap(([key, descriptor]) => unchanged(descriptors.get(key), descriptor) ? [] : [key, descriptor.value])
|
|
113
|
+
]));
|
|
75
114
|
}
|
|
76
115
|
export function releaseObjectPrototype(budget) {
|
|
77
|
-
const prototype
|
|
78
|
-
if (prototype !== undefined)
|
|
116
|
+
for (const prototype of intrinsicPrototypeRoots.get(budget) ?? [])
|
|
79
117
|
budget.setRetainedValues(prototype, undefined);
|
|
118
|
+
intrinsicPrototypeRoots.delete(budget);
|
|
119
|
+
boxedPrototypes.delete(budget);
|
|
80
120
|
intrinsicPrototypes.delete(budget);
|
|
81
121
|
}
|
|
82
122
|
export function getSandboxPrototype(value, budget) {
|
|
83
123
|
if (prototypes.has(value))
|
|
84
124
|
return prototypes.get(value) ?? null;
|
|
85
|
-
|
|
125
|
+
if (budget !== undefined && isSandboxBox(value)) {
|
|
126
|
+
const prototype = getBoxedPrototype(boxedValue(value), budget);
|
|
127
|
+
if (prototype !== undefined && prototype !== value)
|
|
128
|
+
return prototype;
|
|
129
|
+
}
|
|
130
|
+
return budget !== undefined && !isSandboxClosure(value) && isPrototypeRecord(value)
|
|
86
131
|
? intrinsicPrototypes.get(budget) ?? null
|
|
87
132
|
: null;
|
|
88
133
|
}
|
|
@@ -95,9 +140,12 @@ export function getSandboxDataProperty(value, key, budget) {
|
|
|
95
140
|
while (typeof current === "object" && current !== null) {
|
|
96
141
|
if (isGuestHostObject(current))
|
|
97
142
|
return getHostObjectMember(current, String(key));
|
|
98
|
-
if (isGuestClosure(current))
|
|
99
|
-
|
|
100
|
-
|
|
143
|
+
if (isGuestClosure(current)) {
|
|
144
|
+
const entry = getGuestFunctionProperty(current, String(key));
|
|
145
|
+
if (entry !== undefined || Object.hasOwn(current.properties ?? {}, String(key)))
|
|
146
|
+
return entry;
|
|
147
|
+
}
|
|
148
|
+
else if (isSandboxClosure(current)) {
|
|
101
149
|
const properties = current.properties;
|
|
102
150
|
return properties !== undefined && Object.hasOwn(properties, String(key))
|
|
103
151
|
? properties[String(key)]
|
|
@@ -109,7 +157,7 @@ export function getSandboxDataProperty(value, key, budget) {
|
|
|
109
157
|
isSandboxRegex(current) ||
|
|
110
158
|
isSandboxGenerator(current))
|
|
111
159
|
return undefined;
|
|
112
|
-
if (Object.hasOwn(current, String(key)))
|
|
160
|
+
if (!isSandboxClosure(current) && Object.hasOwn(current, String(key)))
|
|
113
161
|
return current[String(key)];
|
|
114
162
|
current = getSandboxPrototype(current, budget);
|
|
115
163
|
if (current !== null) {
|
|
@@ -124,7 +172,7 @@ export function setSandboxPrototype(value, prototype, budget) {
|
|
|
124
172
|
throw new TypeError("Object.prototype has an immutable null prototype.");
|
|
125
173
|
}
|
|
126
174
|
if (!isPrototypeRecord(value) || (prototype !== null && !isPrototypeRecord(prototype))) {
|
|
127
|
-
throw new TypeError("Prototype links require ordinary sandbox objects
|
|
175
|
+
throw new TypeError("Prototype links require ordinary sandbox objects or guest functions.");
|
|
128
176
|
}
|
|
129
177
|
if (prototypes.has(value) && getSandboxPrototype(value, budget) === prototype)
|
|
130
178
|
return;
|
|
@@ -141,6 +189,8 @@ export function setSandboxPrototype(value, prototype, budget) {
|
|
|
141
189
|
prototypes.set(value, prototype);
|
|
142
190
|
}
|
|
143
191
|
function isPrototypeRecord(value) {
|
|
192
|
+
if (isGuestClosure(value))
|
|
193
|
+
return true;
|
|
144
194
|
if (isGuestHostObject(value))
|
|
145
195
|
return false;
|
|
146
196
|
if (isSandboxClosure(value) ||
|
|
@@ -167,6 +217,8 @@ export function hasGuestObjectState(value) {
|
|
|
167
217
|
return true;
|
|
168
218
|
if (functionProperties.has(value) || prototypes.has(value))
|
|
169
219
|
return true;
|
|
220
|
+
if (isSandboxBox(value))
|
|
221
|
+
return false;
|
|
170
222
|
return (descriptorObjects.has(value) &&
|
|
171
223
|
Object.values(Object.getOwnPropertyDescriptors(value)).some((descriptor) => !descriptor.enumerable || !descriptor.configurable || !descriptor.writable));
|
|
172
224
|
}
|
|
@@ -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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
378
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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 &&
|
|
@@ -9,6 +9,7 @@ import { getGuestFunctionProperties, getSandboxPrototype, hasExplicitSandboxProt
|
|
|
9
9
|
import { getRegexMember } from "./methods/regex.js";
|
|
10
10
|
import { retainValues } from "./resources.js";
|
|
11
11
|
import { functionString } from "./function-string.js";
|
|
12
|
+
import { boxedValue, isSandboxBox } from "./boxed.js";
|
|
12
13
|
import { isSandboxClosure, isSandboxPromise, isSandboxMap, isSandboxSet, isSandboxRegex, isSandboxGenerator } from "./values.js";
|
|
13
14
|
const defaultStringHook = Symbol("defaultStringHook");
|
|
14
15
|
const defaultValueHook = Symbol("defaultValueHook");
|
|
@@ -40,7 +41,7 @@ async function objectToPrimitive(value, budget, context, joining, hint) {
|
|
|
40
41
|
result = await defaultToString(value, budget, context, joining);
|
|
41
42
|
}
|
|
42
43
|
else if (hook === defaultValueHook) {
|
|
43
|
-
result = isSandboxDate(value) ? dateTime(value) : value;
|
|
44
|
+
result = isSandboxDate(value) ? dateTime(value) : isSandboxBox(value) ? boxedValue(value) : value;
|
|
44
45
|
}
|
|
45
46
|
else {
|
|
46
47
|
if (!isSandboxClosure(hook))
|
|
@@ -97,6 +98,8 @@ function conversionHook(value, name, budget) {
|
|
|
97
98
|
return undefined;
|
|
98
99
|
}
|
|
99
100
|
async function defaultToString(value, budget, context, joining) {
|
|
101
|
+
if (isSandboxBox(value))
|
|
102
|
+
return budget.allocateString(String(boxedValue(value)));
|
|
100
103
|
if (isSandboxClosure(value))
|
|
101
104
|
return budget.allocateString(functionString(value));
|
|
102
105
|
if (isSandboxMap(value))
|
|
@@ -134,6 +134,7 @@ export declare function deepCopyToSandbox(value: unknown): SandboxValue;
|
|
|
134
134
|
export declare function cloneSandboxValue(value: SandboxValue, options?: {
|
|
135
135
|
compilation?: CompileScope;
|
|
136
136
|
resetRegexLastIndex?: boolean;
|
|
137
|
+
structuredClone?: boolean;
|
|
137
138
|
}): SandboxValue;
|
|
138
139
|
export declare function allocateProducedSandboxValue(value: SandboxValue, budget: Budget): SandboxValue;
|
|
139
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";
|
|
@@ -243,6 +244,19 @@ export function measureSandboxData(values, options = {}) {
|
|
|
243
244
|
assertSandboxDataDepth(depth);
|
|
244
245
|
seen.add(value);
|
|
245
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
|
+
}
|
|
246
260
|
if (isSandboxDate(value)) {
|
|
247
261
|
usage += 8;
|
|
248
262
|
return;
|
|
@@ -311,8 +325,19 @@ export function measureSandboxData(values, options = {}) {
|
|
|
311
325
|
if (isSandboxClosure(value)) {
|
|
312
326
|
if (options.ignoreClosures)
|
|
313
327
|
return;
|
|
314
|
-
if (value.properties !== undefined)
|
|
315
|
-
|
|
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
|
+
}
|
|
316
341
|
if (!options.ignoreClosureCaptures)
|
|
317
342
|
for (const retained of value[sandboxRetainedValues]?.() ?? [])
|
|
318
343
|
visit(retained, depth + 1);
|
|
@@ -457,6 +482,26 @@ function copyToSandbox(value, state, path = "<root>", cloneSandboxCollections =
|
|
|
457
482
|
if (typeof value === "object" && value !== null && hasGuestObjectState(value)) {
|
|
458
483
|
throw new TypeError("Guest prototype links and custom descriptors cannot be copied as data.");
|
|
459
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
|
+
}
|
|
460
505
|
if (isSandboxMap(value)) {
|
|
461
506
|
if (!cloneSandboxCollections)
|
|
462
507
|
return value;
|
|
@@ -598,6 +643,22 @@ function copyFromSandbox(value, state, path = "<root>", options, depth = 0) {
|
|
|
598
643
|
if (!isSandboxClosure(value) && hasGuestObjectState(value)) {
|
|
599
644
|
throw new TypeError("Guest prototype links and custom descriptors cannot be copied as data.");
|
|
600
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
|
+
}
|
|
601
662
|
const regexBrand = Object.getOwnPropertyDescriptor(value, sandboxRegexBrand);
|
|
602
663
|
if (regexBrand !== undefined) {
|
|
603
664
|
if (!("value" in regexBrand) || regexBrand.value !== true) {
|
|
@@ -759,6 +820,20 @@ function allocateSandboxValue(value, budget, seen) {
|
|
|
759
820
|
budget.allocateString(value);
|
|
760
821
|
return;
|
|
761
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
|
+
}
|
|
762
837
|
if (isFloat32Array(value)) {
|
|
763
838
|
if (seen.has(value))
|
|
764
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, "
|
|
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, "
|
|
23
|
+
unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
|
|
24
24
|
asynchronous: boolean;
|
|
25
25
|
outcome?: {
|
|
26
26
|
status: "fulfilled" | "rejected";
|
|
@@ -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, "
|
|
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, "
|
|
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, "
|
|
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, "
|
|
45
|
+
unresolvedCalls: (Omit<import("./index.js").HostCallRecord, "asynchronous" | "outcome"> & {
|
|
46
46
|
asynchronous: boolean;
|
|
47
47
|
outcome?: {
|
|
48
48
|
status: "fulfilled" | "rejected";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type SandboxBox } from "../interp/boxed.js";
|
|
2
|
+
import type { SandboxValue } from "../interp/values.js";
|
|
3
|
+
export type BoxedData<T> = {
|
|
4
|
+
kind: "boxed";
|
|
5
|
+
value: T;
|
|
6
|
+
properties: Record<string, {
|
|
7
|
+
value: T;
|
|
8
|
+
configurable: boolean;
|
|
9
|
+
enumerable: boolean;
|
|
10
|
+
writable: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
extensible: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare function encodeBoxedData<T>(value: SandboxBox, encode: (value: SandboxValue, key: string) => T): BoxedData<T>;
|
|
15
|
+
export declare function restoreBoxedProperties<T>(value: SandboxBox, data: BoxedData<T>, decode: (value: T) => unknown): void;
|
|
16
|
+
export declare function validateBoxedProperties(data: Record<string, unknown>): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { boxedDataProperties, boxedValue } from "../interp/boxed.js";
|
|
2
|
+
export function encodeBoxedData(value, encode) {
|
|
3
|
+
const properties = Object.create(null);
|
|
4
|
+
for (const [key, descriptor] of boxedDataProperties(value)) {
|
|
5
|
+
if (!("value" in descriptor))
|
|
6
|
+
throw new TypeError("Boxed data cannot contain accessors.");
|
|
7
|
+
properties[key] = {
|
|
8
|
+
value: encode(descriptor.value, key),
|
|
9
|
+
configurable: descriptor.configurable === true,
|
|
10
|
+
enumerable: descriptor.enumerable === true,
|
|
11
|
+
writable: descriptor.writable === true
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
kind: "boxed",
|
|
16
|
+
value: encode(boxedValue(value), "<payload>"),
|
|
17
|
+
properties,
|
|
18
|
+
extensible: Object.isExtensible(value)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function restoreBoxedProperties(value, data, decode) {
|
|
22
|
+
for (const [key, descriptor] of Object.entries(data.properties))
|
|
23
|
+
Object.defineProperty(value, key, { ...descriptor, value: decode(descriptor.value) });
|
|
24
|
+
if (!data.extensible)
|
|
25
|
+
Object.preventExtensions(value);
|
|
26
|
+
}
|
|
27
|
+
export function validateBoxedProperties(data) {
|
|
28
|
+
if (!Object.hasOwn(data, "kind") ||
|
|
29
|
+
data.kind !== "boxed" ||
|
|
30
|
+
!Object.hasOwn(data, "value") ||
|
|
31
|
+
!Object.hasOwn(data, "extensible") ||
|
|
32
|
+
!Object.hasOwn(data, "properties") ||
|
|
33
|
+
Object.keys(data).length !== 4 ||
|
|
34
|
+
typeof data.extensible !== "boolean" ||
|
|
35
|
+
typeof data.properties !== "object" ||
|
|
36
|
+
data.properties === null ||
|
|
37
|
+
Array.isArray(data.properties))
|
|
38
|
+
throw new TypeError("Invalid boxed primitive properties.");
|
|
39
|
+
for (const descriptor of Object.values(data.properties)) {
|
|
40
|
+
if (typeof descriptor !== "object" ||
|
|
41
|
+
descriptor === null ||
|
|
42
|
+
!Object.hasOwn(descriptor, "value") ||
|
|
43
|
+
!Object.hasOwn(descriptor, "writable") ||
|
|
44
|
+
!Object.hasOwn(descriptor, "enumerable") ||
|
|
45
|
+
!Object.hasOwn(descriptor, "configurable") ||
|
|
46
|
+
typeof descriptor.writable !== "boolean" ||
|
|
47
|
+
typeof descriptor.enumerable !== "boolean" ||
|
|
48
|
+
typeof descriptor.configurable !== "boolean" ||
|
|
49
|
+
Object.keys(descriptor).length !== 4)
|
|
50
|
+
throw new TypeError("Invalid boxed primitive descriptor.");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -9,6 +9,8 @@ import { requiresArrayEntries, serializeArray } from "./arrays.js";
|
|
|
9
9
|
import { float32DataProperties, isFloat32Array } from "../interp/float32.js";
|
|
10
10
|
import { encodeFloat32Storage } from "./float32array.js";
|
|
11
11
|
import { isSandboxDate, serializedDateTime } from "../interp/date.js";
|
|
12
|
+
import { boxedDataProperties, isSandboxBox } from "../interp/boxed.js";
|
|
13
|
+
import { encodeBoxedData } from "./boxed.js";
|
|
12
14
|
const SKIP_VALUE = Symbol("SafeJS.skip-dump-value");
|
|
13
15
|
export function serializeSafeJSSnapshot(snapshot) {
|
|
14
16
|
const replayError = Object.getOwnPropertyDescriptor(snapshot, "replayError");
|
|
@@ -73,7 +75,7 @@ function serializeDumpValue(value, path, state) {
|
|
|
73
75
|
if (hasGuestObjectState(value)) {
|
|
74
76
|
throw new TypeError("Guest function properties and prototype links cannot be serialized.");
|
|
75
77
|
}
|
|
76
|
-
if (isSandboxDate(value) || isFloat32Array(value))
|
|
78
|
+
if (isSandboxBox(value) || isSandboxDate(value) || isFloat32Array(value))
|
|
77
79
|
return serializeHeapReference(value, path, state);
|
|
78
80
|
if (Array.isArray(value)) {
|
|
79
81
|
const reference = serializeHeapReference(value, path, state);
|
|
@@ -98,7 +100,15 @@ function serializeHeapReference(value, path, state) {
|
|
|
98
100
|
}
|
|
99
101
|
if (!state.serializedHeapIds.has(id)) {
|
|
100
102
|
state.serializedHeapIds.add(id);
|
|
101
|
-
if (
|
|
103
|
+
if (isSandboxBox(value)) {
|
|
104
|
+
state.heap[String(id)] = encodeBoxedData(value, (entry, key) => {
|
|
105
|
+
if (Object.is(entry, -0))
|
|
106
|
+
return { kind: "number", value: "-0" };
|
|
107
|
+
const serialized = serializeDumpValue(entry, `${path}.${key}`, state);
|
|
108
|
+
return serialized === SKIP_VALUE ? { kind: "undefined" } : serialized;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else if (isSandboxDate(value)) {
|
|
102
112
|
state.heap[String(id)] = { kind: "date", time: serializedDateTime(value) };
|
|
103
113
|
}
|
|
104
114
|
else if (isFloat32Array(value)) {
|
|
@@ -163,6 +173,7 @@ function indexHeapContainers(snapshot) {
|
|
|
163
173
|
for (const [value, stat] of stats.entries()) {
|
|
164
174
|
if (stat.count > 1 ||
|
|
165
175
|
stat.cyclic ||
|
|
176
|
+
isSandboxBox(value) ||
|
|
166
177
|
isSandboxDate(value) ||
|
|
167
178
|
isFloat32Array(value) ||
|
|
168
179
|
(Array.isArray(value) && requiresArrayEntries(value)) ||
|
|
@@ -200,9 +211,11 @@ function collectContainerStats(value, stats, ancestors) {
|
|
|
200
211
|
}
|
|
201
212
|
stat.expanded = true;
|
|
202
213
|
ancestors.add(value);
|
|
203
|
-
const entries =
|
|
204
|
-
?
|
|
205
|
-
:
|
|
214
|
+
const entries = isSandboxBox(value)
|
|
215
|
+
? boxedDataProperties(value).map(([, descriptor]) => descriptor.value)
|
|
216
|
+
: isSandboxArguments(value)
|
|
217
|
+
? getSandboxArgumentEntries(value).map(([, entry]) => entry)
|
|
218
|
+
: getEnumerableDataValues(value);
|
|
206
219
|
for (const entry of entries) {
|
|
207
220
|
collectContainerStats(entry, stats, ancestors);
|
|
208
221
|
}
|