@poe-platform/safe-js 0.1.730 → 0.1.732
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/README.md +5 -5
- package/dist/safe-js/chunks/{chunk-SJZWSPWD.js → chunk-2GVBG23M.js} +1 -1
- package/dist/safe-js/chunks/{chunk-SJZWSPWD.js.map → chunk-2GVBG23M.js.map} +1 -1
- package/dist/safe-js/chunks/{chunk-MW2QLYVN.js → chunk-3ITVV2R6.js} +77 -66
- package/dist/safe-js/chunks/chunk-3ITVV2R6.js.map +7 -0
- package/dist/safe-js/chunks/{chunk-VIMROAQA.js → chunk-E4XLBT7R.js} +2 -2
- package/dist/safe-js/chunks/chunk-F6CV74HU.js +114 -0
- package/dist/safe-js/chunks/chunk-F6CV74HU.js.map +7 -0
- package/dist/safe-js/chunks/{chunk-OZLTLIHX.js → chunk-G6WTEIJJ.js} +2 -2
- package/dist/safe-js/chunks/{chunk-XX57YCGZ.js → chunk-GNEORAQI.js} +55 -50
- package/dist/safe-js/chunks/chunk-GNEORAQI.js.map +7 -0
- package/dist/safe-js/chunks/chunk-GVKXZW53.js +76 -0
- package/dist/safe-js/chunks/chunk-GVKXZW53.js.map +7 -0
- package/dist/safe-js/chunks/{chunk-MRULOD5J.js → chunk-K4T4EBED.js} +59 -38
- package/dist/safe-js/chunks/chunk-K4T4EBED.js.map +7 -0
- package/dist/safe-js/chunks/{chunk-MFCEBCGV.js → chunk-KETIFEA7.js} +17 -8
- package/dist/safe-js/chunks/chunk-KETIFEA7.js.map +7 -0
- package/dist/safe-js/chunks/{chunk-S5YDMMM3.js → chunk-MDIWQFYU.js} +95 -169
- package/dist/safe-js/chunks/{chunk-S5YDMMM3.js.map → chunk-MDIWQFYU.js.map} +4 -4
- package/dist/safe-js/chunks/{chunk-EN724VH3.js → chunk-O4IDRXW7.js} +4 -4
- package/dist/safe-js/chunks/{chunk-3LSNBW5N.js → chunk-P64CQIUA.js} +4 -4
- package/dist/safe-js/chunks/{chunk-EA7AHM2X.js → chunk-PSVV3IG2.js} +3 -3
- package/dist/safe-js/chunks/{chunk-Y5EZYHU6.js → chunk-QKO4FATZ.js} +33 -5
- package/dist/safe-js/chunks/chunk-QKO4FATZ.js.map +7 -0
- package/dist/safe-js/chunks/{chunk-EG3QWREA.js → chunk-RULU2F5J.js} +2 -2
- package/dist/safe-js/chunks/chunk-UQPYTZOZ.js +72 -0
- package/dist/safe-js/chunks/chunk-UQPYTZOZ.js.map +7 -0
- package/dist/safe-js/chunks/{chunk-LRSWTDIV.js → chunk-W4SWUHCQ.js} +58 -23
- package/dist/safe-js/chunks/chunk-W4SWUHCQ.js.map +7 -0
- package/dist/safe-js/chunks/{chunk-W3IY6FKO.js → chunk-WS2FJBH6.js} +2 -2
- package/dist/safe-js/chunks/{chunk-MVLDRXWS.js → chunk-X3CKAZQ4.js} +3 -3
- package/dist/safe-js/chunks/chunk-YUQFFRUF.js +26 -0
- package/dist/safe-js/chunks/chunk-YUQFFRUF.js.map +7 -0
- package/dist/safe-js/chunks/{cli-runtime-WSVW5DNT.js → cli-runtime-ZSOTNIVC.js} +51 -30
- package/dist/safe-js/chunks/cli-runtime-ZSOTNIVC.js.map +7 -0
- package/dist/safe-js/chunks/{dump-UNYK24YS.js → dump-RGRC3CG3.js} +5 -4
- package/dist/safe-js/chunks/harness-UMOMFXDT.js +11 -0
- package/dist/safe-js/chunks/mcp-2Z4XWHKW.js +12 -0
- package/dist/safe-js/chunks/migration-file-KRXQYI3Q.js +15 -0
- package/dist/safe-js/chunks/restore-GEO7VN2R.js +14 -0
- package/dist/safe-js/chunks/run-ZSFX66RN.js +20 -0
- package/dist/safe-js/chunks/runtime-modules-A3U2DKFL.js +11 -0
- package/dist/safe-js/chunks/source-files-L3SGK63Z.js +10 -0
- package/dist/safe-js/cli-runtime.d.ts +2 -0
- package/dist/safe-js/cli.js +4 -4
- package/dist/safe-js/core.d.ts +1 -0
- package/dist/safe-js/core.js +15 -8
- package/dist/safe-js/error/shape.js +170 -0
- package/dist/safe-js/index.js +42 -33
- package/dist/safe-js/index.js.map +2 -2
- package/dist/safe-js/interp/budget.js +600 -0
- package/dist/safe-js/interp/internal-symbols.js +1 -0
- package/dist/safe-js/interp/promise-replay.d.ts +1 -1
- package/dist/safe-js/interp/template-cache.js +7 -0
- package/dist/safe-js/migration-file.d.ts +2 -0
- package/dist/safe-js/modules/fs-config.d.ts +3 -1
- package/dist/safe-js/modules/fs.d.ts +8 -4
- package/dist/safe-js/modules/fs.js +16 -0
- package/dist/safe-js/modules/source-files.d.ts +3 -2
- package/dist/safe-js/platform/context.js +58 -0
- package/dist/safe-js/platform/node.js +64 -0
- package/dist/safe-js/platform/types.js +63 -0
- package/dist/safe-js/platform/workerd.js +39 -0
- package/dist/safe-js/runner/run-harness.d.ts +2 -0
- package/dist/safe-js/snapshot/backend.d.ts +2 -0
- package/package.json +9 -2
- package/dist/safe-js/chunks/chunk-FCCA27RC.js +0 -69
- package/dist/safe-js/chunks/chunk-FCCA27RC.js.map +0 -7
- package/dist/safe-js/chunks/chunk-LRSWTDIV.js.map +0 -7
- package/dist/safe-js/chunks/chunk-MFCEBCGV.js.map +0 -7
- package/dist/safe-js/chunks/chunk-MRULOD5J.js.map +0 -7
- package/dist/safe-js/chunks/chunk-MW2QLYVN.js.map +0 -7
- package/dist/safe-js/chunks/chunk-XX57YCGZ.js.map +0 -7
- package/dist/safe-js/chunks/chunk-Y5EZYHU6.js.map +0 -7
- package/dist/safe-js/chunks/cli-runtime-WSVW5DNT.js.map +0 -7
- package/dist/safe-js/chunks/fs-AOV77RQC.js +0 -13
- package/dist/safe-js/chunks/harness-ZIEO7NAV.js +0 -10
- package/dist/safe-js/chunks/mcp-E2AJXEQM.js +0 -11
- package/dist/safe-js/chunks/migration-file-3SWGNDS2.js +0 -12
- package/dist/safe-js/chunks/restore-FCTWPLU3.js +0 -13
- package/dist/safe-js/chunks/run-ESJHEXCE.js +0 -17
- package/dist/safe-js/chunks/runtime-modules-7V6BWCRK.js +0 -10
- package/dist/safe-js/chunks/source-files-73OHLNGH.js +0 -8
- /package/dist/safe-js/chunks/{chunk-VIMROAQA.js.map → chunk-E4XLBT7R.js.map} +0 -0
- /package/dist/safe-js/chunks/{chunk-OZLTLIHX.js.map → chunk-G6WTEIJJ.js.map} +0 -0
- /package/dist/safe-js/chunks/{chunk-EN724VH3.js.map → chunk-O4IDRXW7.js.map} +0 -0
- /package/dist/safe-js/chunks/{chunk-3LSNBW5N.js.map → chunk-P64CQIUA.js.map} +0 -0
- /package/dist/safe-js/chunks/{chunk-EA7AHM2X.js.map → chunk-PSVV3IG2.js.map} +0 -0
- /package/dist/safe-js/chunks/{chunk-EG3QWREA.js.map → chunk-RULU2F5J.js.map} +0 -0
- /package/dist/safe-js/chunks/{chunk-W3IY6FKO.js.map → chunk-WS2FJBH6.js.map} +0 -0
- /package/dist/safe-js/chunks/{chunk-MVLDRXWS.js.map → chunk-X3CKAZQ4.js.map} +0 -0
- /package/dist/safe-js/chunks/{dump-UNYK24YS.js.map → dump-RGRC3CG3.js.map} +0 -0
- /package/dist/safe-js/chunks/{fs-AOV77RQC.js.map → harness-UMOMFXDT.js.map} +0 -0
- /package/dist/safe-js/chunks/{harness-ZIEO7NAV.js.map → mcp-2Z4XWHKW.js.map} +0 -0
- /package/dist/safe-js/chunks/{mcp-E2AJXEQM.js.map → migration-file-KRXQYI3Q.js.map} +0 -0
- /package/dist/safe-js/chunks/{migration-file-3SWGNDS2.js.map → restore-GEO7VN2R.js.map} +0 -0
- /package/dist/safe-js/chunks/{restore-FCTWPLU3.js.map → run-ZSFX66RN.js.map} +0 -0
- /package/dist/safe-js/chunks/{run-ESJHEXCE.js.map → runtime-modules-A3U2DKFL.js.map} +0 -0
- /package/dist/safe-js/chunks/{runtime-modules-7V6BWCRK.js.map → source-files-L3SGK63Z.js.map} +0 -0
- /package/dist/safe-js/{chunks/source-files-73OHLNGH.js.map → modules/fs.js.map} +0 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
import { replaceErrorStack } from "../error/shape.js";
|
|
2
|
+
import { releaseTemplateObjects } from "./template-cache.js";
|
|
3
|
+
export const REGEX_COMPILE_LIMITS = Object.freeze({
|
|
4
|
+
sourceLength: Infinity,
|
|
5
|
+
flagsLength: Infinity,
|
|
6
|
+
depth: Infinity,
|
|
7
|
+
allocations: Infinity
|
|
8
|
+
});
|
|
9
|
+
const DEADLINE_CHECK_INTERVAL = 1_024;
|
|
10
|
+
export class SandboxError extends Error {
|
|
11
|
+
code;
|
|
12
|
+
budget;
|
|
13
|
+
current;
|
|
14
|
+
limit;
|
|
15
|
+
constructor(input) {
|
|
16
|
+
super(input === "aborted"
|
|
17
|
+
? "aborted"
|
|
18
|
+
: input === "reentry"
|
|
19
|
+
? "Sandbox object is already running."
|
|
20
|
+
: `Sandbox budget exceeded for ${input.budget}: ${input.current} > ${input.limit}.`);
|
|
21
|
+
this.name = "SandboxError";
|
|
22
|
+
replaceErrorStack(this);
|
|
23
|
+
if (input === "aborted") {
|
|
24
|
+
this.code = "aborted";
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (input === "reentry") {
|
|
28
|
+
this.code = "reentry";
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.code = "budgetExceeded";
|
|
32
|
+
this.budget = input.budget;
|
|
33
|
+
this.current = input.current;
|
|
34
|
+
this.limit = input.limit;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function isFatalSandboxError(error) {
|
|
38
|
+
return (error instanceof SandboxError && (error.code === "budgetExceeded" || error.code === "reentry"));
|
|
39
|
+
}
|
|
40
|
+
class BudgetAccounting {
|
|
41
|
+
deadline;
|
|
42
|
+
limits;
|
|
43
|
+
stepsUsed = 0;
|
|
44
|
+
peakCallDepth = 0;
|
|
45
|
+
currentDataSize = 0;
|
|
46
|
+
peakDataSize = 0;
|
|
47
|
+
currentCallDepth = 0;
|
|
48
|
+
allChecksSuspended = 0;
|
|
49
|
+
deadlineChecksSuspended = 0;
|
|
50
|
+
visitsUntilDeadlineCheck = DEADLINE_CHECK_INTERVAL;
|
|
51
|
+
pendingDeadlines;
|
|
52
|
+
earliestPendingDeadline;
|
|
53
|
+
retainedDataSize = 0;
|
|
54
|
+
retainedData = new Map();
|
|
55
|
+
retainedValueSources = new Map();
|
|
56
|
+
compileGeneration = 0;
|
|
57
|
+
activeCompileOwner;
|
|
58
|
+
defaultCompileOwner;
|
|
59
|
+
compileUses = 0;
|
|
60
|
+
provisionalScopes = 0;
|
|
61
|
+
compileTickets = new Map();
|
|
62
|
+
completedCompileTickets = new Set();
|
|
63
|
+
reconciliationHolds = 0;
|
|
64
|
+
deferredCompileDiscards = new Set();
|
|
65
|
+
realmViews;
|
|
66
|
+
constructor(options = {}) {
|
|
67
|
+
this.deadline = normalizeDeadline(options.deadline);
|
|
68
|
+
this.limits = Object.freeze({
|
|
69
|
+
maxSteps: normalizeLimit("maxSteps", options.maxSteps),
|
|
70
|
+
maxCallDepth: normalizeLimit("maxCallDepth", options.maxCallDepth),
|
|
71
|
+
stringLength: normalizeLimit("stringLength", options.stringLength),
|
|
72
|
+
...(options.regexSourceLength === undefined ? {} : {
|
|
73
|
+
regexSourceLength: normalizeRegexLimit("regexSourceLength", options.regexSourceLength)
|
|
74
|
+
}),
|
|
75
|
+
...(options.regexCompileAllocations === undefined ? {} : {
|
|
76
|
+
regexCompileAllocations: normalizeRegexLimit("regexCompileAllocations", options.regexCompileAllocations)
|
|
77
|
+
}),
|
|
78
|
+
arrayLength: normalizeLimit("arrayLength", options.arrayLength),
|
|
79
|
+
dataSize: normalizeLimit("dataSize", options.dataSize)
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export class Budget {
|
|
84
|
+
accounting;
|
|
85
|
+
realmView = false;
|
|
86
|
+
realmLease = false;
|
|
87
|
+
constructor(options = {}) {
|
|
88
|
+
this.accounting = new BudgetAccounting(options);
|
|
89
|
+
}
|
|
90
|
+
get deadline() { return this.accounting.deadline; }
|
|
91
|
+
get limits() { return this.accounting.limits; }
|
|
92
|
+
get stepsUsed() { return this.accounting.stepsUsed; }
|
|
93
|
+
set stepsUsed(value) { this.accounting.stepsUsed = value; }
|
|
94
|
+
get peakCallDepth() { return this.accounting.peakCallDepth; }
|
|
95
|
+
set peakCallDepth(value) { this.accounting.peakCallDepth = value; }
|
|
96
|
+
get currentDataSize() { return this.accounting.currentDataSize; }
|
|
97
|
+
set currentDataSize(value) { this.accounting.currentDataSize = value; }
|
|
98
|
+
get peakDataSize() { return this.accounting.peakDataSize; }
|
|
99
|
+
set peakDataSize(value) { this.accounting.peakDataSize = value; }
|
|
100
|
+
get currentCallDepth() { return this.accounting.currentCallDepth; }
|
|
101
|
+
set currentCallDepth(value) { this.accounting.currentCallDepth = value; }
|
|
102
|
+
// Pending host operations keep their elapsed deadline active during sampled
|
|
103
|
+
// synchronous work. Linked private records need no observable native hooks.
|
|
104
|
+
acquireDeadline(deadline, onExceeded) {
|
|
105
|
+
if (!Number.isFinite(deadline))
|
|
106
|
+
throw new TypeError("Deadline must be a finite timestamp.");
|
|
107
|
+
const accounting = this.accounting;
|
|
108
|
+
const pending = {
|
|
109
|
+
deadline, onExceeded,
|
|
110
|
+
next: accounting.pendingDeadlines, active: true
|
|
111
|
+
};
|
|
112
|
+
if (pending.next !== undefined)
|
|
113
|
+
pending.next.previous = pending;
|
|
114
|
+
accounting.pendingDeadlines = pending;
|
|
115
|
+
if (accounting.earliestPendingDeadline === undefined ||
|
|
116
|
+
pending.deadline < accounting.earliestPendingDeadline.deadline)
|
|
117
|
+
accounting.earliestPendingDeadline = pending;
|
|
118
|
+
return () => {
|
|
119
|
+
if (!pending.active)
|
|
120
|
+
return;
|
|
121
|
+
pending.active = false;
|
|
122
|
+
if (pending.previous === undefined)
|
|
123
|
+
accounting.pendingDeadlines = pending.next;
|
|
124
|
+
else
|
|
125
|
+
pending.previous.next = pending.next;
|
|
126
|
+
if (pending.next !== undefined)
|
|
127
|
+
pending.next.previous = pending.previous;
|
|
128
|
+
pending.previous = pending.next = undefined;
|
|
129
|
+
pending.onExceeded = undefined;
|
|
130
|
+
if (accounting.earliestPendingDeadline !== pending)
|
|
131
|
+
return;
|
|
132
|
+
accounting.earliestPendingDeadline = undefined;
|
|
133
|
+
for (let next = accounting.pendingDeadlines; next !== undefined; next = next.next)
|
|
134
|
+
if (accounting.earliestPendingDeadline === undefined ||
|
|
135
|
+
next.deadline < accounting.earliestPendingDeadline.deadline)
|
|
136
|
+
accounting.earliestPendingDeadline = next;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
forkRealm() {
|
|
140
|
+
// Realm-indexed caches use the view identity; all limits and usage stay shared.
|
|
141
|
+
const view = new Budget();
|
|
142
|
+
view.accounting = this.accounting;
|
|
143
|
+
view.realmView = true;
|
|
144
|
+
const views = this.accounting.realmViews ??= new Set([new WeakRef(this)]);
|
|
145
|
+
views.add(new WeakRef(view));
|
|
146
|
+
return view;
|
|
147
|
+
}
|
|
148
|
+
acquireRealmOwner() {
|
|
149
|
+
// Only explicit realm views share a live compile owner; each view still
|
|
150
|
+
// owns distinct realm-indexed caches and at most one live realm.
|
|
151
|
+
if (this.realmLease)
|
|
152
|
+
throw new SandboxError("reentry");
|
|
153
|
+
const lease = this.acquireCompileOwner(!this.realmView, this.realmView ? this.accounting.activeCompileOwner : undefined);
|
|
154
|
+
this.realmLease = true;
|
|
155
|
+
let released = false;
|
|
156
|
+
return {
|
|
157
|
+
owner: lease.owner,
|
|
158
|
+
release: () => {
|
|
159
|
+
if (released)
|
|
160
|
+
return;
|
|
161
|
+
released = true;
|
|
162
|
+
this.realmLease = false;
|
|
163
|
+
lease.release();
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
visitNode(units = 1) {
|
|
168
|
+
if (!Number.isSafeInteger(units) || units < 0) {
|
|
169
|
+
throw new Error("units must be a non-negative safe integer.");
|
|
170
|
+
}
|
|
171
|
+
if (units === 0)
|
|
172
|
+
return;
|
|
173
|
+
this.accounting.stepsUsed += units;
|
|
174
|
+
this.checkSampledDeadline(units);
|
|
175
|
+
if (this.accounting.allChecksSuspended === 0 &&
|
|
176
|
+
this.accounting.limits.maxSteps !== undefined &&
|
|
177
|
+
this.accounting.stepsUsed > this.accounting.limits.maxSteps) {
|
|
178
|
+
throw new SandboxError({
|
|
179
|
+
budget: "steps",
|
|
180
|
+
current: this.accounting.stepsUsed,
|
|
181
|
+
limit: this.accounting.limits.maxSteps
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
allocateString(value) {
|
|
186
|
+
this.allocateStringLength(value.length);
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
allocateStringLength(length) {
|
|
190
|
+
if (this.accounting.allChecksSuspended === 0 &&
|
|
191
|
+
this.accounting.limits.stringLength !== undefined &&
|
|
192
|
+
length > this.accounting.limits.stringLength) {
|
|
193
|
+
throw new SandboxError({
|
|
194
|
+
budget: "stringLength",
|
|
195
|
+
current: length,
|
|
196
|
+
limit: this.accounting.limits.stringLength
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
reserveString(length) {
|
|
201
|
+
if (!Number.isSafeInteger(length) || length < 0) {
|
|
202
|
+
throw new RangeError("string length must be a nonnegative safe integer");
|
|
203
|
+
}
|
|
204
|
+
if (this.accounting.allChecksSuspended === 0 && this.accounting.limits.stringLength !== undefined
|
|
205
|
+
&& length > this.accounting.limits.stringLength) {
|
|
206
|
+
throw new SandboxError({ budget: "stringLength", current: length, limit: this.accounting.limits.stringLength });
|
|
207
|
+
}
|
|
208
|
+
return this.provisionDataUsage(length);
|
|
209
|
+
}
|
|
210
|
+
allocateArrayLength(length) {
|
|
211
|
+
if (this.accounting.allChecksSuspended === 0 &&
|
|
212
|
+
this.accounting.limits.arrayLength !== undefined &&
|
|
213
|
+
length > this.accounting.limits.arrayLength) {
|
|
214
|
+
throw new SandboxError({
|
|
215
|
+
budget: "arrayLength",
|
|
216
|
+
current: length,
|
|
217
|
+
limit: this.accounting.limits.arrayLength
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
allocateCollectionEntries(count) {
|
|
222
|
+
this.allocateArrayLength(count);
|
|
223
|
+
}
|
|
224
|
+
reconcileDataUsage(usage) {
|
|
225
|
+
const measured = usage + this.accounting.retainedDataSize;
|
|
226
|
+
const total = this.reconciliationDeferred ? Math.max(measured, this.accounting.currentDataSize) : measured;
|
|
227
|
+
this.checkDataUsage(total);
|
|
228
|
+
this.accounting.currentDataSize = total;
|
|
229
|
+
this.accounting.peakDataSize = Math.max(this.accounting.peakDataSize, total);
|
|
230
|
+
}
|
|
231
|
+
setRetainedDataUsage(owner, usage) {
|
|
232
|
+
if (!Number.isSafeInteger(usage) || usage < 0) {
|
|
233
|
+
throw new TypeError("Retained data usage must be a non-negative safe integer.");
|
|
234
|
+
}
|
|
235
|
+
const delta = usage - (this.accounting.retainedData.get(owner) ?? 0);
|
|
236
|
+
const total = this.accounting.currentDataSize + delta;
|
|
237
|
+
this.checkDataUsage(total);
|
|
238
|
+
if (usage === 0)
|
|
239
|
+
this.accounting.retainedData.delete(owner);
|
|
240
|
+
else
|
|
241
|
+
this.accounting.retainedData.set(owner, usage);
|
|
242
|
+
this.accounting.retainedDataSize += delta;
|
|
243
|
+
this.accounting.currentDataSize = total;
|
|
244
|
+
this.accounting.peakDataSize = Math.max(this.accounting.peakDataSize, total);
|
|
245
|
+
}
|
|
246
|
+
setRetainedValues(owner, values) {
|
|
247
|
+
if (values === undefined)
|
|
248
|
+
this.accounting.retainedValueSources.delete(owner);
|
|
249
|
+
else
|
|
250
|
+
this.accounting.retainedValueSources.set(owner, values);
|
|
251
|
+
}
|
|
252
|
+
get reconciliationDeferred() {
|
|
253
|
+
return this.accounting.reconciliationHolds > 0;
|
|
254
|
+
}
|
|
255
|
+
deferReconciliation() {
|
|
256
|
+
this.accounting.reconciliationHolds++;
|
|
257
|
+
const generation = this.accounting.compileGeneration;
|
|
258
|
+
let released = false;
|
|
259
|
+
return () => {
|
|
260
|
+
if (released)
|
|
261
|
+
return;
|
|
262
|
+
released = true;
|
|
263
|
+
if (generation !== this.accounting.compileGeneration)
|
|
264
|
+
return;
|
|
265
|
+
if (--this.accounting.reconciliationHolds !== 0)
|
|
266
|
+
return;
|
|
267
|
+
for (const ticket of this.accounting.deferredCompileDiscards)
|
|
268
|
+
this.accounting.completedCompileTickets.add(ticket);
|
|
269
|
+
this.accounting.deferredCompileDiscards.clear();
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
*retainedValues() {
|
|
273
|
+
for (const values of this.accounting.retainedValueSources.values()) {
|
|
274
|
+
const retained = values();
|
|
275
|
+
if (retained !== undefined)
|
|
276
|
+
yield* retained;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
acquireCompileOwner(reset = false, owner) {
|
|
280
|
+
if ((owner !== undefined &&
|
|
281
|
+
(owner.budget.accounting !== this.accounting || owner.generation !== this.accounting.compileGeneration)) ||
|
|
282
|
+
(this.accounting.activeCompileOwner !== undefined && this.accounting.activeCompileOwner !== owner) ||
|
|
283
|
+
(reset && (this.accounting.compileUses !== 0 || owner !== undefined))) {
|
|
284
|
+
throw new SandboxError("reentry");
|
|
285
|
+
}
|
|
286
|
+
if (reset)
|
|
287
|
+
this.reset();
|
|
288
|
+
const selected = owner ?? (this.accounting.defaultCompileOwner ??= Object.freeze({ budget: this, generation: this.accounting.compileGeneration }));
|
|
289
|
+
this.accounting.activeCompileOwner = selected;
|
|
290
|
+
this.accounting.compileUses += 1;
|
|
291
|
+
let released = false;
|
|
292
|
+
return {
|
|
293
|
+
owner: selected,
|
|
294
|
+
release: () => {
|
|
295
|
+
if (released)
|
|
296
|
+
return;
|
|
297
|
+
released = true;
|
|
298
|
+
this.accounting.compileUses -= 1;
|
|
299
|
+
if (this.accounting.compileUses === 0)
|
|
300
|
+
this.accounting.activeCompileOwner = undefined;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
createCompileTicket(owner) {
|
|
305
|
+
if (owner.budget.accounting !== this.accounting ||
|
|
306
|
+
owner.generation !== this.accounting.compileGeneration ||
|
|
307
|
+
this.accounting.activeCompileOwner !== owner) {
|
|
308
|
+
throw new SandboxError("reentry");
|
|
309
|
+
}
|
|
310
|
+
const ticket = Object.freeze({ owner });
|
|
311
|
+
this.accounting.compileTickets.set(ticket, 0);
|
|
312
|
+
return ticket;
|
|
313
|
+
}
|
|
314
|
+
compileTicketUsage(ticket) {
|
|
315
|
+
return ticket.owner.generation === this.accounting.compileGeneration
|
|
316
|
+
? (this.accounting.compileTickets.get(ticket) ?? 0)
|
|
317
|
+
: 0;
|
|
318
|
+
}
|
|
319
|
+
resizeCompileTicket(ticket, usage) {
|
|
320
|
+
if (ticket.owner.generation !== this.accounting.compileGeneration || !this.accounting.compileTickets.has(ticket)) {
|
|
321
|
+
throw new SandboxError("reentry");
|
|
322
|
+
}
|
|
323
|
+
if (this.reconciliationDeferred)
|
|
324
|
+
usage = Math.max(usage, this.compileTicketUsage(ticket));
|
|
325
|
+
this.setRetainedDataUsage(ticket, usage);
|
|
326
|
+
this.accounting.compileTickets.set(ticket, usage);
|
|
327
|
+
}
|
|
328
|
+
discardCompileTicket(ticket) {
|
|
329
|
+
if (ticket.owner.generation !== this.accounting.compileGeneration)
|
|
330
|
+
return;
|
|
331
|
+
const usage = this.accounting.compileTickets.get(ticket);
|
|
332
|
+
if (usage === undefined)
|
|
333
|
+
return;
|
|
334
|
+
if (this.reconciliationDeferred) {
|
|
335
|
+
this.accounting.deferredCompileDiscards.add(ticket);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
this.accounting.compileTickets.delete(ticket);
|
|
339
|
+
this.accounting.completedCompileTickets.delete(ticket);
|
|
340
|
+
this.accounting.retainedData.delete(ticket);
|
|
341
|
+
this.accounting.retainedDataSize -= usage;
|
|
342
|
+
this.accounting.currentDataSize -= usage;
|
|
343
|
+
}
|
|
344
|
+
reconcileCompileData(usage, included, transferred = included, retainedOwner, complete = false) {
|
|
345
|
+
if (this.reconciliationDeferred) {
|
|
346
|
+
if (retainedOwner !== undefined)
|
|
347
|
+
this.setRetainedDataUsage(retainedOwner, usage);
|
|
348
|
+
else {
|
|
349
|
+
// Keep tickets owned while another job can still use them. Their bytes
|
|
350
|
+
// already appear in the measured graph, so do not charge them twice.
|
|
351
|
+
let includedUsage = 0;
|
|
352
|
+
for (const ticket of included)
|
|
353
|
+
includedUsage += this.compileTicketUsage(ticket);
|
|
354
|
+
const measured = usage + this.accounting.retainedDataSize - includedUsage;
|
|
355
|
+
const total = Math.max(measured, this.accounting.currentDataSize);
|
|
356
|
+
this.checkDataUsage(total);
|
|
357
|
+
this.accounting.currentDataSize = total;
|
|
358
|
+
this.accounting.peakDataSize = Math.max(this.accounting.peakDataSize, total);
|
|
359
|
+
}
|
|
360
|
+
return new Set();
|
|
361
|
+
}
|
|
362
|
+
let includedUsage = 0;
|
|
363
|
+
let transferredUsage = 0;
|
|
364
|
+
let discardedUsage = 0;
|
|
365
|
+
const releasing = [];
|
|
366
|
+
const retained = new Set();
|
|
367
|
+
for (const ticket of included) {
|
|
368
|
+
const charge = this.compileTicketUsage(ticket);
|
|
369
|
+
includedUsage += charge;
|
|
370
|
+
if (charge > 0 &&
|
|
371
|
+
transferred.has(ticket) &&
|
|
372
|
+
(this.accounting.provisionalScopes === 0 || retainedOwner !== undefined)) {
|
|
373
|
+
releasing.push(ticket);
|
|
374
|
+
transferredUsage += charge;
|
|
375
|
+
}
|
|
376
|
+
else if (charge > 0 && complete && transferred.has(ticket)) {
|
|
377
|
+
retained.add(ticket);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (retainedOwner === undefined) {
|
|
381
|
+
for (const ticket of this.accounting.completedCompileTickets) {
|
|
382
|
+
if (included.has(ticket))
|
|
383
|
+
continue;
|
|
384
|
+
releasing.push(ticket);
|
|
385
|
+
discardedUsage += this.compileTicketUsage(ticket);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const oldOwnerUsage = retainedOwner === undefined ? 0 : (this.accounting.retainedData.get(retainedOwner) ?? 0);
|
|
389
|
+
const nextRetained = this.accounting.retainedDataSize -
|
|
390
|
+
transferredUsage -
|
|
391
|
+
discardedUsage +
|
|
392
|
+
(retainedOwner === undefined ? 0 : usage - oldOwnerUsage);
|
|
393
|
+
const measured = retainedOwner === undefined
|
|
394
|
+
? usage - includedUsage + transferredUsage
|
|
395
|
+
: this.accounting.currentDataSize - this.accounting.retainedDataSize;
|
|
396
|
+
const total = measured + nextRetained;
|
|
397
|
+
this.checkDataUsage(total);
|
|
398
|
+
for (const ticket of releasing) {
|
|
399
|
+
this.accounting.compileTickets.delete(ticket);
|
|
400
|
+
this.accounting.completedCompileTickets.delete(ticket);
|
|
401
|
+
this.accounting.retainedData.delete(ticket);
|
|
402
|
+
}
|
|
403
|
+
if (retainedOwner !== undefined) {
|
|
404
|
+
if (usage === 0)
|
|
405
|
+
this.accounting.retainedData.delete(retainedOwner);
|
|
406
|
+
else
|
|
407
|
+
this.accounting.retainedData.set(retainedOwner, usage);
|
|
408
|
+
}
|
|
409
|
+
this.accounting.retainedDataSize = nextRetained;
|
|
410
|
+
this.accounting.currentDataSize = total;
|
|
411
|
+
this.accounting.peakDataSize = Math.max(this.accounting.peakDataSize, total);
|
|
412
|
+
for (const ticket of retained)
|
|
413
|
+
this.accounting.completedCompileTickets.add(ticket);
|
|
414
|
+
return retained;
|
|
415
|
+
}
|
|
416
|
+
chargeDataUsage(usage) {
|
|
417
|
+
const total = this.accounting.currentDataSize + usage;
|
|
418
|
+
this.checkDataUsage(total);
|
|
419
|
+
this.accounting.currentDataSize = total;
|
|
420
|
+
this.accounting.peakDataSize = Math.max(this.accounting.peakDataSize, total);
|
|
421
|
+
}
|
|
422
|
+
provisionDataUsage(usage) {
|
|
423
|
+
const previous = this.accounting.currentDataSize;
|
|
424
|
+
const previousRetained = this.accounting.retainedDataSize;
|
|
425
|
+
const next = previous + usage;
|
|
426
|
+
this.checkDataUsage(next);
|
|
427
|
+
this.accounting.currentDataSize = next;
|
|
428
|
+
this.accounting.peakDataSize = Math.max(this.accounting.peakDataSize, next);
|
|
429
|
+
const generation = this.accounting.compileGeneration;
|
|
430
|
+
this.accounting.provisionalScopes += 1;
|
|
431
|
+
let released = false;
|
|
432
|
+
return (commit = false) => {
|
|
433
|
+
if (released)
|
|
434
|
+
return;
|
|
435
|
+
released = true;
|
|
436
|
+
if (generation !== this.accounting.compileGeneration)
|
|
437
|
+
return;
|
|
438
|
+
this.accounting.provisionalScopes -= 1;
|
|
439
|
+
if (!commit) {
|
|
440
|
+
const restored = previous + this.accounting.retainedDataSize - previousRetained;
|
|
441
|
+
this.accounting.currentDataSize = this.reconciliationDeferred
|
|
442
|
+
? Math.max(restored, this.accounting.currentDataSize) : restored;
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
enterCall() {
|
|
447
|
+
return this.enterDepth();
|
|
448
|
+
}
|
|
449
|
+
enterAwait() {
|
|
450
|
+
return this.enterDepth();
|
|
451
|
+
}
|
|
452
|
+
reset() {
|
|
453
|
+
if (this.accounting.compileUses !== 0)
|
|
454
|
+
throw new SandboxError("reentry");
|
|
455
|
+
releaseTemplateObjects(this);
|
|
456
|
+
for (const reference of this.accounting.realmViews ?? []) {
|
|
457
|
+
const view = reference.deref();
|
|
458
|
+
if (view === undefined)
|
|
459
|
+
this.accounting.realmViews.delete(reference);
|
|
460
|
+
else if (view !== this)
|
|
461
|
+
releaseTemplateObjects(view);
|
|
462
|
+
}
|
|
463
|
+
this.accounting.compileGeneration += 1;
|
|
464
|
+
this.accounting.defaultCompileOwner = undefined;
|
|
465
|
+
this.accounting.provisionalScopes = 0;
|
|
466
|
+
this.accounting.compileTickets.clear();
|
|
467
|
+
this.accounting.completedCompileTickets.clear();
|
|
468
|
+
this.accounting.reconciliationHolds = 0;
|
|
469
|
+
this.accounting.deferredCompileDiscards.clear();
|
|
470
|
+
this.accounting.stepsUsed = 0;
|
|
471
|
+
this.accounting.peakCallDepth = 0;
|
|
472
|
+
this.accounting.currentCallDepth = 0;
|
|
473
|
+
this.accounting.currentDataSize = 0;
|
|
474
|
+
this.accounting.peakDataSize = 0;
|
|
475
|
+
this.accounting.retainedDataSize = 0;
|
|
476
|
+
this.accounting.retainedData.clear();
|
|
477
|
+
this.accounting.retainedValueSources.clear();
|
|
478
|
+
this.accounting.allChecksSuspended = 0;
|
|
479
|
+
this.accounting.deadlineChecksSuspended = 0;
|
|
480
|
+
this.accounting.visitsUntilDeadlineCheck = DEADLINE_CHECK_INTERVAL;
|
|
481
|
+
}
|
|
482
|
+
suspendChecks() {
|
|
483
|
+
this.accounting.allChecksSuspended += 1;
|
|
484
|
+
let resumed = false;
|
|
485
|
+
return () => {
|
|
486
|
+
if (resumed) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
resumed = true;
|
|
490
|
+
this.accounting.allChecksSuspended -= 1;
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
suspendDeadlineChecks() {
|
|
494
|
+
this.accounting.deadlineChecksSuspended += 1;
|
|
495
|
+
let resumed = false;
|
|
496
|
+
return () => {
|
|
497
|
+
if (resumed) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
resumed = true;
|
|
501
|
+
this.accounting.deadlineChecksSuspended -= 1;
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
checkDeadline() {
|
|
505
|
+
const pending = this.accounting.earliestPendingDeadline;
|
|
506
|
+
const deadline = this.accounting.deadline === undefined ? pending?.deadline
|
|
507
|
+
: Math.min(this.accounting.deadline, pending?.deadline ?? Infinity);
|
|
508
|
+
if (this.accounting.allChecksSuspended > 0 ||
|
|
509
|
+
this.accounting.deadlineChecksSuspended > 0 ||
|
|
510
|
+
deadline === undefined) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const now = Date.now();
|
|
514
|
+
if (now <= deadline) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const reason = new SandboxError({
|
|
518
|
+
budget: "deadline",
|
|
519
|
+
current: now,
|
|
520
|
+
limit: deadline
|
|
521
|
+
});
|
|
522
|
+
if (pending?.deadline === deadline) {
|
|
523
|
+
const notify = pending.onExceeded;
|
|
524
|
+
pending.onExceeded = undefined;
|
|
525
|
+
notify?.(reason);
|
|
526
|
+
}
|
|
527
|
+
throw reason;
|
|
528
|
+
}
|
|
529
|
+
checkDataUsage(usage) {
|
|
530
|
+
if (this.accounting.allChecksSuspended === 0 &&
|
|
531
|
+
this.accounting.limits.dataSize !== undefined &&
|
|
532
|
+
usage > this.accounting.limits.dataSize) {
|
|
533
|
+
throw new SandboxError({
|
|
534
|
+
budget: "dataSize",
|
|
535
|
+
current: usage,
|
|
536
|
+
limit: this.accounting.limits.dataSize
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
checkSampledDeadline(units) {
|
|
541
|
+
if (this.accounting.allChecksSuspended > 0 ||
|
|
542
|
+
this.accounting.deadlineChecksSuspended > 0 ||
|
|
543
|
+
(this.accounting.deadline === undefined && this.accounting.earliestPendingDeadline === undefined)) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
this.accounting.visitsUntilDeadlineCheck -= units;
|
|
547
|
+
if (this.accounting.visitsUntilDeadlineCheck > 0) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
this.accounting.visitsUntilDeadlineCheck =
|
|
551
|
+
DEADLINE_CHECK_INTERVAL - (-this.accounting.visitsUntilDeadlineCheck % DEADLINE_CHECK_INTERVAL);
|
|
552
|
+
this.checkDeadline();
|
|
553
|
+
}
|
|
554
|
+
enterDepth() {
|
|
555
|
+
const nextDepth = this.accounting.currentCallDepth + 1;
|
|
556
|
+
if (this.accounting.allChecksSuspended === 0 &&
|
|
557
|
+
this.accounting.limits.maxCallDepth !== undefined &&
|
|
558
|
+
nextDepth > this.accounting.limits.maxCallDepth) {
|
|
559
|
+
throw new SandboxError({
|
|
560
|
+
budget: "callDepth",
|
|
561
|
+
current: nextDepth,
|
|
562
|
+
limit: this.accounting.limits.maxCallDepth
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
this.accounting.currentCallDepth = nextDepth;
|
|
566
|
+
if (nextDepth > this.accounting.peakCallDepth) {
|
|
567
|
+
this.accounting.peakCallDepth = nextDepth;
|
|
568
|
+
}
|
|
569
|
+
let left = false;
|
|
570
|
+
return () => {
|
|
571
|
+
if (left) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
left = true;
|
|
575
|
+
this.accounting.currentCallDepth -= 1;
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function normalizeDeadline(deadline) {
|
|
580
|
+
if (deadline === undefined || deadline === Infinity) {
|
|
581
|
+
return undefined;
|
|
582
|
+
}
|
|
583
|
+
return deadline instanceof Date ? deadline.getTime() : deadline;
|
|
584
|
+
}
|
|
585
|
+
function normalizeLimit(name, value) {
|
|
586
|
+
if (value === undefined || value === Infinity) {
|
|
587
|
+
return undefined;
|
|
588
|
+
}
|
|
589
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
590
|
+
throw new Error(`${name} must be a non-negative integer.`);
|
|
591
|
+
}
|
|
592
|
+
return value;
|
|
593
|
+
}
|
|
594
|
+
function normalizeRegexLimit(name, value) {
|
|
595
|
+
if (value === undefined || value === Infinity)
|
|
596
|
+
return undefined;
|
|
597
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
598
|
+
throw new RangeError(`${name} must be a positive safe integer.`);
|
|
599
|
+
return value;
|
|
600
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const internalSymbols = new Set();
|
|
@@ -4,9 +4,11 @@ export type FsConfig = {
|
|
|
4
4
|
readonly adapter: FileSystemConfig;
|
|
5
5
|
readonly root?: string;
|
|
6
6
|
readonly cwd?: string;
|
|
7
|
+
readonly readFileMaxBytes?: number;
|
|
8
|
+
readonly hostReadMemoryLimit?: number;
|
|
7
9
|
};
|
|
8
10
|
export type ResolveFsConfigOptions = {
|
|
9
11
|
readonly registry?: FileSystemAdapterRegistry;
|
|
10
12
|
};
|
|
11
13
|
export declare function parseFsConfig(json: string): FsConfig;
|
|
12
|
-
export declare function resolveFsConfig(config: FsConfig, options?: ResolveFsConfigOptions): Promise<Required<Pick<FsModuleOptions, "adapter">> &
|
|
14
|
+
export declare function resolveFsConfig(config: FsConfig, options?: ResolveFsConfigOptions): Promise<Required<Pick<FsModuleOptions, "adapter">> & Omit<FsConfig, "adapter">>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { hostFs as nodeFsPromises } from "#safe-js-platform";
|
|
2
|
+
import type { FsBridgeEncoding } from "@poe-platform/safe-fs/core";
|
|
3
|
+
type PathLike = string | Uint8Array | URL;
|
|
3
4
|
import { type FileSystem } from "@poe-platform/safe-fs/core";
|
|
4
5
|
type FsOperationName = "access" | "appendFile" | "chmod" | "copyFile" | "cp" | "link" | "lstat" | "mkdir" | "mkdtemp" | "readFile" | "readdir" | "readlink" | "realpath" | "rename" | "rm" | "rmdir" | "stat" | "symlink" | "truncate" | "utimes" | "writeFile";
|
|
5
6
|
type FsPassthroughName = Exclude<FsOperationName, "lstat" | "mkdtemp" | "readFile" | "readdir" | "readlink" | "realpath" | "stat">;
|
|
@@ -27,7 +28,7 @@ export type SandboxDirent = {
|
|
|
27
28
|
readonly name: string;
|
|
28
29
|
readonly parentPath: string;
|
|
29
30
|
} & FileTypePredicates;
|
|
30
|
-
type StringEncoding =
|
|
31
|
+
type StringEncoding = FsBridgeEncoding;
|
|
31
32
|
type EncodingOptions = StringEncoding | {
|
|
32
33
|
encoding: StringEncoding;
|
|
33
34
|
};
|
|
@@ -43,6 +44,9 @@ type StatOptions = {
|
|
|
43
44
|
bigint?: false;
|
|
44
45
|
};
|
|
45
46
|
export type FsModuleOptions = {
|
|
47
|
+
hostReadMemoryLimit?: number;
|
|
48
|
+
readFileMaxBytes?: number;
|
|
49
|
+
} & ({
|
|
46
50
|
root?: string;
|
|
47
51
|
fs?: FsImplementation;
|
|
48
52
|
adapter?: never;
|
|
@@ -54,7 +58,7 @@ export type FsModuleOptions = {
|
|
|
54
58
|
cwd?: string;
|
|
55
59
|
signal?: AbortSignal;
|
|
56
60
|
fs?: never;
|
|
57
|
-
};
|
|
61
|
+
});
|
|
58
62
|
export type FsModule = Pick<FsImplementation, FsPassthroughName> & {
|
|
59
63
|
readFile(path: PathLike, options: ReadFileOptions): Promise<string>;
|
|
60
64
|
readlink(path: PathLike, options?: EncodingOptions): Promise<string>;
|