poe-code 13.0.2 → 13.0.4
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 +14764 -13923
- 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-RIQRHKCB.js → chunk-WGS5B4OT.js} +14518 -13706
- package/packages/safe-js/dist/chunks/chunk-WGS5B4OT.js.map +7 -0
- package/packages/safe-js/dist/chunks/{chunk-JP474LP7.js → chunk-ZGWWEL43.js} +2 -2
- package/packages/safe-js/dist/cli.js +2 -2
- package/packages/safe-js/dist/core.js +1 -1
- package/packages/safe-js/dist/index.d.ts +11 -3
- package/packages/safe-js/dist/index.js +14 -6
- package/packages/safe-js/dist/index.js.map +4 -4
- package/packages/safe-js/dist/interp/async.d.ts +2 -0
- package/packages/safe-js/dist/interp/async.js +31 -15
- package/packages/safe-js/dist/interp/budget.d.ts +29 -0
- package/packages/safe-js/dist/interp/budget.js +143 -0
- package/packages/safe-js/dist/interp/globals/console-json.d.ts +2 -1
- package/packages/safe-js/dist/interp/globals/console-json.js +24 -6
- package/packages/safe-js/dist/interp/globals/regex.d.ts +2 -1
- package/packages/safe-js/dist/interp/globals/regex.js +24 -3
- package/packages/safe-js/dist/interp/host-bridge.d.ts +2 -1
- package/packages/safe-js/dist/interp/host-bridge.js +98 -55
- package/packages/safe-js/dist/interp/host-call.d.ts +3 -2
- package/packages/safe-js/dist/interp/host-call.js +91 -52
- package/packages/safe-js/dist/interp/interpreter.d.ts +4 -1
- package/packages/safe-js/dist/interp/interpreter.js +115 -79
- package/packages/safe-js/dist/interp/methods/array.js +136 -71
- package/packages/safe-js/dist/interp/methods/string.d.ts +2 -1
- package/packages/safe-js/dist/interp/methods/string.js +111 -100
- package/packages/safe-js/dist/interp/regex/compile-guard.d.ts +33 -0
- package/packages/safe-js/dist/interp/regex/compile-guard.js +179 -0
- package/packages/safe-js/dist/interp/regex/parse.d.ts +2 -1
- package/packages/safe-js/dist/interp/regex/parse.js +87 -16
- package/packages/safe-js/dist/interp/values.d.ts +7 -2
- package/packages/safe-js/dist/interp/values.js +91 -9
- package/packages/safe-js/dist/modules/registry.d.ts +2 -1
- package/packages/safe-js/dist/modules/registry.js +1 -0
- package/packages/safe-js/dist/parse/hash.d.ts +2 -1
- package/packages/safe-js/dist/parse/hash.js +7 -4
- package/packages/safe-js/dist/parse/parser.d.ts +5 -3
- package/packages/safe-js/dist/parse/parser.js +53 -27
- package/packages/safe-js/dist/parse/tokenizer.d.ts +2 -0
- package/packages/safe-js/dist/parse/tokenizer.js +48 -26
- package/packages/safe-js/dist/parse.d.ts +3 -1
- package/packages/safe-js/dist/parse.js +3 -1
- package/packages/safe-js/dist/restore.d.ts +2 -1
- package/packages/safe-js/dist/restore.js +3 -3
- package/packages/safe-js/dist/run.js +289 -263
- package/packages/safe-js/dist/snapshot/migration.d.ts +3 -2
- package/packages/safe-js/dist/snapshot/migration.js +12 -5
- package/packages/safe-js/dist/snapshot/replay-data.d.ts +2 -1
- package/packages/safe-js/dist/snapshot/replay-data.js +172 -162
- package/packages/safe-js/dist/snapshot/replay-inputs.d.ts +2 -1
- package/packages/safe-js/dist/snapshot/replay-inputs.js +18 -11
- package/packages/safe-js/dist/snapshot/restore.d.ts +2 -2
- package/packages/safe-js/dist/snapshot/restore.js +142 -113
- package/packages/safe-js/dist/chunks/chunk-RIQRHKCB.js.map +0 -7
- /package/packages/safe-js/dist/chunks/{chunk-JP474LP7.js.map → chunk-ZGWWEL43.js.map} +0 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { REGEX_COMPILE_LIMITS, SandboxError } from "../budget.js";
|
|
2
|
+
const compiledData = new WeakMap();
|
|
3
|
+
export class CompileScope {
|
|
4
|
+
owner;
|
|
5
|
+
parent;
|
|
6
|
+
tickets = new Set();
|
|
7
|
+
closed = false;
|
|
8
|
+
constructor(owner, parent) {
|
|
9
|
+
this.owner = owner;
|
|
10
|
+
this.parent = parent;
|
|
11
|
+
}
|
|
12
|
+
forward(tickets, parent) {
|
|
13
|
+
if (parent === this)
|
|
14
|
+
return;
|
|
15
|
+
if (this.owner !== parent.owner)
|
|
16
|
+
throw new SandboxError("reentry");
|
|
17
|
+
for (const ticket of tickets) {
|
|
18
|
+
if (!this.tickets.delete(ticket))
|
|
19
|
+
continue;
|
|
20
|
+
if (ticket.owner.budget.compileTicketUsage(ticket) > 0)
|
|
21
|
+
parent.tickets.add(ticket);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
dispose() {
|
|
25
|
+
for (const ticket of this.tickets)
|
|
26
|
+
ticket.owner.budget.discardCompileTicket(ticket);
|
|
27
|
+
this.tickets.clear();
|
|
28
|
+
this.closed = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class RegexCompileGuard {
|
|
32
|
+
scope;
|
|
33
|
+
allocations = 0;
|
|
34
|
+
depth = 0;
|
|
35
|
+
ticket;
|
|
36
|
+
retained = false;
|
|
37
|
+
constructor(scope) {
|
|
38
|
+
this.scope = scope;
|
|
39
|
+
if (scope?.owner !== undefined) {
|
|
40
|
+
this.ticket = scope.owner.budget.createCompileTicket(scope.owner);
|
|
41
|
+
scope.tickets.add(this.ticket);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
checkLength(length, flags = false) {
|
|
45
|
+
const hard = flags ? REGEX_COMPILE_LIMITS.flagsLength : REGEX_COMPILE_LIMITS.sourceLength;
|
|
46
|
+
const limit = Math.min(hard, this.scope?.owner?.budget.limits.stringLength ?? hard);
|
|
47
|
+
if (length > limit)
|
|
48
|
+
throw new SandboxError({ budget: "stringLength", current: length, limit });
|
|
49
|
+
}
|
|
50
|
+
work(units) {
|
|
51
|
+
const budget = this.scope?.owner?.budget;
|
|
52
|
+
if (budget === undefined)
|
|
53
|
+
return;
|
|
54
|
+
for (let index = 0; index < units; index += 1)
|
|
55
|
+
budget.visitNode();
|
|
56
|
+
}
|
|
57
|
+
allocate(units) {
|
|
58
|
+
const next = this.allocations + units;
|
|
59
|
+
if (next > REGEX_COMPILE_LIMITS.allocations) {
|
|
60
|
+
throw new SandboxError({
|
|
61
|
+
budget: "dataSize",
|
|
62
|
+
current: next,
|
|
63
|
+
limit: REGEX_COMPILE_LIMITS.allocations
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
this.work(units);
|
|
67
|
+
if (this.ticket !== undefined) {
|
|
68
|
+
this.ticket.owner.budget.resizeCompileTicket(this.ticket, next);
|
|
69
|
+
}
|
|
70
|
+
this.allocations = next;
|
|
71
|
+
}
|
|
72
|
+
array(length) {
|
|
73
|
+
this.scope?.owner?.budget.allocateArrayLength(length);
|
|
74
|
+
this.allocate(1);
|
|
75
|
+
}
|
|
76
|
+
enterGroup() {
|
|
77
|
+
const next = this.depth + 1;
|
|
78
|
+
if (next > REGEX_COMPILE_LIMITS.depth) {
|
|
79
|
+
throw new SandboxError({
|
|
80
|
+
budget: "dataDepth",
|
|
81
|
+
current: next,
|
|
82
|
+
limit: REGEX_COMPILE_LIMITS.depth
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
this.depth = next;
|
|
86
|
+
}
|
|
87
|
+
leaveGroup() {
|
|
88
|
+
this.depth -= 1;
|
|
89
|
+
}
|
|
90
|
+
preflight(source, flags) {
|
|
91
|
+
this.checkLength(source.length);
|
|
92
|
+
this.checkLength(flags.length, true);
|
|
93
|
+
let escaped = false;
|
|
94
|
+
let characterClass = false;
|
|
95
|
+
let nesting = 0;
|
|
96
|
+
for (let position = 0; position < source.length; position += 1) {
|
|
97
|
+
this.work(1);
|
|
98
|
+
const character = source[position];
|
|
99
|
+
if (escaped) {
|
|
100
|
+
escaped = false;
|
|
101
|
+
}
|
|
102
|
+
else if (character === "\\") {
|
|
103
|
+
escaped = true;
|
|
104
|
+
}
|
|
105
|
+
else if (character === "[" && !characterClass) {
|
|
106
|
+
characterClass = true;
|
|
107
|
+
}
|
|
108
|
+
else if (character === "]" && characterClass) {
|
|
109
|
+
characterClass = false;
|
|
110
|
+
}
|
|
111
|
+
else if (!characterClass && character === "(") {
|
|
112
|
+
nesting += 1;
|
|
113
|
+
this.enterGroup();
|
|
114
|
+
}
|
|
115
|
+
else if (!characterClass && character === ")" && nesting > 0) {
|
|
116
|
+
nesting -= 1;
|
|
117
|
+
this.leaveGroup();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
this.depth = 0;
|
|
121
|
+
this.work(flags.length);
|
|
122
|
+
}
|
|
123
|
+
retain(pattern, valueUnits = 0) {
|
|
124
|
+
const units = 10 + pattern.source.length + measureNode(pattern.body);
|
|
125
|
+
if (this.ticket !== undefined) {
|
|
126
|
+
this.ticket.owner.budget.resizeCompileTicket(this.ticket, units + valueUnits);
|
|
127
|
+
}
|
|
128
|
+
compiledData.set(pattern, { units, ticket: this.ticket });
|
|
129
|
+
this.retained = true;
|
|
130
|
+
}
|
|
131
|
+
retainScratch() {
|
|
132
|
+
this.retained = true;
|
|
133
|
+
}
|
|
134
|
+
close() {
|
|
135
|
+
if (this.retained || this.ticket === undefined)
|
|
136
|
+
return;
|
|
137
|
+
this.ticket.owner.budget.discardCompileTicket(this.ticket);
|
|
138
|
+
this.scope?.tickets.delete(this.ticket);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export function regexCompiledData(pattern) {
|
|
142
|
+
return (compiledData.get(pattern) ?? { units: 10 + pattern.source.length + measureNode(pattern.body) });
|
|
143
|
+
}
|
|
144
|
+
function measureNode(node) {
|
|
145
|
+
switch (node.type) {
|
|
146
|
+
case "empty":
|
|
147
|
+
case "dot":
|
|
148
|
+
return 2;
|
|
149
|
+
case "literal":
|
|
150
|
+
return 3 + node.value.length;
|
|
151
|
+
case "anchor":
|
|
152
|
+
case "wordBoundary":
|
|
153
|
+
return 3;
|
|
154
|
+
case "characterClass": {
|
|
155
|
+
let usage = 5 + node.items.length;
|
|
156
|
+
for (const item of node.items) {
|
|
157
|
+
usage +=
|
|
158
|
+
item.type === "character"
|
|
159
|
+
? 3 + item.value.length
|
|
160
|
+
: item.type === "range"
|
|
161
|
+
? 4 + item.from.length + item.to.length
|
|
162
|
+
: 4;
|
|
163
|
+
}
|
|
164
|
+
return usage;
|
|
165
|
+
}
|
|
166
|
+
case "sequence":
|
|
167
|
+
case "alternation": {
|
|
168
|
+
const children = node.type === "sequence" ? node.elements : node.alternatives;
|
|
169
|
+
let usage = 4 + children.length;
|
|
170
|
+
for (const child of children)
|
|
171
|
+
usage += measureNode(child);
|
|
172
|
+
return usage;
|
|
173
|
+
}
|
|
174
|
+
case "group":
|
|
175
|
+
return 5 + measureNode(node.body);
|
|
176
|
+
case "quantifier":
|
|
177
|
+
return (Object.hasOwn(node, "max") ? 6 : 5) + measureNode(node.body);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CompileScope } from "./compile-guard.js";
|
|
1
2
|
export type RegexFlags = {
|
|
2
3
|
global: boolean;
|
|
3
4
|
ignoreCase: boolean;
|
|
@@ -58,4 +59,4 @@ export type RegexPattern = {
|
|
|
58
59
|
captureCount: number;
|
|
59
60
|
body: RegexNode;
|
|
60
61
|
};
|
|
61
|
-
export declare function parseRegex(source: string, flags?: string): RegexPattern;
|
|
62
|
+
export declare function parseRegex(source: string, flags?: string, compilation?: CompileScope, valueUnits?: number): RegexPattern;
|
|
@@ -1,20 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { RegexCompileGuard } from "./compile-guard.js";
|
|
2
|
+
export function parseRegex(source, flags = "", compilation, valueUnits = 0) {
|
|
3
|
+
const guard = new RegexCompileGuard(compilation);
|
|
4
|
+
try {
|
|
5
|
+
guard.checkLength(source.length);
|
|
6
|
+
guard.checkLength(flags.length, true);
|
|
7
|
+
guard.allocate(5 + valueUnits);
|
|
8
|
+
const parsedFlags = parseFlags(flags, guard);
|
|
9
|
+
const parser = new RegexParser(source, guard);
|
|
10
|
+
const body = parser.parse();
|
|
11
|
+
guard.allocate(5 + source.length);
|
|
12
|
+
const pattern = { source, flags: parsedFlags, captureCount: parser.captureCount, body };
|
|
13
|
+
guard.retain(pattern, valueUnits);
|
|
14
|
+
return pattern;
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
guard.close();
|
|
18
|
+
}
|
|
11
19
|
}
|
|
12
20
|
class RegexParser {
|
|
13
21
|
source;
|
|
22
|
+
guard;
|
|
14
23
|
captureCount = 0;
|
|
15
|
-
|
|
16
|
-
constructor(source) {
|
|
24
|
+
cursor = 0;
|
|
25
|
+
constructor(source, guard) {
|
|
17
26
|
this.source = source;
|
|
27
|
+
this.guard = guard;
|
|
28
|
+
}
|
|
29
|
+
get position() {
|
|
30
|
+
return this.cursor;
|
|
31
|
+
}
|
|
32
|
+
set position(next) {
|
|
33
|
+
this.guard.work(Math.max(0, next - this.cursor));
|
|
34
|
+
this.cursor = next;
|
|
18
35
|
}
|
|
19
36
|
parse() {
|
|
20
37
|
const body = this.parseAlternation();
|
|
@@ -27,22 +44,34 @@ class RegexParser {
|
|
|
27
44
|
return body;
|
|
28
45
|
}
|
|
29
46
|
parseAlternation() {
|
|
47
|
+
this.guard.allocate(1);
|
|
48
|
+
this.guard.array(1);
|
|
30
49
|
const alternatives = [this.parseSequence()];
|
|
31
50
|
while (this.peek() === "|") {
|
|
32
51
|
this.position += 1;
|
|
52
|
+
this.guard.array(alternatives.length + 1);
|
|
33
53
|
alternatives.push(this.parseSequence());
|
|
34
54
|
}
|
|
35
|
-
|
|
55
|
+
if (alternatives.length === 1)
|
|
56
|
+
return alternatives[0];
|
|
57
|
+
this.guard.allocate(3);
|
|
58
|
+
return { type: "alternation", alternatives };
|
|
36
59
|
}
|
|
37
60
|
parseSequence() {
|
|
61
|
+
this.guard.allocate(1);
|
|
38
62
|
const elements = [];
|
|
39
63
|
while (!this.atEnd() && this.peek() !== ")" && this.peek() !== "|") {
|
|
64
|
+
this.guard.array(elements.length + 1);
|
|
40
65
|
elements.push(this.parseQuantifiedAtom());
|
|
41
66
|
}
|
|
42
67
|
if (elements.length === 0) {
|
|
68
|
+
this.guard.allocate(2);
|
|
43
69
|
return { type: "empty" };
|
|
44
70
|
}
|
|
45
|
-
|
|
71
|
+
if (elements.length === 1)
|
|
72
|
+
return elements[0];
|
|
73
|
+
this.guard.allocate(3);
|
|
74
|
+
return { type: "sequence", elements };
|
|
46
75
|
}
|
|
47
76
|
parseQuantifiedAtom() {
|
|
48
77
|
const quantifierStart = this.position;
|
|
@@ -62,16 +91,20 @@ class RegexParser {
|
|
|
62
91
|
if (!greedy) {
|
|
63
92
|
this.position += 1;
|
|
64
93
|
}
|
|
94
|
+
this.guard.allocate(Object.hasOwn(quantifier, "max") ? 6 : 5);
|
|
65
95
|
return { type: "quantifier", body, ...quantifier, greedy };
|
|
66
96
|
}
|
|
67
97
|
parseAtom() {
|
|
68
98
|
const character = this.take();
|
|
69
99
|
switch (character) {
|
|
70
100
|
case ".":
|
|
101
|
+
this.guard.allocate(2);
|
|
71
102
|
return { type: "dot" };
|
|
72
103
|
case "^":
|
|
104
|
+
this.guard.allocate(3);
|
|
73
105
|
return { type: "anchor", kind: "start" };
|
|
74
106
|
case "$":
|
|
107
|
+
this.guard.allocate(3);
|
|
75
108
|
return { type: "anchor", kind: "end" };
|
|
76
109
|
case "(":
|
|
77
110
|
return this.parseGroup(this.position - 1);
|
|
@@ -80,12 +113,15 @@ class RegexParser {
|
|
|
80
113
|
case "\\":
|
|
81
114
|
return this.parseEscape(false, this.position - 1);
|
|
82
115
|
default:
|
|
116
|
+
this.guard.allocate(3 + character.length);
|
|
83
117
|
return { type: "literal", value: character };
|
|
84
118
|
}
|
|
85
119
|
}
|
|
86
120
|
parseGroup(start) {
|
|
87
121
|
let capturing = true;
|
|
88
122
|
if (this.peek() === "?") {
|
|
123
|
+
this.guard.allocate(Math.min(3, this.source.length - this.position));
|
|
124
|
+
this.guard.work(Math.min(3, this.source.length - this.position));
|
|
89
125
|
const extension = this.source.slice(this.position, this.position + 3);
|
|
90
126
|
if (extension.startsWith("?:")) {
|
|
91
127
|
capturing = false;
|
|
@@ -104,12 +140,22 @@ class RegexParser {
|
|
|
104
140
|
this.fail("Unsupported group construct", start);
|
|
105
141
|
}
|
|
106
142
|
}
|
|
143
|
+
if (capturing)
|
|
144
|
+
this.guard.allocate(1);
|
|
107
145
|
const index = capturing ? ++this.captureCount : undefined;
|
|
108
|
-
|
|
146
|
+
this.guard.enterGroup();
|
|
147
|
+
let body;
|
|
148
|
+
try {
|
|
149
|
+
body = this.parseAlternation();
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
this.guard.leaveGroup();
|
|
153
|
+
}
|
|
109
154
|
if (this.peek() !== ")") {
|
|
110
155
|
this.fail("Unterminated group", start);
|
|
111
156
|
}
|
|
112
157
|
this.position += 1;
|
|
158
|
+
this.guard.allocate(5);
|
|
113
159
|
return { type: "group", capturing, index, body };
|
|
114
160
|
}
|
|
115
161
|
parseCharacterClass(start) {
|
|
@@ -117,12 +163,15 @@ class RegexParser {
|
|
|
117
163
|
if (negated) {
|
|
118
164
|
this.position += 1;
|
|
119
165
|
}
|
|
166
|
+
this.guard.allocate(1);
|
|
120
167
|
const items = [];
|
|
121
168
|
while (!this.atEnd()) {
|
|
122
169
|
if (this.peek() === "]") {
|
|
123
170
|
this.position += 1;
|
|
171
|
+
this.guard.allocate(4);
|
|
124
172
|
return { type: "characterClass", negated, items };
|
|
125
173
|
}
|
|
174
|
+
this.guard.array(items.length + 1);
|
|
126
175
|
const left = this.parseClassItem(start);
|
|
127
176
|
if (this.peek() === "-" && this.source[this.position + 1] !== "]") {
|
|
128
177
|
const rangePosition = this.position;
|
|
@@ -134,6 +183,7 @@ class RegexParser {
|
|
|
134
183
|
if (left.value.charCodeAt(0) > right.value.charCodeAt(0)) {
|
|
135
184
|
this.fail("Character class range is out of order", rangePosition);
|
|
136
185
|
}
|
|
186
|
+
this.guard.allocate(4 + left.value.length + right.value.length);
|
|
137
187
|
items.push({ type: "range", from: left.value, to: right.value });
|
|
138
188
|
}
|
|
139
189
|
else {
|
|
@@ -151,6 +201,7 @@ class RegexParser {
|
|
|
151
201
|
this.position += 1;
|
|
152
202
|
const escaped = this.parseEscape(true, escapeStart);
|
|
153
203
|
if (escaped.type === "literal") {
|
|
204
|
+
this.guard.allocate(3 + escaped.value.length);
|
|
154
205
|
return { type: "character", value: escaped.value };
|
|
155
206
|
}
|
|
156
207
|
if (escaped.type === "characterClass" && escaped.items.length === 1) {
|
|
@@ -158,6 +209,7 @@ class RegexParser {
|
|
|
158
209
|
}
|
|
159
210
|
this.fail("Unsupported character class escape", escapeStart);
|
|
160
211
|
}
|
|
212
|
+
this.guard.allocate(4);
|
|
161
213
|
return { type: "character", value: this.take() };
|
|
162
214
|
}
|
|
163
215
|
parseEscape(inCharacterClass, start) {
|
|
@@ -172,11 +224,14 @@ class RegexParser {
|
|
|
172
224
|
this.fail("Unicode property escapes are not supported", start);
|
|
173
225
|
}
|
|
174
226
|
if (escaped === "x") {
|
|
227
|
+
this.guard.allocate(4);
|
|
175
228
|
return { type: "literal", value: this.parseHexEscape(2, "hexadecimal", start) };
|
|
176
229
|
}
|
|
177
230
|
if (escaped === "u") {
|
|
231
|
+
this.guard.allocate(4);
|
|
178
232
|
return { type: "literal", value: this.parseHexEscape(4, "Unicode", start) };
|
|
179
233
|
}
|
|
234
|
+
this.guard.allocate(25);
|
|
180
235
|
const kinds = {
|
|
181
236
|
d: { kind: "digit", negated: false },
|
|
182
237
|
D: { kind: "digit", negated: true },
|
|
@@ -187,11 +242,15 @@ class RegexParser {
|
|
|
187
242
|
};
|
|
188
243
|
const kind = kinds[escaped];
|
|
189
244
|
if (kind !== undefined) {
|
|
245
|
+
this.guard.allocate(9);
|
|
246
|
+
this.guard.array(1);
|
|
190
247
|
return { type: "characterClass", negated: false, items: [{ type: "kind", ...kind }] };
|
|
191
248
|
}
|
|
192
249
|
if (!inCharacterClass && (escaped === "b" || escaped === "B")) {
|
|
250
|
+
this.guard.allocate(3);
|
|
193
251
|
return { type: "wordBoundary", negated: escaped === "B" };
|
|
194
252
|
}
|
|
253
|
+
this.guard.allocate(8);
|
|
195
254
|
const controls = {
|
|
196
255
|
b: "\b",
|
|
197
256
|
f: "\f",
|
|
@@ -201,10 +260,13 @@ class RegexParser {
|
|
|
201
260
|
v: "\v",
|
|
202
261
|
"0": "\0"
|
|
203
262
|
};
|
|
263
|
+
this.guard.allocate(4);
|
|
204
264
|
return { type: "literal", value: controls[escaped] ?? escaped };
|
|
205
265
|
}
|
|
206
266
|
parseHexEscape(length, name, start) {
|
|
207
267
|
const end = this.position + length;
|
|
268
|
+
this.guard.allocate(Math.min(length, this.source.length - this.position));
|
|
269
|
+
this.guard.work(Math.min(length, this.source.length - this.position));
|
|
208
270
|
const digits = this.source.slice(this.position, end);
|
|
209
271
|
if (digits.length !== length || !allHexDigits(digits)) {
|
|
210
272
|
this.fail(`Invalid ${name} escape`, start);
|
|
@@ -216,14 +278,17 @@ class RegexParser {
|
|
|
216
278
|
const character = this.peek();
|
|
217
279
|
if (character === "*") {
|
|
218
280
|
this.position += 1;
|
|
281
|
+
this.guard.allocate(2);
|
|
219
282
|
return { min: 0 };
|
|
220
283
|
}
|
|
221
284
|
if (character === "+") {
|
|
222
285
|
this.position += 1;
|
|
286
|
+
this.guard.allocate(2);
|
|
223
287
|
return { min: 1 };
|
|
224
288
|
}
|
|
225
289
|
if (character === "?") {
|
|
226
290
|
this.position += 1;
|
|
291
|
+
this.guard.allocate(3);
|
|
227
292
|
return { min: 0, max: 1 };
|
|
228
293
|
}
|
|
229
294
|
if (character !== "{") {
|
|
@@ -238,6 +303,7 @@ class RegexParser {
|
|
|
238
303
|
}
|
|
239
304
|
if (this.peek() === "}") {
|
|
240
305
|
this.position += 1;
|
|
306
|
+
this.guard.allocate(3);
|
|
241
307
|
return { min, max: min };
|
|
242
308
|
}
|
|
243
309
|
if (this.peek() !== ",") {
|
|
@@ -252,6 +318,7 @@ class RegexParser {
|
|
|
252
318
|
if (max !== undefined && min > max) {
|
|
253
319
|
this.fail("Quantifier range is out of order", start);
|
|
254
320
|
}
|
|
321
|
+
this.guard.allocate(3);
|
|
255
322
|
return { min, max };
|
|
256
323
|
}
|
|
257
324
|
parseDecimal() {
|
|
@@ -262,6 +329,8 @@ class RegexParser {
|
|
|
262
329
|
if (start === this.position) {
|
|
263
330
|
return undefined;
|
|
264
331
|
}
|
|
332
|
+
this.guard.allocate(this.position - start);
|
|
333
|
+
this.guard.work(this.position - start);
|
|
265
334
|
const value = Number(this.source.slice(start, this.position));
|
|
266
335
|
if (!Number.isSafeInteger(value)) {
|
|
267
336
|
this.fail("Quantifier is too large", start);
|
|
@@ -283,7 +352,8 @@ class RegexParser {
|
|
|
283
352
|
throw new SyntaxError(`${message} at position ${position}`);
|
|
284
353
|
}
|
|
285
354
|
}
|
|
286
|
-
function parseFlags(flags) {
|
|
355
|
+
function parseFlags(flags, guard) {
|
|
356
|
+
guard.allocate(10);
|
|
287
357
|
const parsed = {
|
|
288
358
|
global: false,
|
|
289
359
|
ignoreCase: false,
|
|
@@ -297,6 +367,7 @@ function parseFlags(flags) {
|
|
|
297
367
|
s: "dotAll"
|
|
298
368
|
};
|
|
299
369
|
for (let position = 0; position < flags.length; position += 1) {
|
|
370
|
+
guard.work(1);
|
|
300
371
|
const flag = flags[position];
|
|
301
372
|
const name = names[flag];
|
|
302
373
|
if (name === undefined) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Budget } from "./budget.js";
|
|
1
|
+
import type { Budget, CompileTicket } from "./budget.js";
|
|
2
|
+
import { CompileScope } from "./regex/compile-guard.js";
|
|
2
3
|
import type { GeneratorChannel } from "./generator.js";
|
|
3
4
|
import { type RegexPattern } from "./regex/parse.js";
|
|
4
5
|
export { createSandboxArguments, isSandboxArguments } from "./arguments.js";
|
|
@@ -35,6 +36,7 @@ export type SandboxRegex = {
|
|
|
35
36
|
readonly [sandboxRegexPattern]: RegexPattern;
|
|
36
37
|
};
|
|
37
38
|
export type SandboxCallContext = {
|
|
39
|
+
readonly compilation?: CompileScope;
|
|
38
40
|
readonly span?: {
|
|
39
41
|
readonly end: {
|
|
40
42
|
readonly column: number;
|
|
@@ -84,6 +86,7 @@ export type SandboxGenerator = {
|
|
|
84
86
|
};
|
|
85
87
|
type CopyFromSandboxOptions = {
|
|
86
88
|
wrapClosure?: (value: SandboxClosure) => unknown;
|
|
89
|
+
compilation?: CompileScope;
|
|
87
90
|
};
|
|
88
91
|
export declare function createSandboxClosure(input: {
|
|
89
92
|
async?: boolean;
|
|
@@ -110,7 +113,7 @@ export declare function createSandboxGenerator(channel: GeneratorChannel, metada
|
|
|
110
113
|
} | undefined): SandboxGenerator;
|
|
111
114
|
export declare function createSandboxMap(entries?: Iterable<readonly [SandboxValue, SandboxValue]>): SandboxMap;
|
|
112
115
|
export declare function createSandboxSet(values?: Iterable<SandboxValue>): SandboxSet;
|
|
113
|
-
export declare function createSandboxRegex(source: string, flags?: string, lastIndex?: number): SandboxRegex;
|
|
116
|
+
export declare function createSandboxRegex(source: string, flags?: string, lastIndex?: number, compilation?: CompileScope): SandboxRegex;
|
|
114
117
|
export declare function getSandboxRegexPattern(regex: SandboxRegex): RegexPattern;
|
|
115
118
|
export declare function isSandboxClosure(value: unknown): value is SandboxClosure;
|
|
116
119
|
export declare function isSandboxMap(value: unknown): value is SandboxMap;
|
|
@@ -124,7 +127,9 @@ export declare function allocateProducedSandboxValue(value: SandboxValue, budget
|
|
|
124
127
|
export declare function measureSandboxData(values: Iterable<unknown>, options?: {
|
|
125
128
|
ignoreClosures?: boolean;
|
|
126
129
|
ignoreClosureCaptures?: boolean;
|
|
130
|
+
compileTickets?: Set<CompileTicket>;
|
|
127
131
|
}): number;
|
|
132
|
+
export declare function reconcileCompiledValues(budget: Budget, values: Iterable<unknown>, compilation?: CompileScope, parent?: CompileScope, escaping?: Iterable<unknown>): void;
|
|
128
133
|
export declare function deepCopyFromSandbox(value: SandboxPromise, options?: CopyFromSandboxOptions): Promise<unknown>;
|
|
129
134
|
export declare function deepCopyFromSandbox(value: SandboxValue, options?: CopyFromSandboxOptions): unknown;
|
|
130
135
|
export declare function defineOwnDataProperty(target: object, key: string, value: unknown): void;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { bindOtelSpan, getBoundOtelSpan } from "../observability/otel.js";
|
|
2
|
+
import { types as nodeTypes } from "node:util";
|
|
3
|
+
import { CompileScope, RegexCompileGuard, regexCompiledData } from "./regex/compile-guard.js";
|
|
2
4
|
import { copyFloat32Storage, float32DataProperties, float32Storage, isFloat32Array } from "./float32.js";
|
|
3
5
|
import { SandboxError } from "./budget.js";
|
|
4
6
|
import { observeSandboxPromise, trackSandboxPromise } from "./promise-tracker.js";
|
|
@@ -142,11 +144,15 @@ export function createSandboxSet(values = []) {
|
|
|
142
144
|
});
|
|
143
145
|
return Object.freeze(set);
|
|
144
146
|
}
|
|
145
|
-
export function createSandboxRegex(source, flags = "", lastIndex = 0) {
|
|
147
|
+
export function createSandboxRegex(source, flags = "", lastIndex = 0, compilation) {
|
|
148
|
+
if (typeof source !== "string" || typeof flags !== "string") {
|
|
149
|
+
throw new TypeError("Invalid sandbox RegExp source or flags: expected own string data properties.");
|
|
150
|
+
}
|
|
151
|
+
const pattern = parseRegex(source, flags, compilation, 7 + source.length + flags.length);
|
|
146
152
|
const regex = { kind: "regex", source, flags, lastIndex };
|
|
147
153
|
Object.defineProperties(regex, {
|
|
148
154
|
[sandboxRegexBrand]: { value: true },
|
|
149
|
-
[sandboxRegexPattern]: { value:
|
|
155
|
+
[sandboxRegexPattern]: { value: pattern }
|
|
150
156
|
});
|
|
151
157
|
return Object.seal(regex);
|
|
152
158
|
}
|
|
@@ -254,7 +260,14 @@ export function measureSandboxData(values, options = {}) {
|
|
|
254
260
|
if (isSandboxPromise(value))
|
|
255
261
|
return;
|
|
256
262
|
if (isSandboxRegex(value)) {
|
|
257
|
-
|
|
263
|
+
const { source, flags } = captureRegexData(value);
|
|
264
|
+
const compiled = regexCompiledData(getSandboxRegexPattern(value));
|
|
265
|
+
const staged = compiled.ticket === undefined
|
|
266
|
+
? 0
|
|
267
|
+
: compiled.ticket.owner.budget.compileTicketUsage(compiled.ticket);
|
|
268
|
+
usage += Math.max(6 + source.length + flags.length + compiled.units, staged - 1);
|
|
269
|
+
if (compiled.ticket !== undefined)
|
|
270
|
+
options.compileTickets?.add(compiled.ticket);
|
|
258
271
|
return;
|
|
259
272
|
}
|
|
260
273
|
const entries = isSandboxArguments(value)
|
|
@@ -272,6 +285,43 @@ export function measureSandboxData(values, options = {}) {
|
|
|
272
285
|
visit(value);
|
|
273
286
|
return usage;
|
|
274
287
|
}
|
|
288
|
+
export function reconcileCompiledValues(budget, values, compilation, parent, escaping = []) {
|
|
289
|
+
while (parent?.closed)
|
|
290
|
+
parent = parent.parent;
|
|
291
|
+
const included = new Set();
|
|
292
|
+
const usage = measureSandboxData(values, { compileTickets: included });
|
|
293
|
+
const kept = new Set();
|
|
294
|
+
if (parent !== undefined)
|
|
295
|
+
measureSandboxData(escaping, { compileTickets: kept });
|
|
296
|
+
const transferred = new Set();
|
|
297
|
+
for (const ticket of included) {
|
|
298
|
+
if (!kept.has(ticket)) {
|
|
299
|
+
transferred.add(ticket);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const retained = budget.reconcileCompileData(usage, included, transferred, undefined, parent === undefined);
|
|
303
|
+
for (const ticket of retained)
|
|
304
|
+
compilation?.tickets.delete(ticket);
|
|
305
|
+
if (compilation !== undefined && parent !== undefined)
|
|
306
|
+
compilation.forward(included, parent);
|
|
307
|
+
}
|
|
308
|
+
function captureRegexData(value) {
|
|
309
|
+
const source = Object.getOwnPropertyDescriptor(value, "source");
|
|
310
|
+
const flags = Object.getOwnPropertyDescriptor(value, "flags");
|
|
311
|
+
if (source === undefined ||
|
|
312
|
+
!("value" in source) ||
|
|
313
|
+
typeof source.value !== "string" ||
|
|
314
|
+
flags === undefined ||
|
|
315
|
+
!("value" in flags) ||
|
|
316
|
+
typeof flags.value !== "string") {
|
|
317
|
+
throw new TypeError("Invalid sandbox RegExp source or flags: expected own string data properties.");
|
|
318
|
+
}
|
|
319
|
+
const cursor = Object.getOwnPropertyDescriptor(value, "lastIndex");
|
|
320
|
+
if (cursor === undefined || !("value" in cursor)) {
|
|
321
|
+
throw new TypeError("Invalid sandbox RegExp lastIndex: expected an own data property.");
|
|
322
|
+
}
|
|
323
|
+
return { source: source.value, flags: flags.value, lastIndex: cursor.value };
|
|
324
|
+
}
|
|
275
325
|
export function deepCopyFromSandbox(value, options = {}) {
|
|
276
326
|
return copyFromSandbox(value, {
|
|
277
327
|
seen: new WeakMap()
|
|
@@ -411,6 +461,27 @@ function copyFromSandbox(value, state, path = "<root>", options, depth = 0) {
|
|
|
411
461
|
if (isSandboxPrimitive(value)) {
|
|
412
462
|
return value;
|
|
413
463
|
}
|
|
464
|
+
if (nodeTypes.isProxy(value))
|
|
465
|
+
throw new TypeError("Unsupported proxy sandbox value.");
|
|
466
|
+
const regexBrand = Object.getOwnPropertyDescriptor(value, sandboxRegexBrand);
|
|
467
|
+
if (regexBrand !== undefined) {
|
|
468
|
+
if (!("value" in regexBrand) || regexBrand.value !== true) {
|
|
469
|
+
throw new TypeError("Invalid sandbox RegExp brand.");
|
|
470
|
+
}
|
|
471
|
+
const { source, flags, lastIndex } = captureRegexData(value);
|
|
472
|
+
const guard = new RegexCompileGuard(options.compilation);
|
|
473
|
+
try {
|
|
474
|
+
guard.preflight(source, flags);
|
|
475
|
+
guard.allocate(1 + source.length + flags.length);
|
|
476
|
+
const regex = new RegExp(source, flags);
|
|
477
|
+
regex.lastIndex = lastIndex;
|
|
478
|
+
guard.retainScratch();
|
|
479
|
+
return regex;
|
|
480
|
+
}
|
|
481
|
+
finally {
|
|
482
|
+
guard.close();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
414
485
|
if (isFloat32Array(value)) {
|
|
415
486
|
const existing = state.seen.get(value);
|
|
416
487
|
if (existing !== undefined)
|
|
@@ -441,17 +512,28 @@ function copyFromSandbox(value, state, path = "<root>", options, depth = 0) {
|
|
|
441
512
|
}
|
|
442
513
|
if (isSandboxPromise(value)) {
|
|
443
514
|
observeSandboxPromise(value);
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
515
|
+
const copySettlement = (settled) => {
|
|
516
|
+
const owner = options.compilation?.owner;
|
|
517
|
+
const operation = owner?.budget.acquireCompileOwner(false, owner);
|
|
518
|
+
const compilation = new CompileScope(operation?.owner);
|
|
519
|
+
try {
|
|
520
|
+
return copyFromSandbox(settled, { seen: new WeakMap() }, "<root>", {
|
|
521
|
+
...options,
|
|
522
|
+
compilation
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
finally {
|
|
526
|
+
compilation.dispose();
|
|
527
|
+
operation?.release();
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
return value.promise.then(copySettlement, (reason) => Promise.reject(reason instanceof SandboxError ? reason : copySettlement(reason)));
|
|
447
531
|
}
|
|
448
532
|
if (isSandboxGenerator(value)) {
|
|
449
533
|
throw new TypeError("Sandbox generators cannot cross into host values.");
|
|
450
534
|
}
|
|
451
535
|
if (isSandboxRegex(value)) {
|
|
452
|
-
|
|
453
|
-
regex.lastIndex = value.lastIndex;
|
|
454
|
-
return regex;
|
|
536
|
+
throw new TypeError("Invalid sandbox RegExp brand.");
|
|
455
537
|
}
|
|
456
538
|
if (isSandboxMap(value)) {
|
|
457
539
|
const existing = state.seen.get(value);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Budget } from "../interp/budget.js";
|
|
1
|
+
import type { Budget, CompileOwner } from "../interp/budget.js";
|
|
2
2
|
import type { HostCallJournal } from "../interp/host-call.js";
|
|
3
3
|
import { type CallerInjectedBinding } from "../interp/host-bridge.js";
|
|
4
4
|
import type { SandboxValue } from "../interp/values.js";
|
|
@@ -9,6 +9,7 @@ export declare function createUnknownModuleMessage(moduleName: string, moduleNam
|
|
|
9
9
|
export declare function createUnknownExportMessage(moduleName: string, exportName: string, availableExports: readonly string[]): string;
|
|
10
10
|
export declare function resolveModuleImports(module: Module, modules: ModuleRegistry | undefined, options: {
|
|
11
11
|
budget: Budget;
|
|
12
|
+
compileOwner?: CompileOwner;
|
|
12
13
|
hostCalls?: HostCallJournal;
|
|
13
14
|
signal?: AbortSignal;
|
|
14
15
|
allowMissing?: boolean;
|