poe-code 14.0.6 → 14.0.8
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 +944 -143
- 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-MO3YD4ID.js} +888 -133
- package/packages/safe-js/dist/chunks/chunk-MO3YD4ID.js.map +7 -0
- package/packages/safe-js/dist/chunks/{chunk-QSSQJNKE.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/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/async.js +1 -0
- 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 +4 -3
- package/packages/safe-js/dist/interp/globals/object-array.js +42 -5
- 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 +4 -1
- 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 +28 -1
- package/packages/safe-js/dist/interp/host-capabilities.js +220 -5
- package/packages/safe-js/dist/interp/interpreter.js +46 -11
- 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/string-coercion.js +3 -0
- package/packages/safe-js/dist/interp/values.d.ts +3 -1
- package/packages/safe-js/dist/interp/values.js +34 -6
- 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 +89 -10
- package/packages/safe-js/dist/run.d.ts +2 -0
- package/packages/safe-js/dist/run.js +3 -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-RXLDJ6LQ.js.map} +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { assertSandboxDataDepth } from "../../graph-depth.js";
|
|
2
|
+
import { isSandboxDate } from "../date.js";
|
|
3
|
+
import { isSandboxErrorConstructorInstance } from "../exceptions.js";
|
|
4
|
+
import { isFloat32Array } from "../float32.js";
|
|
5
|
+
import { hasHostObjectMember, isGuestHostObject } from "../host-capabilities.js";
|
|
6
|
+
import { getSandboxPrototype, installObjectPrototype, isGuestClosure, markDescriptorObject, materializeFunctionProperties } from "../object-model.js";
|
|
7
|
+
import { sandboxString } from "../string-coercion.js";
|
|
8
|
+
import { createSandboxClosure, isSandboxClosure, isSandboxGenerator, isSandboxMap, isSandboxPromise, isSandboxRegex, isSandboxSet } from "../values.js";
|
|
9
|
+
export function createObjectGlobal(methods, budget) {
|
|
10
|
+
const construct = ([value]) => {
|
|
11
|
+
if (value === null || value === undefined) {
|
|
12
|
+
budget.chargeDataUsage(1);
|
|
13
|
+
return Object.create(null);
|
|
14
|
+
}
|
|
15
|
+
if (typeof value !== "object")
|
|
16
|
+
throw new TypeError("Object primitive boxing is not supported.");
|
|
17
|
+
return value;
|
|
18
|
+
};
|
|
19
|
+
const constructor = createSandboxClosure({
|
|
20
|
+
guest: true,
|
|
21
|
+
sandbox: true,
|
|
22
|
+
name: "Object",
|
|
23
|
+
length: 1,
|
|
24
|
+
call: construct,
|
|
25
|
+
construct
|
|
26
|
+
});
|
|
27
|
+
const properties = materializeFunctionProperties(constructor);
|
|
28
|
+
const prototype = properties.prototype;
|
|
29
|
+
Object.defineProperty(properties, "prototype", { writable: false });
|
|
30
|
+
for (const [name, method] of Object.entries(methods)) {
|
|
31
|
+
Object.defineProperty(properties, name, { value: method, writable: true, configurable: true });
|
|
32
|
+
}
|
|
33
|
+
const prototypeMethods = {
|
|
34
|
+
toString: createSandboxClosure({
|
|
35
|
+
sandbox: true,
|
|
36
|
+
name: "toString",
|
|
37
|
+
length: 0,
|
|
38
|
+
call: (_args, context) => budget.allocateString(`[object ${typeTag(context?.thisValue)}]`)
|
|
39
|
+
}),
|
|
40
|
+
valueOf: createSandboxClosure({
|
|
41
|
+
sandbox: true,
|
|
42
|
+
name: "valueOf",
|
|
43
|
+
length: 0,
|
|
44
|
+
call: (_args, context) => {
|
|
45
|
+
const value = requireReceiver(context?.thisValue);
|
|
46
|
+
if (typeof value !== "object")
|
|
47
|
+
throw new TypeError("Object primitive boxing is not supported.");
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
}),
|
|
51
|
+
hasOwnProperty: createSandboxClosure({
|
|
52
|
+
sandbox: true,
|
|
53
|
+
name: "hasOwnProperty",
|
|
54
|
+
length: 1,
|
|
55
|
+
call: async ([key], context) => hasOwnSandboxProperty(requireReceiver(context?.thisValue), await sandboxString(key, budget, context), false)
|
|
56
|
+
}),
|
|
57
|
+
propertyIsEnumerable: createSandboxClosure({
|
|
58
|
+
sandbox: true,
|
|
59
|
+
name: "propertyIsEnumerable",
|
|
60
|
+
length: 1,
|
|
61
|
+
call: async ([key], context) => hasOwnSandboxProperty(requireReceiver(context?.thisValue), await sandboxString(key, budget, context), true)
|
|
62
|
+
}),
|
|
63
|
+
isPrototypeOf: createSandboxClosure({
|
|
64
|
+
sandbox: true,
|
|
65
|
+
name: "isPrototypeOf",
|
|
66
|
+
length: 1,
|
|
67
|
+
call: ([value], context) => {
|
|
68
|
+
if (typeof value !== "object" || value === null)
|
|
69
|
+
return false;
|
|
70
|
+
const receiver = requireReceiver(context?.thisValue);
|
|
71
|
+
let depth = 0;
|
|
72
|
+
for (let current = getSandboxPrototype(value, budget); current !== null; current = getSandboxPrototype(current, budget)) {
|
|
73
|
+
budget.visitNode();
|
|
74
|
+
assertSandboxDataDepth(depth++);
|
|
75
|
+
if (current === receiver)
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
};
|
|
82
|
+
for (const [name, method] of Object.entries(prototypeMethods)) {
|
|
83
|
+
Object.defineProperty(prototype, name, { value: method, writable: true, configurable: true });
|
|
84
|
+
}
|
|
85
|
+
markDescriptorObject(prototype);
|
|
86
|
+
installObjectPrototype(budget, prototype, constructor);
|
|
87
|
+
return constructor;
|
|
88
|
+
}
|
|
89
|
+
function requireReceiver(value) {
|
|
90
|
+
if (value === null || value === undefined)
|
|
91
|
+
throw new TypeError("Object method requires a non-null receiver.");
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
export function hasOwnSandboxProperty(value, key, enumerable) {
|
|
95
|
+
requireReceiver(value);
|
|
96
|
+
if (isGuestHostObject(value))
|
|
97
|
+
return hasHostObjectMember(value, key, enumerable);
|
|
98
|
+
let properties;
|
|
99
|
+
if (isGuestClosure(value))
|
|
100
|
+
properties = materializeFunctionProperties(value);
|
|
101
|
+
else if (isSandboxClosure(value)) {
|
|
102
|
+
if (key === "length" || key === "name")
|
|
103
|
+
return !enumerable;
|
|
104
|
+
properties = value.properties ?? Object.create(null);
|
|
105
|
+
}
|
|
106
|
+
else if (isSandboxMap(value) ||
|
|
107
|
+
isSandboxSet(value) ||
|
|
108
|
+
isSandboxPromise(value) ||
|
|
109
|
+
isSandboxGenerator(value))
|
|
110
|
+
return false;
|
|
111
|
+
else if (isSandboxRegex(value))
|
|
112
|
+
return key === "lastIndex" && !enumerable;
|
|
113
|
+
else
|
|
114
|
+
properties = Object(value);
|
|
115
|
+
const descriptor = Object.getOwnPropertyDescriptor(properties, key);
|
|
116
|
+
return descriptor !== undefined && (!enumerable || descriptor.enumerable === true);
|
|
117
|
+
}
|
|
118
|
+
function typeTag(value) {
|
|
119
|
+
if (value === undefined)
|
|
120
|
+
return "Undefined";
|
|
121
|
+
if (value === null)
|
|
122
|
+
return "Null";
|
|
123
|
+
if (typeof value === "string")
|
|
124
|
+
return "String";
|
|
125
|
+
if (typeof value === "number")
|
|
126
|
+
return "Number";
|
|
127
|
+
if (typeof value === "boolean")
|
|
128
|
+
return "Boolean";
|
|
129
|
+
if (isSandboxClosure(value)) {
|
|
130
|
+
while (value.boundTarget !== undefined)
|
|
131
|
+
value = value.boundTarget;
|
|
132
|
+
return value.generator ? "GeneratorFunction" : value.async ? "AsyncFunction" : "Function";
|
|
133
|
+
}
|
|
134
|
+
if (Array.isArray(value))
|
|
135
|
+
return "Array";
|
|
136
|
+
if (isSandboxDate(value))
|
|
137
|
+
return "Date";
|
|
138
|
+
if (isSandboxErrorConstructorInstance(value, "Error"))
|
|
139
|
+
return "Error";
|
|
140
|
+
if (isSandboxRegex(value))
|
|
141
|
+
return "RegExp";
|
|
142
|
+
if (isSandboxMap(value))
|
|
143
|
+
return "Map";
|
|
144
|
+
if (isSandboxSet(value))
|
|
145
|
+
return "Set";
|
|
146
|
+
if (isSandboxPromise(value))
|
|
147
|
+
return "Promise";
|
|
148
|
+
if (isSandboxGenerator(value))
|
|
149
|
+
return "Generator";
|
|
150
|
+
if (isFloat32Array(value))
|
|
151
|
+
return "Float32Array";
|
|
152
|
+
return "Object";
|
|
153
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createConsoleJsonGlobals } from "./globals/console-json.js";
|
|
2
|
+
import type { RunClock } from "../run.js";
|
|
2
3
|
export declare function createBuiltinBindings(options: Parameters<typeof createConsoleJsonGlobals>[0] & {
|
|
3
4
|
random?: () => number;
|
|
5
|
+
clock?: RunClock;
|
|
4
6
|
}): {
|
|
5
7
|
RegExp: import("./values.js").SandboxClosure;
|
|
6
8
|
Promise: import("./values.js").SandboxClosure;
|
|
@@ -9,7 +11,7 @@ export declare function createBuiltinBindings(options: Parameters<typeof createC
|
|
|
9
11
|
parseFloat: import("./values.js").SandboxClosure;
|
|
10
12
|
isNaN: import("./values.js").SandboxClosure;
|
|
11
13
|
isFinite: import("./values.js").SandboxClosure;
|
|
12
|
-
Object: import("./values.js").
|
|
14
|
+
Object: import("./values.js").SandboxClosure;
|
|
13
15
|
Array: import("./values.js").SandboxClosure;
|
|
14
16
|
String: import("./values.js").SandboxClosure;
|
|
15
17
|
Number: import("./values.js").SandboxClosure;
|
|
@@ -24,6 +26,7 @@ export declare function createBuiltinBindings(options: Parameters<typeof createC
|
|
|
24
26
|
SyntaxError: import("./values.js").SandboxClosure;
|
|
25
27
|
AggregateError: import("./values.js").SandboxClosure;
|
|
26
28
|
Float32Array: import("./values.js").SandboxClosure;
|
|
29
|
+
Date: import("./values.js").SandboxClosure;
|
|
27
30
|
Map: import("./values.js").SandboxClosure;
|
|
28
31
|
Set: import("./values.js").SandboxClosure;
|
|
29
32
|
JSON: import("./values.js").SandboxObject;
|
|
@@ -7,11 +7,13 @@ import { createRegexGlobals } from "./globals/regex.js";
|
|
|
7
7
|
import { createMiscGlobals } from "./globals/misc.js";
|
|
8
8
|
import { createObjectArrayGlobals } from "./globals/object-array.js";
|
|
9
9
|
import { createPromiseGlobals } from "./promise.js";
|
|
10
|
+
import { createDateGlobal } from "./globals/date.js";
|
|
10
11
|
export function createBuiltinBindings(options) {
|
|
11
12
|
return {
|
|
12
13
|
...createConsoleJsonGlobals(options),
|
|
13
14
|
...createCollectionGlobals(options),
|
|
14
15
|
Float32Array: createFloat32ArrayGlobal(options.budget),
|
|
16
|
+
Date: createDateGlobal(options),
|
|
15
17
|
...createErrorGlobals(options),
|
|
16
18
|
...createMathGlobals({ random: options.random }),
|
|
17
19
|
...createObjectArrayGlobals(options),
|
|
@@ -12,6 +12,10 @@ export type RealmBridge = {
|
|
|
12
12
|
wrapCallback(closure: SandboxClosure): (...args: readonly unknown[]) => Promise<unknown>;
|
|
13
13
|
invoke(operation: CallerInjectedFunction, call: () => unknown): unknown;
|
|
14
14
|
awaitResult(operation: CallerInjectedFunction): boolean;
|
|
15
|
+
captureArguments(operation: CallerInjectedFunction, args: readonly SandboxValue[], copy: (values: readonly SandboxValue[]) => unknown[]): {
|
|
16
|
+
args: unknown[];
|
|
17
|
+
rollback(): void;
|
|
18
|
+
};
|
|
15
19
|
};
|
|
16
20
|
export type HostBridgeOptions = {
|
|
17
21
|
realm?: RealmBridge;
|
|
@@ -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,8 @@
|
|
|
1
1
|
import { type HostOperation } from "../extensions.js";
|
|
2
2
|
import type { SandboxClosure, SandboxObject, SandboxValue } from "./values.js";
|
|
3
|
+
import type { SandboxIterator } from "./iteration.js";
|
|
3
4
|
declare const hostObjectBrand: unique symbol;
|
|
5
|
+
declare const guestReferenceBrand: unique symbol;
|
|
4
6
|
declare const sandboxHostObjectBrand: unique symbol;
|
|
5
7
|
type SandboxHostObject = SandboxObject & {
|
|
6
8
|
readonly [sandboxHostObjectBrand]: true;
|
|
@@ -8,18 +10,37 @@ type SandboxHostObject = SandboxObject & {
|
|
|
8
10
|
export type HostObject = Readonly<Record<string, never>> & {
|
|
9
11
|
readonly [hostObjectBrand]: true;
|
|
10
12
|
};
|
|
13
|
+
export type GuestReference = Readonly<Record<string, never>> & {
|
|
14
|
+
readonly [guestReferenceBrand]: true;
|
|
15
|
+
};
|
|
16
|
+
export type HostObjectIndexedDefinition = {
|
|
17
|
+
length(): number;
|
|
18
|
+
get(index: number): unknown;
|
|
19
|
+
maxLength: number;
|
|
20
|
+
};
|
|
11
21
|
export type HostObjectDefinition = {
|
|
22
|
+
indexed?: HostObjectIndexedDefinition;
|
|
23
|
+
named?: HostObjectNamedDefinition;
|
|
12
24
|
properties?: Record<string, {
|
|
13
25
|
get?: () => unknown;
|
|
14
26
|
set?: (value: unknown) => void;
|
|
15
27
|
}>;
|
|
16
28
|
methods?: Record<string, HostOperation>;
|
|
17
29
|
};
|
|
30
|
+
export type HostObjectNamedDefinition = {
|
|
31
|
+
keys(): readonly string[];
|
|
32
|
+
get(name: string): unknown;
|
|
33
|
+
maxKeys: number;
|
|
34
|
+
maxKeyCodeUnits: number;
|
|
35
|
+
enumerable?: boolean;
|
|
36
|
+
};
|
|
18
37
|
export type HostObjectController = {
|
|
19
38
|
owner: object;
|
|
20
39
|
assertActive(): void;
|
|
21
40
|
chargeWork(units?: number): void;
|
|
22
|
-
|
|
41
|
+
checkLength(length: number): void;
|
|
42
|
+
checkTemporaryDataSize(size: number): void;
|
|
43
|
+
read(operation: () => unknown, validate?: (value: unknown) => unknown): SandboxValue;
|
|
23
44
|
write(operation: (value: unknown) => void, value: SandboxValue): void;
|
|
24
45
|
method(operation: HostOperation): SandboxClosure;
|
|
25
46
|
};
|
|
@@ -28,6 +49,9 @@ type GuestCallbackState = {
|
|
|
28
49
|
closure?: SandboxClosure;
|
|
29
50
|
assertActive(): void;
|
|
30
51
|
};
|
|
52
|
+
export declare function createGuestReference(root: [SandboxValue], owner: object, assertActive: () => void): GuestReference;
|
|
53
|
+
export declare function readGuestReference(reference: unknown, owner: object): SandboxValue;
|
|
54
|
+
export declare function revokeGuestReference(reference: GuestReference, owner: object): void;
|
|
31
55
|
export declare function createLiveHostObject(definition: HostObjectDefinition, controller: HostObjectController): HostObject;
|
|
32
56
|
export declare function isGuestHostObject(value: unknown): value is SandboxHostObject;
|
|
33
57
|
export declare function isLiveCapability(value: unknown): boolean;
|
|
@@ -40,4 +64,7 @@ export declare function revokeHostObject(value: HostObject, owner: object): void
|
|
|
40
64
|
export declare function getHostObjectMember(value: SandboxObject, key: string): SandboxValue;
|
|
41
65
|
export declare function setHostObjectMember(value: SandboxObject, key: string, entry: SandboxValue): void;
|
|
42
66
|
export declare function getHostObjectKeys(value: SandboxObject): string[];
|
|
67
|
+
export declare function hasHostObjectMember(value: SandboxObject, key: string, enumerableOnly?: boolean): boolean;
|
|
68
|
+
export declare function measureHostObjectData(value: SandboxObject): number;
|
|
69
|
+
export declare function getHostObjectIterator(value: SandboxObject): SandboxIterator | undefined;
|
|
43
70
|
export {};
|
|
@@ -1,11 +1,83 @@
|
|
|
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();
|
|
9
|
+
const guestReferences = new WeakMap();
|
|
10
|
+
export function createGuestReference(root, owner, assertActive) {
|
|
11
|
+
const reference = Object.freeze(Object.create(null));
|
|
12
|
+
guestReferences.set(reference, { root, owner, assertActive });
|
|
13
|
+
return reference;
|
|
14
|
+
}
|
|
15
|
+
export function readGuestReference(reference, owner) {
|
|
16
|
+
const state = typeof reference === "object" && reference !== null
|
|
17
|
+
? guestReferences.get(reference)
|
|
18
|
+
: undefined;
|
|
19
|
+
if (state === undefined || state.owner !== owner)
|
|
20
|
+
throw new TypeError("Foreign or invalid guest reference.");
|
|
21
|
+
state.assertActive();
|
|
22
|
+
if (state.root === undefined)
|
|
23
|
+
throw new TypeError("Guest reference is revoked.");
|
|
24
|
+
return state.root[0];
|
|
25
|
+
}
|
|
26
|
+
export function revokeGuestReference(reference, owner) {
|
|
27
|
+
const state = guestReferences.get(reference);
|
|
28
|
+
if (state === undefined || state.owner !== owner)
|
|
29
|
+
throw new TypeError("Foreign guest reference.");
|
|
30
|
+
state.root = undefined;
|
|
31
|
+
}
|
|
5
32
|
export function createLiveHostObject(definition, controller) {
|
|
6
33
|
const input = readDataRecord(definition, "Host object definition");
|
|
7
|
-
if (Object.keys(input).some((key) =>
|
|
34
|
+
if (Object.keys(input).some((key) => !["properties", "methods", "indexed", "named"].includes(key)))
|
|
8
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
|
+
}
|
|
9
81
|
const properties = new Map();
|
|
10
82
|
for (const [name, inputProperty] of Object.entries(readDataRecord(input.properties ?? {}, "Host properties"))) {
|
|
11
83
|
const property = readDataRecord(inputProperty, `Host property '${name}'`);
|
|
@@ -26,6 +98,8 @@ export function createLiveHostObject(definition, controller) {
|
|
|
26
98
|
for (const name of [...properties.keys(), ...Object.keys(operations)]) {
|
|
27
99
|
if (["constructor", "prototype", "__proto__"].includes(name))
|
|
28
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}'.`);
|
|
29
103
|
}
|
|
30
104
|
controller.assertActive();
|
|
31
105
|
controller.chargeWork(properties.size + Object.keys(operations).length + 1);
|
|
@@ -35,7 +109,7 @@ export function createLiveHostObject(definition, controller) {
|
|
|
35
109
|
name,
|
|
36
110
|
controller.method(operation)
|
|
37
111
|
]));
|
|
38
|
-
const state = { host, guest, controller, properties, methods };
|
|
112
|
+
const state = { host, guest, controller, properties, methods, indexed, named };
|
|
39
113
|
hostObjects.set(host, state);
|
|
40
114
|
guestObjects.set(guest, state);
|
|
41
115
|
return host;
|
|
@@ -45,9 +119,14 @@ export function isGuestHostObject(value) {
|
|
|
45
119
|
}
|
|
46
120
|
export function isLiveCapability(value) {
|
|
47
121
|
return (((typeof value === "object" && value !== null) || typeof value === "function") &&
|
|
48
|
-
(hostObjects.has(value) ||
|
|
122
|
+
(hostObjects.has(value) ||
|
|
123
|
+
guestObjects.has(value) ||
|
|
124
|
+
guestCallbacks.has(value) ||
|
|
125
|
+
guestReferences.has(value)));
|
|
49
126
|
}
|
|
50
127
|
export function importHostCapability(value, owner) {
|
|
128
|
+
if (guestReferences.has(value))
|
|
129
|
+
return readGuestReference(value, owner);
|
|
51
130
|
const object = hostObjects.get(value);
|
|
52
131
|
if (object !== undefined) {
|
|
53
132
|
if (object.controller.owner !== owner)
|
|
@@ -97,15 +176,32 @@ export function revokeHostObject(value, owner) {
|
|
|
97
176
|
throw new TypeError("Foreign host object.");
|
|
98
177
|
state.properties.clear();
|
|
99
178
|
state.methods.clear();
|
|
179
|
+
state.indexed = undefined;
|
|
180
|
+
state.named = undefined;
|
|
100
181
|
}
|
|
101
182
|
export function getHostObjectMember(value, key) {
|
|
102
183
|
const state = guestObjects.get(value);
|
|
103
184
|
state.controller.assertActive();
|
|
104
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
|
+
}
|
|
105
196
|
const property = state.properties.get(key);
|
|
106
197
|
if (property !== undefined)
|
|
107
198
|
return property.get === undefined ? undefined : state.controller.read(property.get);
|
|
108
|
-
|
|
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;
|
|
109
205
|
}
|
|
110
206
|
export function setHostObjectMember(value, key, entry) {
|
|
111
207
|
const state = guestObjects.get(value);
|
|
@@ -119,5 +215,124 @@ export function setHostObjectMember(value, key, entry) {
|
|
|
119
215
|
export function getHostObjectKeys(value) {
|
|
120
216
|
const state = guestObjects.get(value);
|
|
121
217
|
state.controller.assertActive();
|
|
122
|
-
|
|
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
|
+
});
|
|
123
338
|
}
|