poe-code 14.0.6 → 14.0.7
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 +454 -39
- 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-2M6MNQBY.js → chunk-BWVASPJX.js} +420 -33
- package/packages/safe-js/dist/chunks/chunk-BWVASPJX.js.map +7 -0
- package/packages/safe-js/dist/chunks/{chunk-QSSQJNKE.js → chunk-ETKM3KPA.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/extensions.d.ts +2 -0
- 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/date.d.ts +19 -0
- package/packages/safe-js/dist/interp/date.js +129 -0
- package/packages/safe-js/dist/interp/globals/console-json.js +5 -1
- package/packages/safe-js/dist/interp/globals/date.d.ts +10 -0
- package/packages/safe-js/dist/interp/globals/date.js +95 -0
- package/packages/safe-js/dist/interp/globals/object-array.d.ts +2 -1
- package/packages/safe-js/dist/interp/globals/object-array.js +10 -1
- package/packages/safe-js/dist/interp/globals.d.ts +3 -0
- package/packages/safe-js/dist/interp/globals.js +2 -0
- package/packages/safe-js/dist/interp/host-bridge.d.ts +4 -0
- package/packages/safe-js/dist/interp/host-bridge.js +21 -2
- package/packages/safe-js/dist/interp/host-call.js +4 -0
- package/packages/safe-js/dist/interp/host-capabilities.d.ts +7 -0
- package/packages/safe-js/dist/interp/host-capabilities.js +29 -1
- package/packages/safe-js/dist/interp/interpreter.js +19 -2
- package/packages/safe-js/dist/interp/string-coercion.js +3 -0
- package/packages/safe-js/dist/interp/values.d.ts +1 -1
- package/packages/safe-js/dist/interp/values.js +21 -0
- package/packages/safe-js/dist/lint/rules/known-globals.d.ts +1 -1
- package/packages/safe-js/dist/lint/rules/known-globals.js +1 -0
- package/packages/safe-js/dist/parse/parser.js +4 -2
- package/packages/safe-js/dist/realm.d.ts +4 -1
- package/packages/safe-js/dist/realm.js +74 -8
- package/packages/safe-js/dist/run.d.ts +2 -0
- package/packages/safe-js/dist/run.js +1 -1
- package/packages/safe-js/dist/snapshot/dump-format.js +8 -3
- package/packages/safe-js/dist/snapshot/replay-data.d.ts +4 -1
- package/packages/safe-js/dist/snapshot/replay-data.js +11 -0
- package/packages/safe-js/dist/snapshot/restore.js +6 -0
- package/packages/safe-js/dist/snapshot/serialize.d.ts +5 -2
- package/packages/safe-js/dist/snapshot/serialize.js +8 -2
- package/packages/safe-js/dist/snapshot/validation.js +18 -1
- package/packages/safe-js/dist/chunks/chunk-2M6MNQBY.js.map +0 -7
- /package/packages/safe-js/dist/chunks/{chunk-QSSQJNKE.js.map → chunk-ETKM3KPA.js.map} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeClosureResult } from "./async.js";
|
|
2
|
+
import { copyNativeDate } from "./date.js";
|
|
2
3
|
import { exportHostCapability, importHostCapability, isLiveCapability } from "./host-capabilities.js";
|
|
3
4
|
import { attachErrorSpan, replaceErrorStack } from "../error/shape.js";
|
|
4
5
|
import { SandboxError } from "./budget.js";
|
|
@@ -77,11 +78,13 @@ function wrapCallerInjectedFunction(name, value, options, state) {
|
|
|
77
78
|
seen: new WeakMap(),
|
|
78
79
|
restored: []
|
|
79
80
|
};
|
|
80
|
-
const
|
|
81
|
+
const copyArguments = (values) => deepCopyFromSandbox([...values], {
|
|
81
82
|
compilation,
|
|
82
83
|
unwrapHostObject: options.realm === undefined ? undefined : object => exportHostCapability(object, options.realm.owner),
|
|
83
84
|
wrapClosure: (closure) => options.realm?.wrapCallback(closure) ?? wrapSandboxClosureForHost(closure, stackFrames, options.budget, operationLease.owner, callbacks)
|
|
84
85
|
});
|
|
86
|
+
const captured = options.realm?.captureArguments(callable, args, copyArguments);
|
|
87
|
+
const hostArgs = captured?.args ?? copyArguments(args);
|
|
85
88
|
const hostCalls = options.hostCalls;
|
|
86
89
|
const operation = options.operation ?? bindingName;
|
|
87
90
|
const moduleId = options.moduleId ?? "<bindings>";
|
|
@@ -90,7 +93,14 @@ function wrapCallerInjectedFunction(name, value, options, state) {
|
|
|
90
93
|
"re-issue";
|
|
91
94
|
if (hostCalls === undefined) {
|
|
92
95
|
if (options.realm !== undefined) {
|
|
93
|
-
|
|
96
|
+
let result;
|
|
97
|
+
try {
|
|
98
|
+
result = options.realm.invoke(callable, () => Reflect.apply(callable, undefined, hostArgs));
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
captured.rollback();
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
94
104
|
if (options.realm.awaitResult(callable)) {
|
|
95
105
|
return Promise.resolve(result).then(value => copyHostResultToSandbox(value, stackFrames, options));
|
|
96
106
|
}
|
|
@@ -658,6 +668,15 @@ export function copyHostValueToSandbox(value, stackFrames, options, state, path)
|
|
|
658
668
|
: path
|
|
659
669
|
}, state);
|
|
660
670
|
}
|
|
671
|
+
const date = copyNativeDate(value);
|
|
672
|
+
if (date !== undefined) {
|
|
673
|
+
const existing = state.seen.get(value);
|
|
674
|
+
if (existing !== undefined)
|
|
675
|
+
return existing;
|
|
676
|
+
budget.chargeDataUsage(9);
|
|
677
|
+
state.seen.set(value, date);
|
|
678
|
+
return date;
|
|
679
|
+
}
|
|
661
680
|
if (isFloat32Array(value)) {
|
|
662
681
|
const existing = state.seen.get(value);
|
|
663
682
|
if (existing !== undefined)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { float32DataProperties, float32Storage, isFloat32Array } from "./float32.js";
|
|
3
|
+
import { copyNativeDate, serializedDateTime } from "./date.js";
|
|
3
4
|
import { cloneSandboxValue, createSandboxClosure, defineOwnDataProperty, isArrayIndexKey, measureSandboxData } from "./values.js";
|
|
4
5
|
import { CompileScope } from "./regex/compile-guard.js";
|
|
5
6
|
import { decodeReplayData, encodeReplayData } from "../snapshot/replay-data.js";
|
|
@@ -581,6 +582,9 @@ function normalize(value, seen) {
|
|
|
581
582
|
throw new TypeError("Host call arguments cannot contain cycles.");
|
|
582
583
|
seen.add(value);
|
|
583
584
|
try {
|
|
585
|
+
const date = copyNativeDate(value);
|
|
586
|
+
if (date !== undefined)
|
|
587
|
+
return Object.assign(Object.create(null), { $type: "date", time: serializedDateTime(date) });
|
|
584
588
|
if (isFloat32Array(value)) {
|
|
585
589
|
const storage = float32Storage(value);
|
|
586
590
|
const properties = Object.create(null);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type HostOperation } from "../extensions.js";
|
|
2
2
|
import type { SandboxClosure, SandboxObject, SandboxValue } from "./values.js";
|
|
3
3
|
declare const hostObjectBrand: unique symbol;
|
|
4
|
+
declare const guestReferenceBrand: unique symbol;
|
|
4
5
|
declare const sandboxHostObjectBrand: unique symbol;
|
|
5
6
|
type SandboxHostObject = SandboxObject & {
|
|
6
7
|
readonly [sandboxHostObjectBrand]: true;
|
|
@@ -8,6 +9,9 @@ type SandboxHostObject = SandboxObject & {
|
|
|
8
9
|
export type HostObject = Readonly<Record<string, never>> & {
|
|
9
10
|
readonly [hostObjectBrand]: true;
|
|
10
11
|
};
|
|
12
|
+
export type GuestReference = Readonly<Record<string, never>> & {
|
|
13
|
+
readonly [guestReferenceBrand]: true;
|
|
14
|
+
};
|
|
11
15
|
export type HostObjectDefinition = {
|
|
12
16
|
properties?: Record<string, {
|
|
13
17
|
get?: () => unknown;
|
|
@@ -28,6 +32,9 @@ type GuestCallbackState = {
|
|
|
28
32
|
closure?: SandboxClosure;
|
|
29
33
|
assertActive(): void;
|
|
30
34
|
};
|
|
35
|
+
export declare function createGuestReference(root: [SandboxValue], owner: object, assertActive: () => void): GuestReference;
|
|
36
|
+
export declare function readGuestReference(reference: unknown, owner: object): SandboxValue;
|
|
37
|
+
export declare function revokeGuestReference(reference: GuestReference, owner: object): void;
|
|
31
38
|
export declare function createLiveHostObject(definition: HostObjectDefinition, controller: HostObjectController): HostObject;
|
|
32
39
|
export declare function isGuestHostObject(value: unknown): value is SandboxHostObject;
|
|
33
40
|
export declare function isLiveCapability(value: unknown): boolean;
|
|
@@ -2,6 +2,29 @@ import { readDataRecord } from "../extensions.js";
|
|
|
2
2
|
const hostObjects = new WeakMap();
|
|
3
3
|
const guestObjects = new WeakMap();
|
|
4
4
|
const guestCallbacks = new WeakMap();
|
|
5
|
+
const guestReferences = new WeakMap();
|
|
6
|
+
export function createGuestReference(root, owner, assertActive) {
|
|
7
|
+
const reference = Object.freeze(Object.create(null));
|
|
8
|
+
guestReferences.set(reference, { root, owner, assertActive });
|
|
9
|
+
return reference;
|
|
10
|
+
}
|
|
11
|
+
export function readGuestReference(reference, owner) {
|
|
12
|
+
const state = typeof reference === "object" && reference !== null
|
|
13
|
+
? guestReferences.get(reference)
|
|
14
|
+
: undefined;
|
|
15
|
+
if (state === undefined || state.owner !== owner)
|
|
16
|
+
throw new TypeError("Foreign or invalid guest reference.");
|
|
17
|
+
state.assertActive();
|
|
18
|
+
if (state.root === undefined)
|
|
19
|
+
throw new TypeError("Guest reference is revoked.");
|
|
20
|
+
return state.root[0];
|
|
21
|
+
}
|
|
22
|
+
export function revokeGuestReference(reference, owner) {
|
|
23
|
+
const state = guestReferences.get(reference);
|
|
24
|
+
if (state === undefined || state.owner !== owner)
|
|
25
|
+
throw new TypeError("Foreign guest reference.");
|
|
26
|
+
state.root = undefined;
|
|
27
|
+
}
|
|
5
28
|
export function createLiveHostObject(definition, controller) {
|
|
6
29
|
const input = readDataRecord(definition, "Host object definition");
|
|
7
30
|
if (Object.keys(input).some((key) => key !== "properties" && key !== "methods"))
|
|
@@ -45,9 +68,14 @@ export function isGuestHostObject(value) {
|
|
|
45
68
|
}
|
|
46
69
|
export function isLiveCapability(value) {
|
|
47
70
|
return (((typeof value === "object" && value !== null) || typeof value === "function") &&
|
|
48
|
-
(hostObjects.has(value) ||
|
|
71
|
+
(hostObjects.has(value) ||
|
|
72
|
+
guestObjects.has(value) ||
|
|
73
|
+
guestCallbacks.has(value) ||
|
|
74
|
+
guestReferences.has(value)));
|
|
49
75
|
}
|
|
50
76
|
export function importHostCapability(value, owner) {
|
|
77
|
+
if (guestReferences.has(value))
|
|
78
|
+
return readGuestReference(value, owner);
|
|
51
79
|
const object = hostObjects.get(value);
|
|
52
80
|
if (object !== undefined) {
|
|
53
81
|
if (object.controller.owner !== owner)
|
|
@@ -27,6 +27,8 @@ import { isSandboxErrorConstructorInstance } from "./globals/error.js";
|
|
|
27
27
|
import { isSandboxMapConstructor, isSandboxSetConstructor } from "./globals/collections.js";
|
|
28
28
|
import { getFloat32Member, isFloat32ArrayConstructor, setFloat32Member } from "./globals/float32array.js";
|
|
29
29
|
import { isFloat32Array } from "./float32.js";
|
|
30
|
+
import { dateString, dateTime, isSandboxDate } from "./date.js";
|
|
31
|
+
import { getDateMember, getDatePrototype, isDateConstructor } from "./globals/date.js";
|
|
30
32
|
import { createSandboxRegex, allocateProducedSandboxValue, isSandboxClosure, isSandboxGenerator, isSandboxMap, isSandboxPromise, isSandboxRegex, isSandboxSet, reconcileCompiledValues } from "./values.js";
|
|
31
33
|
import { Scope } from "./scope.js";
|
|
32
34
|
import { hoistVarDeclarations } from "./var-hoist.js";
|
|
@@ -1648,6 +1650,8 @@ function getPropertyValue(target, property, context) {
|
|
|
1648
1650
|
return undefined;
|
|
1649
1651
|
if (isFloat32Array(target))
|
|
1650
1652
|
return getFloat32Member(target, property, context.budget);
|
|
1653
|
+
if (isSandboxDate(target))
|
|
1654
|
+
return getDateMember(property, context.budget, context.compilation?.owner);
|
|
1651
1655
|
if (isSandboxMap(target))
|
|
1652
1656
|
return getMapMember(target, property, createMapMethodOptions(context));
|
|
1653
1657
|
if (isSandboxSet(target))
|
|
@@ -1864,6 +1868,9 @@ async function evaluateMemberCallExpression(node, context) {
|
|
|
1864
1868
|
if (isSandboxMap(member.object)) {
|
|
1865
1869
|
return evaluatePrimitiveMemberCall(node, "Map", member.property, getMapMember(member.object, member.property, createMapMethodOptions(context)), context);
|
|
1866
1870
|
}
|
|
1871
|
+
if (isSandboxDate(member.object)) {
|
|
1872
|
+
return evaluateResolvedCallExpression(node, getDateMember(member.property, context.budget, context.compilation?.owner), context, member.object);
|
|
1873
|
+
}
|
|
1867
1874
|
if (isFloat32Array(member.object)) {
|
|
1868
1875
|
return evaluateResolvedCallExpression(node, getFloat32Member(member.object, member.property, context.budget), context, member.object);
|
|
1869
1876
|
}
|
|
@@ -2076,6 +2083,8 @@ function applyBinaryOperator(node, left, right, context) {
|
|
|
2076
2083
|
}
|
|
2077
2084
|
if (isFloat32ArrayConstructor(right))
|
|
2078
2085
|
return isFloat32Array(left);
|
|
2086
|
+
if (isDateConstructor(right))
|
|
2087
|
+
return isSandboxDate(left) && getDatePrototype(left, context.budget, context.compilation?.owner) !== null;
|
|
2079
2088
|
if (isSandboxSetConstructor(right) && isSandboxSet(left)) {
|
|
2080
2089
|
return true;
|
|
2081
2090
|
}
|
|
@@ -2141,8 +2150,8 @@ function applyAdditionOperator(left, right, context) {
|
|
|
2141
2150
|
return toNumber(leftPrimitive) + toNumber(rightPrimitive);
|
|
2142
2151
|
}
|
|
2143
2152
|
function compareRelational(left, right, operator) {
|
|
2144
|
-
const leftPrimitive = toPrimitive(left);
|
|
2145
|
-
const rightPrimitive = toPrimitive(right);
|
|
2153
|
+
const leftPrimitive = isSandboxDate(left) ? dateTime(left) : toPrimitive(left);
|
|
2154
|
+
const rightPrimitive = isSandboxDate(right) ? dateTime(right) : toPrimitive(right);
|
|
2146
2155
|
if (typeof leftPrimitive === "string" && typeof rightPrimitive === "string") {
|
|
2147
2156
|
switch (operator) {
|
|
2148
2157
|
case "<":
|
|
@@ -2225,6 +2234,8 @@ function toPrimitive(value) {
|
|
|
2225
2234
|
return toString(value);
|
|
2226
2235
|
}
|
|
2227
2236
|
async function toNumericPrimitive(value, context) {
|
|
2237
|
+
if (isSandboxDate(value))
|
|
2238
|
+
return dateTime(value);
|
|
2228
2239
|
if (isPrimitiveCoercionType(getCoercionType(value))) {
|
|
2229
2240
|
return value;
|
|
2230
2241
|
}
|
|
@@ -2247,6 +2258,8 @@ async function toNumericPrimitive(value, context) {
|
|
|
2247
2258
|
return toString(value);
|
|
2248
2259
|
}
|
|
2249
2260
|
function toNumber(value) {
|
|
2261
|
+
if (isSandboxDate(value))
|
|
2262
|
+
return dateTime(value);
|
|
2250
2263
|
if (typeof value === "number") {
|
|
2251
2264
|
return value;
|
|
2252
2265
|
}
|
|
@@ -2265,6 +2278,8 @@ function toNumber(value) {
|
|
|
2265
2278
|
return toNumber(toPrimitive(value));
|
|
2266
2279
|
}
|
|
2267
2280
|
function toString(value) {
|
|
2281
|
+
if (isSandboxDate(value))
|
|
2282
|
+
return dateString(value);
|
|
2268
2283
|
if (Array.isArray(value)) {
|
|
2269
2284
|
return value
|
|
2270
2285
|
.map((entry) => (entry === null || entry === undefined ? "" : toString(entry)))
|
|
@@ -2323,6 +2338,8 @@ function getArrayMemberValue(target, property, context) {
|
|
|
2323
2338
|
return getArrayMember(target, property, createArrayMethodOptions(context));
|
|
2324
2339
|
}
|
|
2325
2340
|
export function setSandboxProperty(target, property, value, budget) {
|
|
2341
|
+
if (isSandboxDate(target))
|
|
2342
|
+
throw new TypeError("Date own properties are not supported.");
|
|
2326
2343
|
if (isGuestHostObject(target)) {
|
|
2327
2344
|
setHostObjectMember(target, String(property), value);
|
|
2328
2345
|
return;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sandboxErrorTypes } from "../error/shape.js";
|
|
2
|
+
import { dateString, isSandboxDate } from "./date.js";
|
|
2
3
|
import { float32Storage, isFloat32Array } from "./float32.js";
|
|
3
4
|
import { isSandboxClosure, isSandboxPromise } from "./values.js";
|
|
4
5
|
export function sandboxString(value, budget, context, joining = new Set()) {
|
|
@@ -41,6 +42,8 @@ async function stringifyObject(value, budget, context, joining) {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
async function defaultToString(value, budget, context, joining) {
|
|
45
|
+
if (isSandboxDate(value))
|
|
46
|
+
return budget.allocateString(dateString(value));
|
|
44
47
|
if (Array.isArray(value) || isFloat32Array(value)) {
|
|
45
48
|
if (Object.hasOwn(value, "join")) {
|
|
46
49
|
const join = ownDataValue(value, "join");
|
|
@@ -12,7 +12,7 @@ declare const sandboxRegexPattern: unique symbol;
|
|
|
12
12
|
declare const sandboxSetBrand: unique symbol;
|
|
13
13
|
declare const sandboxRetainedValues: unique symbol;
|
|
14
14
|
export type SandboxPrimitive = string | number | boolean | null | undefined;
|
|
15
|
-
export type SandboxValue = SandboxPrimitive | Float32Array | SandboxObject | SandboxArray | SandboxClosure | SandboxGenerator | SandboxMap | SandboxSet | SandboxPromise | SandboxRegex;
|
|
15
|
+
export type SandboxValue = SandboxPrimitive | Date | Float32Array | SandboxObject | SandboxArray | SandboxClosure | SandboxGenerator | SandboxMap | SandboxSet | SandboxPromise | SandboxRegex;
|
|
16
16
|
export type SandboxObject = {
|
|
17
17
|
[key: string]: SandboxValue;
|
|
18
18
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { bindOtelSpan, getBoundOtelSpan } from "../observability/otel.js";
|
|
2
|
+
import { copyNativeDate, exportDate, isSandboxDate } from "./date.js";
|
|
2
3
|
import { getHostObjectKeys, getHostObjectMember, isGuestHostObject, isLiveCapability } from "./host-capabilities.js";
|
|
3
4
|
import { types as nodeTypes } from "node:util";
|
|
4
5
|
import { CompileScope, RegexCompileGuard, regexCompiledData } from "./regex/compile-guard.js";
|
|
@@ -225,6 +226,10 @@ export function measureSandboxData(values, options = {}) {
|
|
|
225
226
|
assertSandboxDataDepth(depth);
|
|
226
227
|
seen.add(value);
|
|
227
228
|
usage += 1;
|
|
229
|
+
if (isSandboxDate(value)) {
|
|
230
|
+
usage += 8;
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
228
233
|
if (isGuestHostObject(value)) {
|
|
229
234
|
for (const key of getHostObjectKeys(value))
|
|
230
235
|
usage += key.length + 1;
|
|
@@ -435,6 +440,14 @@ function copyToSandbox(value, state, path = "<root>", cloneSandboxCollections =
|
|
|
435
440
|
}
|
|
436
441
|
return sandboxPromise;
|
|
437
442
|
}
|
|
443
|
+
if (nodeTypes.isDate(value)) {
|
|
444
|
+
const existing = state.seen.get(value);
|
|
445
|
+
if (existing !== undefined)
|
|
446
|
+
return existing;
|
|
447
|
+
const copy = copyNativeDate(value);
|
|
448
|
+
state.seen.set(value, copy);
|
|
449
|
+
return copy;
|
|
450
|
+
}
|
|
438
451
|
if (isFloat32Array(value)) {
|
|
439
452
|
const existing = state.seen.get(value);
|
|
440
453
|
if (existing !== undefined)
|
|
@@ -567,6 +580,14 @@ function copyFromSandbox(value, state, path = "<root>", options, depth = 0) {
|
|
|
567
580
|
Object.preventExtensions(copy);
|
|
568
581
|
return copy;
|
|
569
582
|
}
|
|
583
|
+
if (isSandboxDate(value)) {
|
|
584
|
+
const existing = state.seen.get(value);
|
|
585
|
+
if (existing !== undefined)
|
|
586
|
+
return existing;
|
|
587
|
+
const copy = exportDate(value);
|
|
588
|
+
state.seen.set(value, copy);
|
|
589
|
+
return copy;
|
|
590
|
+
}
|
|
570
591
|
if (isSandboxClosure(value)) {
|
|
571
592
|
if (options.wrapClosure === undefined) {
|
|
572
593
|
throw new TypeError("Sandbox closures cannot cross into host values without an explicit wrapper.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const KNOWN_RUNTIME_GLOBALS: readonly ["AggregateError", "Array", "Boolean", "Error", "Infinity", "isFinite", "isNaN", "JSON", "Map", "Float32Array", "Math", "NaN", "Number", "Object", "parseFloat", "parseInt", "Promise", "RangeError", "ReferenceError", "RegExp", "Set", "String", "structuredClone", "SyntaxError", "TypeError", "console"];
|
|
1
|
+
export declare const KNOWN_RUNTIME_GLOBALS: readonly ["AggregateError", "Array", "Boolean", "Date", "Error", "Infinity", "isFinite", "isNaN", "JSON", "Map", "Float32Array", "Math", "NaN", "Number", "Object", "parseFloat", "parseInt", "Promise", "RangeError", "ReferenceError", "RegExp", "Set", "String", "structuredClone", "SyntaxError", "TypeError", "console"];
|
|
@@ -1832,8 +1832,10 @@ class Parser {
|
|
|
1832
1832
|
}
|
|
1833
1833
|
break;
|
|
1834
1834
|
}
|
|
1835
|
-
this.
|
|
1836
|
-
|
|
1835
|
+
const optional = this.consumePunctuator("?.");
|
|
1836
|
+
if (optional !== undefined)
|
|
1837
|
+
throw new DisallowedSyntaxError("new optional chain", optional.start);
|
|
1838
|
+
const args = this.consumePunctuator("(") === undefined ? [] : this.parseArguments();
|
|
1837
1839
|
const end = this.previousToken();
|
|
1838
1840
|
return {
|
|
1839
1841
|
node: {
|
|
@@ -4,15 +4,17 @@ import { type CallerInjectedBinding } from "./interp/host-bridge.js";
|
|
|
4
4
|
import { type CallbackOptions, type SafeJSExtension } from "./extensions.js";
|
|
5
5
|
import { type ModuleRegistry } from "./modules/registry.js";
|
|
6
6
|
import type { ConsoleSink } from "./interp/globals/console-json.js";
|
|
7
|
-
import type { RunOptions, RunResult } from "./run.js";
|
|
7
|
+
import type { RunClock, RunOptions, RunResult } from "./run.js";
|
|
8
8
|
export type RealmLimits = {
|
|
9
9
|
extensions?: number;
|
|
10
10
|
hostObjects?: number;
|
|
11
11
|
callbacks?: number;
|
|
12
|
+
guestReferences?: number;
|
|
12
13
|
cleanups?: number;
|
|
13
14
|
nestedEvaluations?: number;
|
|
14
15
|
};
|
|
15
16
|
export type RealmOptions = {
|
|
17
|
+
clock?: RunClock;
|
|
16
18
|
bindings?: Record<string, CallerInjectedBinding>;
|
|
17
19
|
modules?: ModuleRegistry;
|
|
18
20
|
extensions?: readonly SafeJSExtension[];
|
|
@@ -37,6 +39,7 @@ export type SafeJSRealm = {
|
|
|
37
39
|
}): Promise<RealmResult>;
|
|
38
40
|
invokeCallback(callback: unknown, options?: CallbackOptions): Promise<unknown>;
|
|
39
41
|
releaseCallback(callback: unknown): void;
|
|
42
|
+
releaseGuestReference(reference: unknown): void;
|
|
40
43
|
close(): Promise<void>;
|
|
41
44
|
};
|
|
42
45
|
export declare function createRealm(options?: RealmOptions): SafeJSRealm;
|
|
@@ -10,7 +10,7 @@ import { enterRunningState } from "./interp/running-state.js";
|
|
|
10
10
|
import { runResources } from "./interp/resources.js";
|
|
11
11
|
import { createSandboxPromiseRejectionTracker, withSandboxPromiseRejectionTracker } from "./interp/promise-tracker.js";
|
|
12
12
|
import { copyHostValueToSandbox, wrapCallerInjectedBindings } from "./interp/host-bridge.js";
|
|
13
|
-
import { createLiveHostObject, exportHostCapability, readGuestCallback, registerGuestCallback, revokeGuestCallback, revokeHostObject } from "./interp/host-capabilities.js";
|
|
13
|
+
import { createLiveHostObject, createGuestReference, exportHostCapability, readGuestReference, readGuestCallback, registerGuestCallback, revokeGuestCallback, revokeHostObject, revokeGuestReference } from "./interp/host-capabilities.js";
|
|
14
14
|
import { deepCopyFromSandbox, isSandboxClosure, measureSandboxData, reconcileCompiledValues } from "./interp/values.js";
|
|
15
15
|
import { getExtensionSetup, readDataRecord, readStringList } from "./extensions.js";
|
|
16
16
|
import { resolveModuleImports } from "./modules/registry.js";
|
|
@@ -36,6 +36,8 @@ class RealmState {
|
|
|
36
36
|
pendingCallbacks = new Set();
|
|
37
37
|
callbackCache = new WeakMap();
|
|
38
38
|
hostObjects = new Set();
|
|
39
|
+
guestReferences = new Map();
|
|
40
|
+
retainedOperations = new WeakMap();
|
|
39
41
|
nestedOperations = new WeakMap();
|
|
40
42
|
convertedModules = new Map();
|
|
41
43
|
nativeConversions = { seen: new WeakMap() };
|
|
@@ -56,6 +58,7 @@ class RealmState {
|
|
|
56
58
|
extensions: 32,
|
|
57
59
|
hostObjects: 1024,
|
|
58
60
|
callbacks: 1024,
|
|
61
|
+
guestReferences: 1024,
|
|
59
62
|
cleanups: 1024,
|
|
60
63
|
nestedEvaluations: 16
|
|
61
64
|
};
|
|
@@ -94,6 +97,7 @@ class RealmState {
|
|
|
94
97
|
owner: this,
|
|
95
98
|
assertActive: this.assertOpen,
|
|
96
99
|
wrapCallback: this.wrapCallback,
|
|
100
|
+
captureArguments: this.captureArguments,
|
|
97
101
|
invoke: this.invokeHost,
|
|
98
102
|
awaitResult: (operation) => this.nestedOperations.has(operation)
|
|
99
103
|
};
|
|
@@ -102,6 +106,7 @@ class RealmState {
|
|
|
102
106
|
budget: this.budget,
|
|
103
107
|
compileOwner: this.lease.owner,
|
|
104
108
|
sink: options.sink,
|
|
109
|
+
clock: options.clock,
|
|
105
110
|
random: createReplayableRandom({ seed: options.randomSeed }).next
|
|
106
111
|
});
|
|
107
112
|
const names = new Set();
|
|
@@ -134,7 +139,7 @@ class RealmState {
|
|
|
134
139
|
options.signal?.addEventListener("abort", this.abort, { once: true });
|
|
135
140
|
if (options.signal?.aborted)
|
|
136
141
|
this.abort();
|
|
137
|
-
this.budget.setRetainedValues(this, this.
|
|
142
|
+
this.budget.setRetainedValues(this, this.retainedRoots);
|
|
138
143
|
this.tracker.onFatalRejection((error) => this.poison(error));
|
|
139
144
|
}
|
|
140
145
|
catch (error) {
|
|
@@ -149,10 +154,48 @@ class RealmState {
|
|
|
149
154
|
signal: this.controller.signal,
|
|
150
155
|
realm: this.bridge
|
|
151
156
|
});
|
|
152
|
-
|
|
157
|
+
retainedRoots = () => [
|
|
153
158
|
...this.callbacks.values(),
|
|
154
|
-
...Array.from(this.pendingCallbacks, (pending) => pending.closure)
|
|
159
|
+
...Array.from(this.pendingCallbacks, (pending) => pending.closure),
|
|
160
|
+
...this.guestReferences.values()
|
|
155
161
|
];
|
|
162
|
+
captureArguments = (operation, args, copy) => {
|
|
163
|
+
const from = this.retainedOperations.get(operation)?.from ?? args.length;
|
|
164
|
+
const values = copy(args.slice(0, from));
|
|
165
|
+
const captured = [];
|
|
166
|
+
const rollback = () => {
|
|
167
|
+
for (const reference of captured) {
|
|
168
|
+
revokeGuestReference(reference, this);
|
|
169
|
+
this.guestReferences.delete(reference);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
for (const value of args.slice(from)) {
|
|
174
|
+
this.checkCollection(this.guestReferences.size + 1, this.limits.guestReferences, "guest reference");
|
|
175
|
+
const root = [value];
|
|
176
|
+
const reference = createGuestReference(root, this, this.assertOpen);
|
|
177
|
+
this.guestReferences.set(reference, root);
|
|
178
|
+
captured.push(reference);
|
|
179
|
+
values.push(reference);
|
|
180
|
+
}
|
|
181
|
+
if (captured.length > 0)
|
|
182
|
+
this.budget.reconcileDataUsage(measureSandboxData([...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()]));
|
|
183
|
+
return { args: values, rollback };
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
rollback();
|
|
187
|
+
if (error instanceof SandboxError)
|
|
188
|
+
this.poison(error);
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
releaseGuestReference = (reference) => {
|
|
193
|
+
readGuestReference(reference, this);
|
|
194
|
+
revokeGuestReference(reference, this);
|
|
195
|
+
this.guestReferences.delete(reference);
|
|
196
|
+
if (this.active === undefined)
|
|
197
|
+
reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()], this.compilation);
|
|
198
|
+
};
|
|
156
199
|
assertOpen = () => {
|
|
157
200
|
if (this.failure !== undefined)
|
|
158
201
|
throw this.failure.reason;
|
|
@@ -321,7 +364,7 @@ class RealmState {
|
|
|
321
364
|
}
|
|
322
365
|
});
|
|
323
366
|
try {
|
|
324
|
-
this.budget.reconcileDataUsage(measureSandboxData([...(this.scope?.retainedValues() ?? []), ...this.
|
|
367
|
+
this.budget.reconcileDataUsage(measureSandboxData([...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()]));
|
|
325
368
|
}
|
|
326
369
|
catch (error) {
|
|
327
370
|
this.poison(error);
|
|
@@ -334,7 +377,7 @@ class RealmState {
|
|
|
334
377
|
revokeGuestCallback(callback, this);
|
|
335
378
|
this.callbacks.delete(callback);
|
|
336
379
|
if (this.active === undefined)
|
|
337
|
-
reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.
|
|
380
|
+
reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()], this.compilation);
|
|
338
381
|
};
|
|
339
382
|
invokeCallback = async (callback, options = {}) => {
|
|
340
383
|
if (this.closed || this.failure !== undefined)
|
|
@@ -384,7 +427,7 @@ class RealmState {
|
|
|
384
427
|
finally {
|
|
385
428
|
this.pendingCallbacks.delete(record);
|
|
386
429
|
if (!this.closed && this.active === undefined)
|
|
387
|
-
reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.
|
|
430
|
+
reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()], this.compilation);
|
|
388
431
|
}
|
|
389
432
|
};
|
|
390
433
|
initialize() {
|
|
@@ -400,6 +443,24 @@ class RealmState {
|
|
|
400
443
|
createHostObject: this.createHostObject,
|
|
401
444
|
invokeCallback: this.invokeCallback,
|
|
402
445
|
releaseCallback: this.releaseCallback,
|
|
446
|
+
releaseGuestReference: this.releaseGuestReference,
|
|
447
|
+
retainGuestArguments: (operation, from) => {
|
|
448
|
+
this.assertOpen();
|
|
449
|
+
if (!extension.manifest.capabilities?.includes("guest:retain"))
|
|
450
|
+
throw new TypeError("Retaining arguments requires the guest:retain grant.");
|
|
451
|
+
if (typeof operation !== "function")
|
|
452
|
+
throw new TypeError("Retained operation must be a function.");
|
|
453
|
+
if (!Number.isSafeInteger(from) || from < 0)
|
|
454
|
+
throw new TypeError("Argument index must be a non-negative safe integer.");
|
|
455
|
+
if (this.scope !== undefined)
|
|
456
|
+
throw new TypeError("Retained operations must be registered during setup.");
|
|
457
|
+
const previous = this.retainedOperations.get(operation);
|
|
458
|
+
if (previous !== undefined &&
|
|
459
|
+
(previous.extension !== extension || previous.from !== from))
|
|
460
|
+
throw new TypeError("Conflicting retained operation declaration.");
|
|
461
|
+
this.retainedOperations.set(operation, { extension, from });
|
|
462
|
+
return operation;
|
|
463
|
+
},
|
|
403
464
|
nestedOperation: (operation) => {
|
|
404
465
|
this.assertOpen();
|
|
405
466
|
if (!extension.manifest.capabilities?.includes("source:nested"))
|
|
@@ -552,7 +613,7 @@ class RealmState {
|
|
|
552
613
|
if (this.failure !== undefined)
|
|
553
614
|
throw this.failure.reason;
|
|
554
615
|
if (!this.closed)
|
|
555
|
-
reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.
|
|
616
|
+
reconcileCompiledValues(this.budget, [...(this.scope?.retainedValues() ?? []), ...this.retainedRoots()], this.compilation);
|
|
556
617
|
return result;
|
|
557
618
|
}
|
|
558
619
|
catch (error) {
|
|
@@ -591,6 +652,9 @@ class RealmState {
|
|
|
591
652
|
for (const object of this.hostObjects)
|
|
592
653
|
revokeHostObject(object, this);
|
|
593
654
|
this.hostObjects.clear();
|
|
655
|
+
for (const reference of this.guestReferences.keys())
|
|
656
|
+
revokeGuestReference(reference, this);
|
|
657
|
+
this.guestReferences.clear();
|
|
594
658
|
this.budget.setRetainedValues(this, undefined);
|
|
595
659
|
this.disposal = (async () => {
|
|
596
660
|
const errors = [];
|
|
@@ -651,6 +715,7 @@ export function createRealm(options = {}) {
|
|
|
651
715
|
evaluate: state.evaluate,
|
|
652
716
|
invokeCallback: state.invokeCallback,
|
|
653
717
|
releaseCallback: state.releaseCallback,
|
|
718
|
+
releaseGuestReference: state.releaseGuestReference,
|
|
654
719
|
close: state.close
|
|
655
720
|
});
|
|
656
721
|
}
|
|
@@ -702,6 +767,7 @@ function readRealmOptions(value, oneShot = false) {
|
|
|
702
767
|
"signal",
|
|
703
768
|
"sink",
|
|
704
769
|
"randomSeed",
|
|
770
|
+
"clock",
|
|
705
771
|
"limits"
|
|
706
772
|
]);
|
|
707
773
|
for (const [key, entry] of Object.entries(options)) {
|
|
@@ -109,7 +109,7 @@ export function run(source, options = {}) {
|
|
|
109
109
|
signal: options.signal,
|
|
110
110
|
lifecycle
|
|
111
111
|
}));
|
|
112
|
-
const builtinBindings = createBuiltinBindings({ compileOwner: operation.owner, budget, hostCalls, sink: options.sink, random: random?.generator.next });
|
|
112
|
+
const builtinBindings = createBuiltinBindings({ compileOwner: operation.owner, budget, hostCalls, sink: options.sink, random: random?.generator.next, clock: options.clock });
|
|
113
113
|
const importMeta = convertInitialInput(() => deepCopyToSandbox(options.importMeta ?? {}));
|
|
114
114
|
if (restoredSnapshot?.migration !== undefined) {
|
|
115
115
|
if (Object.hasOwn(importMeta, "migration"))
|
|
@@ -8,6 +8,7 @@ import { serializeArguments } from "./arguments.js";
|
|
|
8
8
|
import { requiresArrayEntries, serializeArray } from "./arrays.js";
|
|
9
9
|
import { float32DataProperties, isFloat32Array } from "../interp/float32.js";
|
|
10
10
|
import { encodeFloat32Storage } from "./float32array.js";
|
|
11
|
+
import { isSandboxDate, serializedDateTime } from "../interp/date.js";
|
|
11
12
|
const SKIP_VALUE = Symbol("SafeJS.skip-dump-value");
|
|
12
13
|
export function serializeSafeJSSnapshot(snapshot) {
|
|
13
14
|
const replayError = Object.getOwnPropertyDescriptor(snapshot, "replayError");
|
|
@@ -72,7 +73,7 @@ function serializeDumpValue(value, path, state) {
|
|
|
72
73
|
if (hasGuestObjectState(value)) {
|
|
73
74
|
throw new TypeError("Guest function properties and prototype links cannot be serialized.");
|
|
74
75
|
}
|
|
75
|
-
if (isFloat32Array(value))
|
|
76
|
+
if (isSandboxDate(value) || isFloat32Array(value))
|
|
76
77
|
return serializeHeapReference(value, path, state);
|
|
77
78
|
if (Array.isArray(value)) {
|
|
78
79
|
const reference = serializeHeapReference(value, path, state);
|
|
@@ -97,7 +98,10 @@ function serializeHeapReference(value, path, state) {
|
|
|
97
98
|
}
|
|
98
99
|
if (!state.serializedHeapIds.has(id)) {
|
|
99
100
|
state.serializedHeapIds.add(id);
|
|
100
|
-
if (
|
|
101
|
+
if (isSandboxDate(value)) {
|
|
102
|
+
state.heap[String(id)] = { kind: "date", time: serializedDateTime(value) };
|
|
103
|
+
}
|
|
104
|
+
else if (isFloat32Array(value)) {
|
|
101
105
|
const storage = encodeFloat32Storage(value, id, state.float32Buffers, (id) => ({
|
|
102
106
|
kind: "ref",
|
|
103
107
|
id
|
|
@@ -159,6 +163,7 @@ function indexHeapContainers(snapshot) {
|
|
|
159
163
|
for (const [value, stat] of stats.entries()) {
|
|
160
164
|
if (stat.count > 1 ||
|
|
161
165
|
stat.cyclic ||
|
|
166
|
+
isSandboxDate(value) ||
|
|
162
167
|
isFloat32Array(value) ||
|
|
163
168
|
(Array.isArray(value) && requiresArrayEntries(value)) ||
|
|
164
169
|
isSandboxArguments(value) ||
|
|
@@ -173,7 +178,7 @@ function collectContainerStats(value, stats, ancestors) {
|
|
|
173
178
|
if (value === null || typeof value !== "object") {
|
|
174
179
|
return;
|
|
175
180
|
}
|
|
176
|
-
if (!Array.isArray(value) && !isPlainObject(value) && !isFloat32Array(value)) {
|
|
181
|
+
if (!Array.isArray(value) && !isPlainObject(value) && !isFloat32Array(value) && !isSandboxDate(value)) {
|
|
177
182
|
return;
|
|
178
183
|
}
|
|
179
184
|
let stat = stats.get(value);
|
|
@@ -24,7 +24,10 @@ type Properties = Record<string, {
|
|
|
24
24
|
enumerable: boolean;
|
|
25
25
|
writable: boolean;
|
|
26
26
|
}>;
|
|
27
|
-
type DataNode =
|
|
27
|
+
type DataNode = {
|
|
28
|
+
kind: "date";
|
|
29
|
+
time: number | null;
|
|
30
|
+
} | (Float32Data<Atom> & {
|
|
28
31
|
properties: Properties;
|
|
29
32
|
extensible: boolean;
|
|
30
33
|
}) | {
|
|
@@ -3,6 +3,7 @@ import { hasGuestObjectState } from "../interp/object-model.js";
|
|
|
3
3
|
import { CompileScope } from "../interp/regex/compile-guard.js";
|
|
4
4
|
import { float32DataProperties, isFloat32Array } from "../interp/float32.js";
|
|
5
5
|
import { decodeFloat32Storage, encodeFloat32Storage } from "./float32array.js";
|
|
6
|
+
import { isSandboxDate, restoreDateTime, serializedDateTime } from "../interp/date.js";
|
|
6
7
|
import { sandboxErrorNames, sandboxErrorTypes } from "../error/shape.js";
|
|
7
8
|
import { createSandboxArguments, createSandboxClosure, createSandboxMap, createSandboxRegex, createSandboxSet, isSandboxArguments, isSandboxClosure, isSandboxGenerator, isSandboxMap, isSandboxPromise, isSandboxRegex, isSandboxSet } from "../interp/values.js";
|
|
8
9
|
import { serializeArguments } from "./arguments.js";
|
|
@@ -63,6 +64,9 @@ export function encodeReplayData(value, options = {}) {
|
|
|
63
64
|
properties: child(entry.properties, "properties")
|
|
64
65
|
};
|
|
65
66
|
}
|
|
67
|
+
else if (isSandboxDate(entry)) {
|
|
68
|
+
nodes[id] = { kind: "date", time: serializedDateTime(entry) };
|
|
69
|
+
}
|
|
66
70
|
else if (isFloat32Array(entry)) {
|
|
67
71
|
const storage = encodeFloat32Storage(entry, id, float32Buffers, (id) => ({
|
|
68
72
|
tag: "ref",
|
|
@@ -231,6 +235,13 @@ export function decodeReplayData(input, options = {}, parent) {
|
|
|
231
235
|
options.onCapabilityRestored?.(capability, copy);
|
|
232
236
|
return copy;
|
|
233
237
|
}
|
|
238
|
+
if (kind === "date") {
|
|
239
|
+
if (Object.keys(node).length !== 2)
|
|
240
|
+
throw new TypeError("Invalid serialized Date fields.");
|
|
241
|
+
const result = restoreDateTime(own(node, "time"));
|
|
242
|
+
restored.set(id, result);
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
234
245
|
if (kind === "float32array") {
|
|
235
246
|
if (typeof node.extensible !== "boolean")
|
|
236
247
|
throw new TypeError("Invalid Float32Array extensibility.");
|