poe-code 5.0.4 → 7.0.0

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.
Files changed (52) hide show
  1. package/dist/index.js +3457 -3542
  2. package/dist/index.js.map +4 -4
  3. package/dist/metafile.json +1 -1
  4. package/package.json +1 -1
  5. package/packages/safejs/dist/interp/async.d.ts +2 -1
  6. package/packages/safejs/dist/interp/async.js +56 -33
  7. package/packages/safejs/dist/interp/cancel.d.ts +7 -2
  8. package/packages/safejs/dist/interp/cancel.js +128 -92
  9. package/packages/safejs/dist/interp/exceptions.js +7 -3
  10. package/packages/safejs/dist/interp/globals/collections.js +2 -0
  11. package/packages/safejs/dist/interp/globals/console-json.js +4 -0
  12. package/packages/safejs/dist/interp/globals/error.js +1 -5
  13. package/packages/safejs/dist/interp/globals/math.js +2 -4
  14. package/packages/safejs/dist/interp/globals/misc.js +5 -0
  15. package/packages/safejs/dist/interp/globals/object-array.js +25 -0
  16. package/packages/safejs/dist/interp/globals/regex.js +1 -1
  17. package/packages/safejs/dist/interp/host-bridge.js +8 -5
  18. package/packages/safejs/dist/interp/interpreter.js +33 -37
  19. package/packages/safejs/dist/interp/iteration.d.ts +3 -3
  20. package/packages/safejs/dist/interp/iteration.js +20 -9
  21. package/packages/safejs/dist/interp/methods/array.js +1 -0
  22. package/packages/safejs/dist/interp/methods/function.d.ts +1 -1
  23. package/packages/safejs/dist/interp/methods/function.js +17 -1
  24. package/packages/safejs/dist/interp/methods/generator.js +1 -0
  25. package/packages/safejs/dist/interp/methods/map.js +1 -0
  26. package/packages/safejs/dist/interp/methods/number.js +1 -0
  27. package/packages/safejs/dist/interp/methods/regex.js +1 -0
  28. package/packages/safejs/dist/interp/methods/set.js +1 -0
  29. package/packages/safejs/dist/interp/methods/string.js +1 -0
  30. package/packages/safejs/dist/interp/patterns.d.ts +1 -0
  31. package/packages/safejs/dist/interp/patterns.js +1 -1
  32. package/packages/safejs/dist/interp/promise-tracker.d.ts +12 -2
  33. package/packages/safejs/dist/interp/promise-tracker.js +65 -7
  34. package/packages/safejs/dist/interp/promise.d.ts +7 -5
  35. package/packages/safejs/dist/interp/promise.js +531 -148
  36. package/packages/safejs/dist/interp/values.d.ts +6 -0
  37. package/packages/safejs/dist/interp/values.js +25 -5
  38. package/packages/safejs/dist/interp/var-hoist.js +5 -135
  39. package/packages/safejs/dist/lint/index.js +0 -4
  40. package/packages/safejs/dist/lint/rules/AS001.js +4 -9
  41. package/packages/safejs/dist/lint/rules/AS003.js +47 -7
  42. package/packages/safejs/dist/lint/rules/AS006-007.js +48 -7
  43. package/packages/safejs/dist/parse/bindings.d.ts +6 -0
  44. package/packages/safejs/dist/parse/bindings.js +90 -0
  45. package/packages/safejs/dist/parse/parser.js +70 -37
  46. package/packages/safejs/dist/snapshot/dump-format.d.ts +1 -1
  47. package/packages/safejs/dist/snapshot/dump-format.js +1 -1
  48. package/packages/safejs/dist/snapshot/replay-data.js +3 -0
  49. package/packages/safejs/dist/lint/rules/AS-await-non-promise.d.ts +0 -13
  50. package/packages/safejs/dist/lint/rules/AS-await-non-promise.js +0 -301
  51. package/packages/safejs/dist/lint/rules/AS008.d.ts +0 -13
  52. package/packages/safejs/dist/lint/rules/AS008.js +0 -386
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-code",
3
- "version": "5.0.4",
3
+ "version": "7.0.0",
4
4
  "description": "CLI tool to configure Poe API for developer workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,4 +1,4 @@
1
- import type { ArrowFunctionExpression, AwaitExpression, FunctionDeclaration, FunctionExpression, ParseResult, SourceSpan } from "../parse.js";
1
+ import type { ArrowFunctionExpression, AwaitExpression, BlockStatement, FunctionDeclaration, FunctionExpression, ParseResult, SourceSpan } from "../parse.js";
2
2
  import { type GeneratorCompletion } from "./generator.js";
3
3
  import { type OtelSpan } from "../observability/otel.js";
4
4
  import type { Budget } from "./budget.js";
@@ -25,6 +25,7 @@ export type AsyncEvaluationContext = {
25
25
  onSuspend?: () => void;
26
26
  captureReplayState?: () => unknown;
27
27
  rootNode?: ParseResult;
28
+ functionBody?: BlockStatement;
28
29
  restoredLoopIterations: Map<number, LoopIterationSnapshot>;
29
30
  resumeTarget?: {
30
31
  nodeId?: number;
@@ -5,7 +5,8 @@ import { getThenable, resolveSandboxValue } from "./promise.js";
5
5
  import { runAsyncPrefix, suspendJob } from "./jobs.js";
6
6
  import { awaitSandboxValue } from "./cancel.js";
7
7
  import { hoistVarDeclarations } from "./var-hoist.js";
8
- import { createSandboxArguments, createSandboxClosure, createSandboxGenerator, createSandboxPromise, isSandboxPromise } from "./values.js";
8
+ import { boundIdentifiers, containsParameterExpression, hoistedVarDeclarations } from "../parse/bindings.js";
9
+ import { createSandboxArguments, createSandboxClosure, createSandboxGenerator, createSandboxPromise, allocateProducedSandboxValue, isSandboxPromise } from "./values.js";
9
10
  export function emitResumeBreakpoint(context, breakpoint) {
10
11
  context.onYield?.({
11
12
  ...breakpoint,
@@ -53,6 +54,7 @@ export function createInterpretedClosure(node, context, evaluateNode) {
53
54
  }
54
55
  : undefined;
55
56
  return createSandboxClosure({
57
+ sandbox: true,
56
58
  ...(node.async ? { async: true } : {}),
57
59
  ...(node.type === "FunctionDeclaration" || node.type === "FunctionExpression"
58
60
  ? node.id === undefined
@@ -72,16 +74,34 @@ export function createInterpretedClosure(node, context, evaluateNode) {
72
74
  const synchronousPrefix = new Promise((resolve) => {
73
75
  completePrefix = resolve;
74
76
  });
75
- const execution = runAsyncPrefix(() => executeClosure(node, args, callContext?.thisValue, { ...invocationContext, onSuspend: completePrefix }, evaluateNode));
76
- execution.then(completePrefix, completePrefix);
77
- return createSandboxPromise(resolveSandboxValue(execution.then((value) => isSandboxPromise(value) || getThenable(value) !== undefined
78
- ? awaitSandboxValue(value, context.signal)
79
- : value), { budget: context.budget }), { synchronousPrefix });
77
+ const promise = new Promise((resolve, reject) => {
78
+ runAsyncPrefix(async () => {
79
+ try {
80
+ const value = await executeClosure(node, args, callContext?.thisValue, { ...invocationContext, onSuspend: completePrefix }, evaluateNode);
81
+ resolve(isSandboxPromise(value) || getThenable(value) !== undefined
82
+ ? awaitSandboxValue(createSandboxPromise(resolveSandboxValue(value, { budget: context.budget }), {
83
+ trackReplay: false
84
+ }), context.signal, context.budget)
85
+ : allocateProducedSandboxValue(value, context.budget));
86
+ }
87
+ catch (error) {
88
+ reject(error);
89
+ }
90
+ finally {
91
+ completePrefix();
92
+ }
93
+ }).catch((error) => {
94
+ reject(error);
95
+ completePrefix();
96
+ });
97
+ });
98
+ return createSandboxPromise(promise, { synchronousPrefix });
80
99
  }
81
100
  });
82
101
  }
83
102
  function createGeneratorClosure(node, context, evaluateNode) {
84
103
  return createSandboxClosure({
104
+ sandbox: true,
85
105
  ...(node.id === undefined ? {} : { name: node.id.name }),
86
106
  retainedValues: () => context.scope.retainedValues(),
87
107
  call: async (args, callContext) => {
@@ -93,6 +113,7 @@ function createGeneratorClosure(node, context, evaluateNode) {
93
113
  const channel = createGeneratorChannel(async (generatorYield) => {
94
114
  const result = await evaluateNode(node.body, {
95
115
  ...closureContext,
116
+ functionBody: node.body,
96
117
  generatorYield: (value, yieldNodeId) => {
97
118
  generator.state = "suspended";
98
119
  return generatorYield(value, yieldNodeId);
@@ -136,7 +157,7 @@ export async function evaluateAwaitExpression(node, context, evaluateNode) {
136
157
  return {
137
158
  kind: "normal",
138
159
  hasValue: true,
139
- value: await suspendJob(awaitSandboxValue(argument.value, context.signal))
160
+ value: await suspendJob(awaitSandboxValue(argument.value, context.signal, context.budget))
140
161
  };
141
162
  }
142
163
  finally {
@@ -147,6 +168,7 @@ async function executeClosure(node, args, thisValue, context, evaluateNode) {
147
168
  const scope = await createClosureScope(node, args, thisValue, context, evaluateNode);
148
169
  const result = await evaluateNode(node.body, {
149
170
  ...context,
171
+ functionBody: node.body.type === "BlockStatement" ? node.body : undefined,
150
172
  scope
151
173
  });
152
174
  if (result.kind === "error") {
@@ -168,16 +190,32 @@ async function createClosureScope(node, args, thisValue, context, evaluateNode)
168
190
  scope.declare("arguments", "let", createSandboxArguments(args));
169
191
  }
170
192
  await bindParameters(node.params, args, scope, context, evaluateNode);
171
- hoistVarDeclarations(node.body, scope);
172
- return scope;
193
+ const bodyScope = node.params.some(containsParameterExpression)
194
+ ? scope.child({}, { functionBoundary: true })
195
+ : scope;
196
+ hoistVarDeclarations(node.body, bodyScope);
197
+ if (bodyScope !== scope) {
198
+ for (const declaration of hoistedVarDeclarations([node.body])) {
199
+ for (const declarator of declaration.declarations) {
200
+ for (const identifier of boundIdentifiers(declarator.id)) {
201
+ if (!scope.hasOwnBinding(identifier.name))
202
+ continue;
203
+ const parameter = scope.lookup(identifier.name);
204
+ if (parameter.found)
205
+ bodyScope.assign(identifier.name, parameter.value);
206
+ }
207
+ }
208
+ }
209
+ }
210
+ return bodyScope;
173
211
  }
174
212
  function isBlockBody(body) {
175
213
  return body.type === "BlockStatement";
176
214
  }
177
215
  async function bindParameters(params, args, scope, context, evaluateNode) {
178
216
  for (const param of params) {
179
- for (const name of getParameterBindingNames(param)) {
180
- scope.predeclare(name, "let");
217
+ for (const identifier of boundIdentifiers(param)) {
218
+ scope.predeclare(identifier.name, "var");
181
219
  }
182
220
  }
183
221
  for (let index = 0; index < params.length; index += 1) {
@@ -185,7 +223,7 @@ async function bindParameters(params, args, scope, context, evaluateNode) {
185
223
  if (param.type === "RestElement") {
186
224
  const rest = args.slice(index);
187
225
  context.budget.allocateArrayLength(rest.length);
188
- const binding = await bindPattern(param, rest, { kind: "let" }, scope, {
226
+ const binding = await bindPattern(param, rest, { kind: "var", initialize: true }, scope, {
189
227
  evaluate: (defaultNode) => evaluateNode(defaultNode, { ...context, scope })
190
228
  });
191
229
  if (!binding.ok) {
@@ -198,7 +236,7 @@ async function bindParameters(params, args, scope, context, evaluateNode) {
198
236
  }
199
237
  return;
200
238
  }
201
- const binding = await bindPattern(param, args[index], { kind: "let" }, scope, {
239
+ const binding = await bindPattern(param, args[index], { kind: "var", initialize: true }, scope, {
202
240
  evaluate: (defaultNode) => evaluateNode(defaultNode, { ...context, scope })
203
241
  });
204
242
  if (!binding.ok) {
@@ -211,27 +249,12 @@ async function bindParameters(params, args, scope, context, evaluateNode) {
211
249
  }
212
250
  }
213
251
  }
214
- function getParameterBindingNames(pattern) {
215
- switch (pattern.type) {
216
- case "Identifier":
217
- return [pattern.name];
218
- case "MemberExpression":
219
- return [];
220
- case "AssignmentPattern":
221
- return getParameterBindingNames(pattern.left);
222
- case "RestElement":
223
- return getParameterBindingNames(pattern.argument);
224
- case "ArrayPattern":
225
- return pattern.elements.flatMap((element) => element === null ? [] : getParameterBindingNames(element));
226
- case "ObjectPattern":
227
- return pattern.properties.flatMap((property) => property.type === "RestElement"
228
- ? getParameterBindingNames(property)
229
- : getParameterBindingNames(property.value));
230
- }
231
- }
232
252
  export function normalizeClosureResult(result, budget) {
233
- if (isSandboxPromise(result)) {
253
+ if (isSandboxPromise(result) &&
254
+ (result.synchronousPrefix !== undefined || result.hostCall !== undefined)) {
234
255
  return result;
235
256
  }
236
- return createSandboxPromise(resolveSandboxValue(result, { budget }));
257
+ return createSandboxPromise(resolveSandboxValue(result, { budget }), {
258
+ ...(isSandboxPromise(result) ? { synchronousPrefix: Promise.resolve() } : {})
259
+ });
237
260
  }
@@ -1,3 +1,8 @@
1
- import { type SandboxValue } from "./values.js";
1
+ import { type SandboxCallContext, type SandboxClosure, type SandboxPromise, type SandboxValue } from "./values.js";
2
+ import type { Budget } from "./budget.js";
3
+ export declare function withCancellationSignal<T>(signal: AbortSignal | undefined, task: () => T): T;
4
+ export declare function invokeCancelableClosure(closure: SandboxClosure, operation: SandboxClosure["call"], args: readonly SandboxValue[], context?: SandboxCallContext, construct?: boolean): SandboxValue | Promise<SandboxValue>;
2
5
  export declare function wrapCancelableBindings(bindings: Record<string, SandboxValue>, signal?: AbortSignal): Record<string, SandboxValue>;
3
- export declare function awaitSandboxValue(value: SandboxValue, signal?: AbortSignal): Promise<SandboxValue>;
6
+ export declare function registerPromiseCancellation(promise: SandboxPromise): void;
7
+ export declare function readPromiseCancellation(promise: SandboxPromise, original: Promise<SandboxValue>): Promise<SandboxValue>;
8
+ export declare function awaitSandboxValue(value: SandboxValue, signal?: AbortSignal, budget?: Budget): Promise<SandboxValue>;
@@ -1,90 +1,139 @@
1
- import { createSandboxClosure, createSandboxPromise, isSandboxClosure, isSandboxPromise } from "./values.js";
2
- import { observeSandboxPromise } from "./promise-tracker.js";
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { createSandboxPromise, isSandboxClosure, isSandboxMap, isSandboxPromise, isSandboxSet } from "./values.js";
3
+ import { assertPromiseExecutionAllowed, interruptOnFatalPromiseRejection, onFatalPromiseRejection, observeSandboxPromise, trackSandboxPromise } from "./promise-tracker.js";
3
4
  import { replaceErrorStack } from "../error/shape.js";
4
- import { resolveSandboxValue } from "./promise.js";
5
- const cancellationSignals = new WeakMap();
6
- export function wrapCancelableBindings(bindings, signal) {
7
- if (signal === undefined) {
8
- return bindings;
9
- }
10
- return wrapCancelableValue(bindings, signal, new WeakMap());
5
+ import { consumeSettledHostCall, resolveSandboxValue } from "./promise.js";
6
+ const activeCancellation = new AsyncLocalStorage();
7
+ const sandboxPromises = new WeakSet();
8
+ const cancelableOutcomes = new WeakMap();
9
+ export function withCancellationSignal(signal, task) {
10
+ return activeCancellation.run({ signal, host: false }, task);
11
11
  }
12
- function wrapCancelableValue(value, signal, seen) {
13
- if (typeof value !== "object" || value === null) {
14
- return value;
12
+ export function invokeCancelableClosure(closure, operation, args, context, construct = false) {
13
+ assertPromiseExecutionAllowed();
14
+ const active = activeCancellation.getStore();
15
+ if (active === undefined)
16
+ return operation(args, context);
17
+ if (closure.sandbox === true) {
18
+ return activeCancellation.run({ signal: active.signal, host: false }, () => operation(args, context));
15
19
  }
16
- const existing = seen.get(value);
17
- if (existing !== undefined) {
18
- return existing;
20
+ const signal = active.signal;
21
+ const managed = signal !== undefined && closure.cancellationSignal === signal;
22
+ if (signal?.aborted && !managed) {
23
+ if (!construct && closure.async === true)
24
+ return createRejectedSandboxPromise(readAbortReason(signal));
25
+ throw readAbortReason(signal);
19
26
  }
20
- if (isSandboxClosure(value)) {
21
- const wrapped = createSandboxClosure({
22
- ...(value.async === true ? { async: true } : {}),
23
- call: (args, context) => {
24
- if (signal.aborted) {
25
- if (value.async === true) {
26
- return createRejectedSandboxPromise(readAbortReason(signal));
27
- }
28
- throw readAbortReason(signal);
29
- }
30
- return wrapCancelableResult(value.call(args, context), signal, seen);
31
- },
32
- name: value.name
33
- });
34
- seen.set(value, wrapped);
35
- return wrapped;
27
+ return activeCancellation.run({ signal, host: true }, () => {
28
+ const result = operation(args, context);
29
+ if (signal === undefined || managed)
30
+ return result;
31
+ if (isPromiseLike(result))
32
+ return awaitWithSignal(result, signal).then((value) => {
33
+ registerCancelablePromises(value, signal);
34
+ return value;
35
+ });
36
+ registerCancelablePromises(result, signal);
37
+ return result;
38
+ });
39
+ }
40
+ export function wrapCancelableBindings(bindings, signal) {
41
+ if (signal !== undefined)
42
+ registerCancelablePromises(bindings, signal);
43
+ return bindings;
44
+ }
45
+ export function registerPromiseCancellation(promise) {
46
+ const active = activeCancellation.getStore();
47
+ if (active?.host === false)
48
+ sandboxPromises.add(promise);
49
+ else if (active?.signal !== undefined)
50
+ managePromiseCancellation(promise, active.signal);
51
+ }
52
+ export function readPromiseCancellation(promise, original) {
53
+ const signal = activeCancellation.getStore()?.signal;
54
+ return signal === undefined
55
+ ? original
56
+ : (cancelableOutcomes.get(promise)?.get(signal) ?? original);
57
+ }
58
+ function managePromiseCancellation(promise, signal) {
59
+ let outcomes = cancelableOutcomes.get(promise);
60
+ if (outcomes?.has(signal))
61
+ return;
62
+ if (outcomes === undefined) {
63
+ outcomes = new WeakMap();
64
+ cancelableOutcomes.set(promise, outcomes);
65
+ }
66
+ const outcome = awaitWithSignal(promise.promise, signal);
67
+ outcomes.set(signal, outcome);
68
+ trackSandboxPromise(promise, outcome);
69
+ }
70
+ function registerCancelablePromises(value, signal) {
71
+ const seen = new WeakSet();
72
+ const pending = [value];
73
+ while (pending.length > 0) {
74
+ const current = pending.pop();
75
+ if (typeof current !== "object" || current === null || seen.has(current))
76
+ continue;
77
+ seen.add(current);
78
+ if (isSandboxPromise(current)) {
79
+ if (!sandboxPromises.has(current))
80
+ managePromiseCancellation(current, signal);
81
+ continue;
82
+ }
83
+ if (isSandboxClosure(current)) {
84
+ if (current.properties !== undefined)
85
+ pending.push(current.properties);
86
+ }
87
+ else if (isSandboxMap(current)) {
88
+ for (const [key, entry] of current.entries)
89
+ pending.push(key, entry);
90
+ }
91
+ else if (isSandboxSet(current)) {
92
+ for (const entry of current.values)
93
+ pending.push(entry);
94
+ }
95
+ else {
96
+ for (const descriptor of Object.values(Object.getOwnPropertyDescriptors(current))) {
97
+ if ("value" in descriptor)
98
+ pending.push(descriptor.value);
99
+ }
100
+ }
36
101
  }
102
+ }
103
+ export function awaitSandboxValue(value, signal, budget) {
104
+ let resolved;
37
105
  if (isSandboxPromise(value)) {
38
- // The wrapper delivers this promise's rejection to the sandbox in its place, so
39
- // the sandbox never awaits the original and it would otherwise read as unhandled.
40
106
  observeSandboxPromise(value);
41
- const wrapped = createSandboxPromise(wrapCancelablePromise(value.promise, signal, seen), {
42
- trackReplay: false,
43
- ...(value.synchronousPrefix === undefined
44
- ? {}
45
- : { synchronousPrefix: value.synchronousPrefix }),
46
- ...(value.hostCall === undefined ? {} : { hostCall: value.hostCall }),
47
- ...(value.hostCallJournal === undefined ? {} : { hostCallJournal: value.hostCallJournal }),
48
- ...(value.span === undefined ? {} : { span: value.span })
107
+ const outcome = signal === undefined
108
+ ? value.promise
109
+ : (cancelableOutcomes.get(value)?.get(signal) ?? value.promise);
110
+ resolved = new Promise((resolve, reject) => {
111
+ const detach = onFatalPromiseRejection(reject);
112
+ const complete = (state, result) => {
113
+ detach?.();
114
+ try {
115
+ consumeSettledHostCall(value);
116
+ if (state === "fulfilled")
117
+ resolve(result);
118
+ else
119
+ reject(result);
120
+ }
121
+ catch (error) {
122
+ reject(error);
123
+ }
124
+ };
125
+ outcome.then((result) => complete("fulfilled", result), (reason) => complete("rejected", reason));
49
126
  });
50
- seen.set(value, wrapped);
51
- cancellationSignals.set(wrapped, signal);
52
- return wrapped;
53
- }
54
- if (Array.isArray(value)) {
55
- const wrapped = new Array(value.length);
56
- seen.set(value, wrapped);
57
- for (let index = 0; index < value.length; index += 1) {
58
- wrapped[index] = wrapCancelableValue(value[index], signal, seen);
59
- }
60
- return wrapped;
61
127
  }
62
- const wrapped = Object.create(Object.getPrototypeOf(value));
63
- seen.set(value, wrapped);
64
- for (const [key, entry] of Object.entries(value)) {
65
- wrapped[key] = wrapCancelableValue(entry, signal, seen);
128
+ else {
129
+ resolved = resolveSandboxValue(value, { budget });
66
130
  }
67
- return wrapped;
68
- }
69
- function wrapCancelableResult(result, signal, seen) {
70
- if (!isPromiseLike(result)) {
71
- return wrapCancelableValue(result, signal, seen);
72
- }
73
- return wrapCancelablePromise(result, signal, seen);
74
- }
75
- function wrapCancelablePromise(promise, signal, seen) {
76
- return awaitWithSignal(promise, signal).then((value) => wrapCancelableValue(value, signal, seen), (reason) => {
77
- if (signal.aborted && reason === signal.reason)
78
- throw reason;
79
- throw wrapCancelableUnknown(reason, signal, seen);
80
- });
81
- }
82
- export function awaitSandboxValue(value, signal) {
83
- const resolved = resolveSandboxValue(value);
84
- return isSandboxPromise(value) &&
85
- (value.synchronousPrefix !== undefined || cancellationSignals.get(value) === signal)
131
+ const pending = isSandboxPromise(value) &&
132
+ (value.synchronousPrefix !== undefined ||
133
+ (signal !== undefined && cancelableOutcomes.get(value)?.has(signal)))
86
134
  ? resolved
87
135
  : awaitWithSignal(resolved, signal);
136
+ return isSandboxPromise(value) ? pending : interruptOnFatalPromiseRejection(pending);
88
137
  }
89
138
  function awaitWithSignal(promise, signal) {
90
139
  if (signal === undefined)
@@ -125,22 +174,6 @@ function awaitWithSignal(promise, signal) {
125
174
  });
126
175
  });
127
176
  }
128
- function wrapCancelableUnknown(value, signal, seen) {
129
- if (value === null ||
130
- value === undefined ||
131
- typeof value === "string" ||
132
- typeof value === "number" ||
133
- typeof value === "boolean") {
134
- return value;
135
- }
136
- if (isSandboxClosure(value) ||
137
- isSandboxPromise(value) ||
138
- Array.isArray(value) ||
139
- (typeof value === "object" && value !== null)) {
140
- return wrapCancelableValue(value, signal, seen);
141
- }
142
- return value;
143
- }
144
177
  function readAbortReason(signal) {
145
178
  return signal.reason ?? createAbortError();
146
179
  }
@@ -161,5 +194,8 @@ function createRejectedSandboxPromise(reason) {
161
194
  return createSandboxPromise(promise);
162
195
  }
163
196
  function isPromiseLike(value) {
164
- return typeof value === "object" && value !== null && "then" in value;
197
+ return (typeof value === "object" &&
198
+ value !== null &&
199
+ "then" in value &&
200
+ typeof value.then === "function");
165
201
  }
@@ -1,6 +1,7 @@
1
1
  import { attachErrorSpan, attachWrappedErrorCause, describeThrownValue, formatErrorStack, readErrorCause, readErrorSpan } from "../error/shape.js";
2
2
  import { SandboxError } from "./budget.js";
3
3
  import { HostCallResumabilityError } from "./host-call.js";
4
+ import { withFatalPromiseCleanup } from "./promise-tracker.js";
4
5
  import { deepCopyToSandbox } from "./values.js";
5
6
  const capturedExceptionBrand = Symbol("CapturedException");
6
7
  const sandboxErrorBrand = Symbol("SandboxError");
@@ -50,9 +51,12 @@ export async function evaluateTryStatement(node, context, evaluateNode) {
50
51
  if (node.finalizer === undefined || tryOrCatchResult.kind === "error") {
51
52
  return tryOrCatchResult;
52
53
  }
53
- const finalizerResult = fatalBudgetError?.budget === "deadline"
54
- ? await evaluateWithoutDeadlineChecks(context, () => evaluateBlockCompletion(node.finalizer, context, evaluateNode))
55
- : await evaluateBlockCompletion(node.finalizer, context, evaluateNode);
54
+ const evaluateFinalizer = () => fatalBudgetError?.budget === "deadline"
55
+ ? evaluateWithoutDeadlineChecks(context, () => evaluateBlockCompletion(node.finalizer, context, evaluateNode))
56
+ : evaluateBlockCompletion(node.finalizer, context, evaluateNode);
57
+ const finalizerResult = await (fatalBudgetError === undefined
58
+ ? evaluateFinalizer()
59
+ : withFatalPromiseCleanup(evaluateFinalizer));
56
60
  if (fatalBudgetError !== undefined) {
57
61
  throw fatalBudgetError;
58
62
  }
@@ -4,6 +4,7 @@ const mapConstructors = new WeakSet();
4
4
  const setConstructors = new WeakSet();
5
5
  export function createCollectionGlobals(options) {
6
6
  const mapConstructor = createSandboxClosure({
7
+ sandbox: true,
7
8
  call: () => {
8
9
  throw new TypeError("Constructor Map requires 'new'.");
9
10
  },
@@ -17,6 +18,7 @@ export function createCollectionGlobals(options) {
17
18
  name: "Map"
18
19
  });
19
20
  const setConstructor = createSandboxClosure({
21
+ sandbox: true,
20
22
  call: () => {
21
23
  throw new TypeError("Constructor Set requires 'new'.");
22
24
  },
@@ -5,10 +5,12 @@ export function createConsoleJsonGlobals(options) {
5
5
  return {
6
6
  JSON: {
7
7
  parse: createSandboxClosure({
8
+ sandbox: true,
8
9
  call: async ([text]) => parseJson(text, options.budget),
9
10
  name: "parse"
10
11
  }),
11
12
  stringify: createSandboxClosure({
13
+ sandbox: true,
12
14
  call: async ([value, replacer, indent]) => stringifyJson(value, replacer, indent, options.budget),
13
15
  name: "stringify"
14
16
  })
@@ -16,6 +18,7 @@ export function createConsoleJsonGlobals(options) {
16
18
  console: options.hostCalls === undefined
17
19
  ? {
18
20
  error: createSandboxClosure({
21
+ sandbox: true,
19
22
  call: async (args) => {
20
23
  sink.error(...args.map((value) => deepCopyFromSandbox(value)));
21
24
  return undefined;
@@ -23,6 +26,7 @@ export function createConsoleJsonGlobals(options) {
23
26
  name: "error"
24
27
  }),
25
28
  log: createSandboxClosure({
29
+ sandbox: true,
26
30
  call: async (args) => {
27
31
  sink.log(...args.map((value) => deepCopyFromSandbox(value)));
28
32
  return undefined;
@@ -27,11 +27,7 @@ export function isSandboxErrorConstructor(value) {
27
27
  }
28
28
  function createErrorConstructor(name, budget) {
29
29
  const call = (args, context) => createSubsetError(name, args, context?.stack ?? [], budget);
30
- const closure = createSandboxClosure({
31
- call,
32
- construct: call,
33
- name
34
- });
30
+ const closure = createSandboxClosure({ sandbox: true, call, construct: call, name });
35
31
  errorConstructorNames.set(closure, name);
36
32
  return closure;
37
33
  }
@@ -46,13 +46,11 @@ export function createMathGlobals(options = {}) {
46
46
  PI: Math.PI,
47
47
  SQRT1_2: Math.SQRT1_2,
48
48
  SQRT2: Math.SQRT2,
49
- random: createSandboxClosure({
50
- call: () => random(),
51
- name: "random"
52
- })
49
+ random: createSandboxClosure({ sandbox: true, call: () => random(), name: "random" })
53
50
  };
54
51
  for (const [name, method] of Object.entries(mathMethods)) {
55
52
  mathObject[name] = createSandboxClosure({
53
+ sandbox: true,
56
54
  call: (args) => Reflect.apply(method, Math, args),
57
55
  name
58
56
  });
@@ -3,22 +3,27 @@ import { allocateProducedSandboxValue, cloneSandboxValue, createSandboxClosure,
3
3
  export function createMiscGlobals(options) {
4
4
  return {
5
5
  structuredClone: createSandboxClosure({
6
+ sandbox: true,
6
7
  call: ([value]) => structuredCloneSandboxValue(value, options.budget),
7
8
  name: "structuredClone"
8
9
  }),
9
10
  parseInt: createSandboxClosure({
11
+ sandbox: true,
10
12
  call: (args) => Reflect.apply(globalThis.parseInt, globalThis, [...args]),
11
13
  name: "parseInt"
12
14
  }),
13
15
  parseFloat: createSandboxClosure({
16
+ sandbox: true,
14
17
  call: (args) => Reflect.apply(globalThis.parseFloat, globalThis, [...args]),
15
18
  name: "parseFloat"
16
19
  }),
17
20
  isNaN: createSandboxClosure({
21
+ sandbox: true,
18
22
  call: ([value]) => globalThis.isNaN(value),
19
23
  name: "isNaN"
20
24
  }),
21
25
  isFinite: createSandboxClosure({
26
+ sandbox: true,
22
27
  call: ([value]) => globalThis.isFinite(value),
23
28
  name: "isFinite"
24
29
  })