cfsolve 0.1.1 → 0.1.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +10 -9
- data/UPSTREAM.md +6 -6
- data/lib/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
- data/lib/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
- data/lib/cfsolve/profiles/safari-26-ios.json +0 -8
- data/lib/cfsolve/version.rb +2 -2
- data/vendor/cfsolve/README.md +0 -35
- data/vendor/cfsolve/examples/build_profile.rs +17 -15
- data/vendor/cfsolve/examples/capture_session.rs +37 -0
- data/vendor/cfsolve/examples/check_answers.rs +66 -0
- data/vendor/cfsolve/examples/compare_session.rs +190 -0
- data/vendor/cfsolve/examples/stage2_snapshot.rs +4 -1
- data/vendor/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
- data/vendor/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
- data/vendor/cfsolve/profiles/safari-26-ios.json +0 -8
- data/vendor/cfsolve/scripts/capture_cssom.mjs +42 -0
- data/vendor/cfsolve/scripts/serve_cssom.py +111 -0
- data/vendor/cfsolve/src/capture/profile.rs +7 -18
- data/vendor/cfsolve/src/capture/recorded.rs +42 -7
- data/vendor/cfsolve/src/challenge/installed/audit.rs +131 -0
- data/vendor/cfsolve/src/challenge/installed.rs +122 -271
- data/vendor/cfsolve/src/challenge/payload.rs +107 -5
- data/vendor/cfsolve/src/challenge/state.rs +98 -16
- data/vendor/cfsolve/src/deobfuscate/encoded_strings.rs +3 -2
- data/vendor/cfsolve/src/deobfuscate/helper_objects.rs +17 -32
- data/vendor/cfsolve/src/deobfuscate/register_arrays.rs +18 -5
- data/vendor/cfsolve/src/embedding_page/extra_params.rs +0 -14
- data/vendor/cfsolve/src/embedding_page/page.rs +28 -2
- data/vendor/cfsolve/src/http/client.rs +11 -0
- data/vendor/cfsolve/src/js/ast.rs +12 -3
- data/vendor/cfsolve/src/js/objects.rs +9 -2
- data/vendor/cfsolve/src/listing/fold.rs +0 -83
- data/vendor/cfsolve/src/listing/query.rs +25 -244
- data/vendor/cfsolve/src/listing.rs +0 -1
- data/vendor/cfsolve/src/session/initial.rs +14 -5
- data/vendor/cfsolve/src/session/probes.rs +160 -125
- data/vendor/cfsolve/src/session/timing.rs +58 -53
- data/vendor/cfsolve/src/session/widget.rs +5 -6
- data/vendor/cfsolve/src/stage2/analysis/analyze.rs +50 -33
- data/vendor/cfsolve/src/stage2/analysis/discovery.rs +61 -66
- data/vendor/cfsolve/src/stage2/analysis/envelope.rs +31 -21
- data/vendor/cfsolve/src/stage2/analysis/recognize.rs +3 -5
- data/vendor/cfsolve/src/stage2/analysis.rs +0 -1
- data/vendor/cfsolve/src/stage2/audit_globals.rs +310 -0
- data/vendor/cfsolve/src/stage2/benchmark.rs +5 -8
- data/vendor/cfsolve/src/stage2/call_stack.rs +72 -16
- data/vendor/cfsolve/src/stage2/interaction/native.rs +2 -1
- data/vendor/cfsolve/src/stage2/interaction/plan.rs +0 -3
- data/vendor/cfsolve/src/stage2/payload/recover.rs +54 -23
- data/vendor/cfsolve/src/stage2/probes/ice.rs +11 -10
- data/vendor/cfsolve/src/stage2/probes/media.rs +47 -33
- data/vendor/cfsolve/src/stage2/probes/performance.rs +1 -0
- data/vendor/cfsolve/src/stage2/probes/rects.rs +12 -17
- data/vendor/cfsolve/src/stage2/probes/stack.rs +50 -103
- data/vendor/cfsolve/src/stage2/probes/websocket_echo.rs +17 -86
- data/vendor/cfsolve/src/stage2/probes.rs +0 -1
- data/vendor/cfsolve/src/stage2/records.rs +4 -13
- data/vendor/cfsolve/src/stage2/reference.json +1 -1
- data/vendor/cfsolve/src/stage2/reference.rs +98 -100
- data/vendor/cfsolve/src/stage2/registry.rs +41 -34
- data/vendor/cfsolve/src/stage2/replay.rs +182 -254
- data/vendor/cfsolve/src/stage2/stylesheets.rs +176 -0
- data/vendor/cfsolve/src/stage2/trace.rs +18 -0
- data/vendor/cfsolve/src/stage2/vocabulary.rs +111 -401
- data/vendor/cfsolve/src/stage2.rs +2 -1
- data/vendor/cfsolve/src/vm/disassemble.rs +39 -10
- data/vendor/cfsolve/src/vm/interpret/access.rs +5 -21
- data/vendor/cfsolve/src/vm/interpret/builtin.rs +6 -53
- data/vendor/cfsolve/src/vm/interpret/call.rs +86 -9
- data/vendor/cfsolve/src/vm/interpret/compile.rs +626 -0
- data/vendor/cfsolve/src/vm/interpret/copies.rs +232 -120
- data/vendor/cfsolve/src/vm/interpret/evaluate/tests.rs +96 -8
- data/vendor/cfsolve/src/vm/interpret/evaluate.rs +219 -92
- data/vendor/cfsolve/src/vm/interpret/explore.rs +155 -39
- data/vendor/cfsolve/src/vm/interpret/frozen.rs +171 -0
- data/vendor/cfsolve/src/vm/interpret/intrinsic.rs +350 -0
- data/vendor/cfsolve/src/vm/interpret/plan.rs +71 -44
- data/vendor/cfsolve/src/vm/interpret/run.rs +202 -13
- data/vendor/cfsolve/src/vm/interpret.rs +7 -2
- data/vendor/cfsolve/src/vm/machine.rs +6 -2
- data/vendor/cfsolve/src/vm/packed_names.rs +307 -0
- data/vendor/cfsolve/src/vm/session.rs +36 -14
- data/vendor/cfsolve/src/vm/value.rs +13 -0
- data/vendor/cfsolve/src/vm.rs +1 -0
- data/vendor/cfsolve/vendor/btls-sys/src/lib.rs +3 -0
- metadata +13 -6
- data/vendor/cfsolve/src/listing/normalize.rs +0 -264
- data/vendor/cfsolve/src/stage2/analysis/outputs.rs +0 -327
- data/vendor/cfsolve/src/stage2/probes/stylesheets.rs +0 -249
- data/vendor/cfsolve/src/stage2/reference-expanded.json +0 -42
- data/vendor/cfsolve/src/stage2/session_names.rs +0 -470
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
//! Compiles slot plans into closures. Compiled code takes the same steps as
|
|
2
|
+
//! [`super::evaluate`] takes over a plan, in the same order and through the same [`Run`]
|
|
3
|
+
//! methods, but resolves each node's kind and operands once, when the plan is compiled.
|
|
4
|
+
//!
|
|
5
|
+
//! A few common shapes run more directly: assignments to locals in statement position skip
|
|
6
|
+
//! the result they would discard, and arithmetic reads program bytes at local indexes, as in
|
|
7
|
+
//! `program[pc++]`, without building values.
|
|
8
|
+
|
|
9
|
+
use std::cell::RefCell;
|
|
10
|
+
use std::rc::Rc;
|
|
11
|
+
|
|
12
|
+
use swc_core::ecma::ast::{BinaryOp, UpdateOp};
|
|
13
|
+
|
|
14
|
+
use super::access::{self, Key};
|
|
15
|
+
use super::evaluate::{self, Locals, Location, MAXIMUM_ITERATIONS};
|
|
16
|
+
use super::plan::{Case, Declaration, Expression, Name, Property, Statement, Target};
|
|
17
|
+
use super::run::{Evaluation, Flow, Run, Stop};
|
|
18
|
+
use super::semantics;
|
|
19
|
+
use crate::vm::convert;
|
|
20
|
+
use crate::vm::value::Value;
|
|
21
|
+
|
|
22
|
+
/// Compiled code producing `T`.
|
|
23
|
+
type Compiled<'ast, T> = Box<
|
|
24
|
+
dyn for<'session> Fn(&mut Run<'ast, 'session>, &mut Locals<'ast>) -> Evaluation<'ast, T> + 'ast,
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
/// A compiled statement.
|
|
28
|
+
pub(super) type Code<'ast> = Compiled<'ast, Flow<'ast>>;
|
|
29
|
+
|
|
30
|
+
/// A compiled expression.
|
|
31
|
+
type Compute<'ast> = Compiled<'ast, Value<'ast>>;
|
|
32
|
+
|
|
33
|
+
/// A compiled operand of arithmetic, as [`Run::number`] evaluates it: a number, or the value
|
|
34
|
+
/// of a nonnumeric operand.
|
|
35
|
+
type Numeric<'ast> = Compiled<'ast, Result<f64, Value<'ast>>>;
|
|
36
|
+
|
|
37
|
+
/// Compiles a function body or block.
|
|
38
|
+
pub(super) fn statements<'ast>(statements: &[Statement<'ast>]) -> Vec<Code<'ast>> {
|
|
39
|
+
statements.iter().map(statement).collect()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Runs statements in order until one jumps, as `Run::execute_all` does.
|
|
43
|
+
pub(super) fn run_all<'ast>(
|
|
44
|
+
code: &[Code<'ast>],
|
|
45
|
+
run: &mut Run<'ast, '_>,
|
|
46
|
+
locals: &mut Locals<'ast>,
|
|
47
|
+
) -> Evaluation<'ast, Flow<'ast>> {
|
|
48
|
+
for statement in code {
|
|
49
|
+
let flow = statement(run, locals)?;
|
|
50
|
+
if !matches!(flow, Flow::Normal) {
|
|
51
|
+
return Ok(flow);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
Ok(Flow::Normal)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn statement<'ast>(statement: &Statement<'ast>) -> Code<'ast> {
|
|
58
|
+
match statement {
|
|
59
|
+
Statement::Switch(discriminant, cases) => switch(discriminant, cases),
|
|
60
|
+
Statement::Empty => code(|_, _| Ok(Flow::Normal)),
|
|
61
|
+
Statement::Expression(expression) => effect(expression),
|
|
62
|
+
Statement::Declare(declarations) => declare(declarations),
|
|
63
|
+
Statement::Try {
|
|
64
|
+
block,
|
|
65
|
+
parameter,
|
|
66
|
+
handler,
|
|
67
|
+
finalizer,
|
|
68
|
+
} => {
|
|
69
|
+
let block = statements(block);
|
|
70
|
+
let parameter = *parameter;
|
|
71
|
+
let handler = handler.as_deref().map(statements);
|
|
72
|
+
let finalizer = finalizer.as_deref().map(statements);
|
|
73
|
+
code(move |run, locals| {
|
|
74
|
+
run.attempt(
|
|
75
|
+
block.as_slice(),
|
|
76
|
+
parameter,
|
|
77
|
+
handler.as_deref(),
|
|
78
|
+
finalizer.as_deref(),
|
|
79
|
+
locals,
|
|
80
|
+
|run, code, locals| run_all(code, run, locals),
|
|
81
|
+
)
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
Statement::Block(block) => {
|
|
85
|
+
let block = statements(block);
|
|
86
|
+
code(move |run, locals| run_all(&block, run, locals))
|
|
87
|
+
}
|
|
88
|
+
Statement::If(test, yes, no) => {
|
|
89
|
+
let test = compute(test);
|
|
90
|
+
let yes = self::statement(yes);
|
|
91
|
+
let no = no.as_deref().map(self::statement);
|
|
92
|
+
code(move |run, locals| {
|
|
93
|
+
let test = test(run, locals)?;
|
|
94
|
+
if run.decide(&test) {
|
|
95
|
+
return yes(run, locals);
|
|
96
|
+
}
|
|
97
|
+
no.as_ref().map_or(Ok(Flow::Normal), |no| no(run, locals))
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
Statement::Return(Some(expression)) => {
|
|
101
|
+
let expression = compute(expression);
|
|
102
|
+
code(move |run, locals| Ok(Flow::Return(expression(run, locals)?)))
|
|
103
|
+
}
|
|
104
|
+
Statement::Return(None) => code(|_, _| Ok(Flow::Return(Value::Undefined))),
|
|
105
|
+
Statement::Throw(expression) => {
|
|
106
|
+
let expression = compute(expression);
|
|
107
|
+
code(move |run, locals| Err(Stop::throw(expression(run, locals)?)))
|
|
108
|
+
}
|
|
109
|
+
Statement::Break(label) => {
|
|
110
|
+
let label = label.cloned();
|
|
111
|
+
code(move |_, _| Ok(Flow::Break(label.clone())))
|
|
112
|
+
}
|
|
113
|
+
Statement::Continue(label) => {
|
|
114
|
+
let label = label.cloned();
|
|
115
|
+
code(move |_, _| Ok(Flow::Continue(label.clone())))
|
|
116
|
+
}
|
|
117
|
+
Statement::Label(label, body) => {
|
|
118
|
+
let label = (*label).clone();
|
|
119
|
+
let body = self::statement(body);
|
|
120
|
+
code(move |run, locals| {
|
|
121
|
+
Ok(match body(run, locals)? {
|
|
122
|
+
Flow::Break(Some(target)) if target == label => Flow::Normal,
|
|
123
|
+
flow => flow,
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
Statement::Loop {
|
|
128
|
+
init,
|
|
129
|
+
test,
|
|
130
|
+
update,
|
|
131
|
+
body,
|
|
132
|
+
body_first,
|
|
133
|
+
label,
|
|
134
|
+
} => {
|
|
135
|
+
let init = init.as_deref().map(self::statement);
|
|
136
|
+
let test = test.as_ref().map(compute);
|
|
137
|
+
let update = update.as_ref().map(compute);
|
|
138
|
+
let body = self::statement(body);
|
|
139
|
+
let body_first = *body_first;
|
|
140
|
+
let label = label.cloned();
|
|
141
|
+
code(move |run, locals| {
|
|
142
|
+
if let Some(init) = &init {
|
|
143
|
+
init(run, locals)?;
|
|
144
|
+
}
|
|
145
|
+
let mut first = body_first;
|
|
146
|
+
for _ in 0..MAXIMUM_ITERATIONS {
|
|
147
|
+
if !first && let Some(test) = &test {
|
|
148
|
+
let repeats = test(run, locals)?.truthy();
|
|
149
|
+
if !repeats.ok_or_else(|| Stop::unsupported("a loop over registers"))? {
|
|
150
|
+
return Ok(Flow::Normal);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
first = false;
|
|
154
|
+
match body(run, locals)? {
|
|
155
|
+
Flow::Normal | Flow::Continue(None) => {}
|
|
156
|
+
Flow::Continue(Some(target)) if Some(&target) == label.as_ref() => {}
|
|
157
|
+
Flow::Break(None) => return Ok(Flow::Normal),
|
|
158
|
+
flow => return Ok(flow),
|
|
159
|
+
}
|
|
160
|
+
if let Some(update) = &update {
|
|
161
|
+
update(run, locals)?;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
Err(Stop::unsupported("a loop past the longest constant"))
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/// An expression statement. Assigning a local writes the value without keeping the result.
|
|
171
|
+
fn effect<'ast>(expression: &Expression<'ast>) -> Code<'ast> {
|
|
172
|
+
if let Expression::Assign(Target::Name(name @ Name::Local(..)), None, right) = expression {
|
|
173
|
+
let (name, right) = (*name, compute(right));
|
|
174
|
+
return code(move |run, locals| {
|
|
175
|
+
let value = right(run, locals)?;
|
|
176
|
+
run.write(Location::Name(name), value, locals)?;
|
|
177
|
+
Ok(Flow::Normal)
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
let expression = compute(expression);
|
|
181
|
+
code(move |run, locals| {
|
|
182
|
+
expression(run, locals)?.discard();
|
|
183
|
+
Ok(Flow::Normal)
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn declare<'ast>(declarations: &[Declaration<'ast>]) -> Code<'ast> {
|
|
188
|
+
let declarations: Vec<_> = declarations
|
|
189
|
+
.iter()
|
|
190
|
+
.map(|Declaration { name, init, keeps }| (*name, init.as_ref().map(compute), *keeps))
|
|
191
|
+
.collect();
|
|
192
|
+
code(move |run, locals| {
|
|
193
|
+
for (name, init, keeps) in &declarations {
|
|
194
|
+
let value = match init {
|
|
195
|
+
Some(init) => init(run, locals)?,
|
|
196
|
+
None if *keeps && run.declared(*name, locals) => continue,
|
|
197
|
+
None => Value::Undefined,
|
|
198
|
+
};
|
|
199
|
+
run.bind(*name, value, locals);
|
|
200
|
+
}
|
|
201
|
+
Ok(Flow::Normal)
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// Runs from the first case strictly equal to the discriminant, or else from the default.
|
|
206
|
+
fn switch<'ast>(discriminant: &Expression<'ast>, cases: &[Case<'ast>]) -> Code<'ast> {
|
|
207
|
+
let discriminant = compute(discriminant);
|
|
208
|
+
let cases: Vec<_> = cases
|
|
209
|
+
.iter()
|
|
210
|
+
.map(|case| (case.test.as_ref().map(compute), statements(&case.body)))
|
|
211
|
+
.collect();
|
|
212
|
+
code(move |run, locals| {
|
|
213
|
+
let discriminant = discriminant(run, locals)?;
|
|
214
|
+
let mut matched = None;
|
|
215
|
+
let mut default = None;
|
|
216
|
+
for (index, (test, _)) in cases.iter().enumerate() {
|
|
217
|
+
let Some(test) = test else {
|
|
218
|
+
default = default.or(Some(index));
|
|
219
|
+
continue;
|
|
220
|
+
};
|
|
221
|
+
let equal = semantics::strict_equal(&discriminant, &test(run, locals)?);
|
|
222
|
+
if equal.ok_or_else(|| Stop::unsupported("a switch over registers"))? {
|
|
223
|
+
matched = Some(index);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
let Some(start) = matched.or(default) else {
|
|
228
|
+
return Ok(Flow::Normal);
|
|
229
|
+
};
|
|
230
|
+
for (_, body) in &cases[start..] {
|
|
231
|
+
match run_all(body, run, locals)? {
|
|
232
|
+
Flow::Normal => {}
|
|
233
|
+
Flow::Break(None) => return Ok(Flow::Normal),
|
|
234
|
+
flow => return Ok(flow),
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
Ok(Flow::Normal)
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// A compiled property key.
|
|
242
|
+
enum Keyed<'ast> {
|
|
243
|
+
Name(&'ast str),
|
|
244
|
+
Computed(Compute<'ast>),
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
impl<'ast> Keyed<'ast> {
|
|
248
|
+
fn new(property: &Property<'ast>) -> Self {
|
|
249
|
+
match property {
|
|
250
|
+
Property::Name(name) => Self::Name(name),
|
|
251
|
+
Property::Computed(expression) => Self::Computed(compute(expression)),
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fn key(
|
|
256
|
+
&self,
|
|
257
|
+
run: &mut Run<'ast, '_>,
|
|
258
|
+
locals: &mut Locals<'ast>,
|
|
259
|
+
) -> Evaluation<'ast, Key<'ast, 'ast>> {
|
|
260
|
+
Ok(match self {
|
|
261
|
+
Self::Name(name) => Key::name(name),
|
|
262
|
+
Self::Computed(expression) => Key::from(expression(run, locals)?),
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/// A compiled assignment target.
|
|
268
|
+
enum Located<'ast> {
|
|
269
|
+
Name(Name<'ast>),
|
|
270
|
+
Member(Compute<'ast>, Keyed<'ast>),
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
impl<'ast> Located<'ast> {
|
|
274
|
+
fn new(target: &Target<'ast>) -> Self {
|
|
275
|
+
match target {
|
|
276
|
+
Target::Name(name) => Self::Name(*name),
|
|
277
|
+
Target::Member(object, key) => Self::Member(compute(object), Keyed::new(key)),
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
fn locate(
|
|
282
|
+
&self,
|
|
283
|
+
run: &mut Run<'ast, '_>,
|
|
284
|
+
locals: &mut Locals<'ast>,
|
|
285
|
+
) -> Evaluation<'ast, Location<'ast>> {
|
|
286
|
+
Ok(match self {
|
|
287
|
+
Self::Name(name) => Location::Name(*name),
|
|
288
|
+
Self::Member(object, key) => {
|
|
289
|
+
Location::Member(object(run, locals)?, key.key(run, locals)?)
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/// Compiled call arguments.
|
|
296
|
+
struct Arguments<'ast>(Vec<Compute<'ast>>);
|
|
297
|
+
|
|
298
|
+
impl<'ast> Arguments<'ast> {
|
|
299
|
+
fn new(arguments: &[Expression<'ast>]) -> Self {
|
|
300
|
+
Self(arguments.iter().map(compute).collect())
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/// Evaluates the arguments into a reused buffer, as `Run::arguments` does.
|
|
304
|
+
fn values(
|
|
305
|
+
&self,
|
|
306
|
+
run: &mut Run<'ast, '_>,
|
|
307
|
+
locals: &mut Locals<'ast>,
|
|
308
|
+
) -> Evaluation<'ast, Vec<Value<'ast>>> {
|
|
309
|
+
let mut values = run.spare.arguments.pop().unwrap_or_default();
|
|
310
|
+
for argument in &self.0 {
|
|
311
|
+
values.push(argument(run, locals)?);
|
|
312
|
+
}
|
|
313
|
+
Ok(values)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
fn compute<'ast>(expression: &Expression<'ast>) -> Compute<'ast> {
|
|
318
|
+
match expression {
|
|
319
|
+
Expression::Constant(value) => {
|
|
320
|
+
let value = value.clone();
|
|
321
|
+
code(move |_, _| Ok(evaluate::copied(&value)))
|
|
322
|
+
}
|
|
323
|
+
Expression::Name(Name::Local(slot, name)) => {
|
|
324
|
+
let (slot, name) = (*slot, *name);
|
|
325
|
+
code(move |run, locals| match &locals[slot] {
|
|
326
|
+
Some(value @ Value::Symbolic(_)) => Ok(run.refined(value)),
|
|
327
|
+
Some(value) => Ok(evaluate::copied(value)),
|
|
328
|
+
None => run.lookup(name),
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
Expression::Name(Name::Global(name)) => {
|
|
332
|
+
let name = *name;
|
|
333
|
+
code(move |run, _| run.lookup(name))
|
|
334
|
+
}
|
|
335
|
+
Expression::This => code(|run, _| Ok(run.frame().this.clone())),
|
|
336
|
+
Expression::Binary(operator, left, right) => {
|
|
337
|
+
let (operator, left, right) = (*operator, compute(left), compute(right));
|
|
338
|
+
code(move |run, locals| {
|
|
339
|
+
Ok(evaluate::binary_value(
|
|
340
|
+
operator,
|
|
341
|
+
left(run, locals)?,
|
|
342
|
+
right(run, locals)?,
|
|
343
|
+
))
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
Expression::Arithmetic { .. } => {
|
|
347
|
+
let number = number(expression);
|
|
348
|
+
code(move |run, locals| Ok(evaluate::number_value(number(run, locals)?)))
|
|
349
|
+
}
|
|
350
|
+
Expression::Member(object, property) => {
|
|
351
|
+
let (object, property) = (compute(object), Keyed::new(property));
|
|
352
|
+
code(move |run, locals| {
|
|
353
|
+
let object = object(run, locals)?;
|
|
354
|
+
let key = property.key(run, locals)?;
|
|
355
|
+
run.get(&object, &key)
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
Expression::Assign(target, operator, right) => assignment(target, *operator, right),
|
|
359
|
+
Expression::Update {
|
|
360
|
+
target,
|
|
361
|
+
operator,
|
|
362
|
+
prefix,
|
|
363
|
+
} => update(target, *operator, *prefix),
|
|
364
|
+
Expression::Call(callee, arguments) => call(callee, arguments),
|
|
365
|
+
Expression::Logical {
|
|
366
|
+
operator,
|
|
367
|
+
left,
|
|
368
|
+
right,
|
|
369
|
+
pure,
|
|
370
|
+
} => logical(*operator, left, right, *pure),
|
|
371
|
+
Expression::Sequence(expressions) => {
|
|
372
|
+
let expressions: Vec<_> = expressions.iter().map(compute).collect();
|
|
373
|
+
code(move |run, locals| {
|
|
374
|
+
let mut value = Value::Undefined;
|
|
375
|
+
for expression in &expressions {
|
|
376
|
+
value = expression(run, locals)?;
|
|
377
|
+
}
|
|
378
|
+
Ok(value)
|
|
379
|
+
})
|
|
380
|
+
}
|
|
381
|
+
Expression::New(callee, arguments) => {
|
|
382
|
+
let (callee, arguments) = (compute(callee), Arguments::new(arguments));
|
|
383
|
+
code(move |run, locals| {
|
|
384
|
+
let callee = callee(run, locals)?;
|
|
385
|
+
let arguments = arguments.values(run, locals)?;
|
|
386
|
+
run.construct(callee, arguments)
|
|
387
|
+
})
|
|
388
|
+
}
|
|
389
|
+
Expression::Unary(operator, argument) => {
|
|
390
|
+
let (operator, argument) = (*operator, compute(argument));
|
|
391
|
+
code(move |run, locals| Ok(semantics::unary_value(operator, argument(run, locals)?)))
|
|
392
|
+
}
|
|
393
|
+
Expression::Conditional(test, yes, no) => {
|
|
394
|
+
let (test, yes, no) = (compute(test), compute(yes), compute(no));
|
|
395
|
+
code(move |run, locals| {
|
|
396
|
+
let test = test(run, locals)?;
|
|
397
|
+
let branch = if run.decide(&test) { &yes } else { &no };
|
|
398
|
+
branch(run, locals)
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
Expression::Array(elements) => {
|
|
402
|
+
let elements: Vec<_> = elements
|
|
403
|
+
.iter()
|
|
404
|
+
.map(|element| element.as_ref().map(compute))
|
|
405
|
+
.collect();
|
|
406
|
+
code(move |run, locals| {
|
|
407
|
+
let mut values = Vec::with_capacity(elements.len());
|
|
408
|
+
for element in &elements {
|
|
409
|
+
values.push(match element {
|
|
410
|
+
Some(element) => element(run, locals)?,
|
|
411
|
+
None => Value::Undefined,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
Ok(Value::array(values))
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
Expression::Object(properties) => {
|
|
418
|
+
let properties: Vec<_> = properties
|
|
419
|
+
.iter()
|
|
420
|
+
.map(|(key, value)| (Keyed::new(key), compute(value)))
|
|
421
|
+
.collect();
|
|
422
|
+
code(move |run, locals| {
|
|
423
|
+
let mut entries = Vec::with_capacity(properties.len());
|
|
424
|
+
for (key, value) in &properties {
|
|
425
|
+
let key = key.key(run, locals)?;
|
|
426
|
+
let name = key
|
|
427
|
+
.property()
|
|
428
|
+
.ok_or_else(|| access::unsupported("object key", &key))?;
|
|
429
|
+
entries.push((name, value(run, locals)?));
|
|
430
|
+
}
|
|
431
|
+
Ok(Value::Object(Rc::new(RefCell::new(entries))))
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
Expression::Function(name, function) => {
|
|
435
|
+
let (name, function) = (name.cloned(), *function);
|
|
436
|
+
code(move |run, _| Ok(run.function_value(name.clone(), function)))
|
|
437
|
+
}
|
|
438
|
+
Expression::Arrow(arrow) => {
|
|
439
|
+
let arrow = *arrow;
|
|
440
|
+
code(move |run, _| Ok(run.arrow_value(arrow)))
|
|
441
|
+
}
|
|
442
|
+
Expression::Unsupported(reason) => {
|
|
443
|
+
let reason = *reason;
|
|
444
|
+
code(move |_, _| Err(Stop::unsupported(reason)))
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
fn call<'ast>(callee: &Expression<'ast>, arguments: &[Expression<'ast>]) -> Compute<'ast> {
|
|
450
|
+
let arguments = Arguments::new(arguments);
|
|
451
|
+
match callee {
|
|
452
|
+
Expression::Member(object, property) => {
|
|
453
|
+
let (object, property) = (compute(object), Keyed::new(property));
|
|
454
|
+
code(move |run, locals| {
|
|
455
|
+
let object = object(run, locals)?;
|
|
456
|
+
let callee = {
|
|
457
|
+
let key = property.key(run, locals)?;
|
|
458
|
+
run.get(&object, &key)?
|
|
459
|
+
};
|
|
460
|
+
let arguments = arguments.values(run, locals)?;
|
|
461
|
+
run.apply(callee, object, arguments)
|
|
462
|
+
})
|
|
463
|
+
}
|
|
464
|
+
callee => {
|
|
465
|
+
let callee = compute(callee);
|
|
466
|
+
code(move |run, locals| {
|
|
467
|
+
let callee = callee(run, locals)?;
|
|
468
|
+
let arguments = arguments.values(run, locals)?;
|
|
469
|
+
run.apply(callee, Value::Undefined, arguments)
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
fn assignment<'ast>(
|
|
476
|
+
target: &Target<'ast>,
|
|
477
|
+
operator: Option<BinaryOp>,
|
|
478
|
+
right: &Expression<'ast>,
|
|
479
|
+
) -> Compute<'ast> {
|
|
480
|
+
let (target, right) = (Located::new(target), compute(right));
|
|
481
|
+
match operator {
|
|
482
|
+
None => code(move |run, locals| {
|
|
483
|
+
let location = target.locate(run, locals)?;
|
|
484
|
+
let value = right(run, locals)?;
|
|
485
|
+
run.write(location, value.clone(), locals)?;
|
|
486
|
+
Ok(value)
|
|
487
|
+
}),
|
|
488
|
+
Some(operator) => code(move |run, locals| {
|
|
489
|
+
let location = target.locate(run, locals)?;
|
|
490
|
+
run.compound(location, operator, locals, |run, locals| right(run, locals))
|
|
491
|
+
}),
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
fn update<'ast>(target: &Target<'ast>, operator: UpdateOp, prefix: bool) -> Compute<'ast> {
|
|
496
|
+
let target = Located::new(target);
|
|
497
|
+
code(move |run, locals| {
|
|
498
|
+
// A counter: the local is read and written in place.
|
|
499
|
+
if let Located::Name(Name::Local(slot, _)) = target
|
|
500
|
+
&& let Some(Value::Number(number)) = &mut locals[slot]
|
|
501
|
+
{
|
|
502
|
+
return Ok(Value::Number(evaluate::step(operator, prefix, number)));
|
|
503
|
+
}
|
|
504
|
+
let location = target.locate(run, locals)?;
|
|
505
|
+
run.update_location(location, operator, prefix, locals)
|
|
506
|
+
})
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/// `&&`, `||`, or `??`, evaluating the right side only when needed.
|
|
510
|
+
fn logical<'ast>(
|
|
511
|
+
operator: BinaryOp,
|
|
512
|
+
left: &Expression<'ast>,
|
|
513
|
+
right: &Expression<'ast>,
|
|
514
|
+
pure: bool,
|
|
515
|
+
) -> Compute<'ast> {
|
|
516
|
+
let (left, right) = (compute(left), compute(right));
|
|
517
|
+
code(move |run, locals| {
|
|
518
|
+
let left = left(run, locals)?;
|
|
519
|
+
run.short_circuit(operator, left, pure, locals, |run, locals| {
|
|
520
|
+
right(run, locals)
|
|
521
|
+
})
|
|
522
|
+
})
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/// Numeric evaluation, as [`Run::number`] does: arithmetic without wrapping intermediate
|
|
526
|
+
/// results, switching to general evaluation where an operand is nonnumeric.
|
|
527
|
+
fn number<'ast>(expression: &Expression<'ast>) -> Numeric<'ast> {
|
|
528
|
+
let Expression::Arithmetic {
|
|
529
|
+
operator,
|
|
530
|
+
operation,
|
|
531
|
+
left,
|
|
532
|
+
right,
|
|
533
|
+
} = expression
|
|
534
|
+
else {
|
|
535
|
+
let general = compute(expression);
|
|
536
|
+
let general = code(move |run, locals| {
|
|
537
|
+
Ok(match general(run, locals)? {
|
|
538
|
+
Value::Number(number) => Ok(number),
|
|
539
|
+
value => Err(value),
|
|
540
|
+
})
|
|
541
|
+
});
|
|
542
|
+
return program_read(expression, general);
|
|
543
|
+
};
|
|
544
|
+
let (operator, operation, left) = (*operator, *operation, operand(left));
|
|
545
|
+
if let Expression::Constant(Value::Number(constant)) = **right {
|
|
546
|
+
return code(move |run, locals| {
|
|
547
|
+
Ok(match left(run, locals)? {
|
|
548
|
+
Ok(left) => Ok(operation(left, constant)),
|
|
549
|
+
left => Err(evaluate::binary_value(
|
|
550
|
+
operator,
|
|
551
|
+
evaluate::number_value(left),
|
|
552
|
+
Value::Number(constant),
|
|
553
|
+
)),
|
|
554
|
+
})
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
let right = operand(right);
|
|
558
|
+
code(move |run, locals| {
|
|
559
|
+
let left = left(run, locals)?;
|
|
560
|
+
let right = right(run, locals)?;
|
|
561
|
+
Ok(match (left, right) {
|
|
562
|
+
(Ok(left), Ok(right)) => Ok(operation(left, right)),
|
|
563
|
+
(left, right) => Err(evaluate::binary_value(
|
|
564
|
+
operator,
|
|
565
|
+
evaluate::number_value(left),
|
|
566
|
+
evaluate::number_value(right),
|
|
567
|
+
)),
|
|
568
|
+
})
|
|
569
|
+
})
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/// An operand of arithmetic, reading numeric constants and locals directly.
|
|
573
|
+
fn operand<'ast>(expression: &Expression<'ast>) -> Numeric<'ast> {
|
|
574
|
+
match expression {
|
|
575
|
+
Expression::Constant(Value::Number(number)) => {
|
|
576
|
+
let number = *number;
|
|
577
|
+
code(move |_, _| Ok(Ok(number)))
|
|
578
|
+
}
|
|
579
|
+
Expression::Name(Name::Local(slot, _)) => {
|
|
580
|
+
let (slot, general) = (*slot, number(expression));
|
|
581
|
+
code(move |run, locals| match locals[slot] {
|
|
582
|
+
Some(Value::Number(number)) => Ok(Ok(number)),
|
|
583
|
+
_ => general(run, locals),
|
|
584
|
+
})
|
|
585
|
+
}
|
|
586
|
+
expression => number(expression),
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/// `program[index++]` and the like, where both are locals: reads the byte directly while the
|
|
591
|
+
/// first local holds the program and the second a number, and runs `general` otherwise.
|
|
592
|
+
fn program_read<'ast>(expression: &Expression<'ast>, general: Numeric<'ast>) -> Numeric<'ast> {
|
|
593
|
+
let Expression::Member(object, Property::Computed(key)) = expression else {
|
|
594
|
+
return general;
|
|
595
|
+
};
|
|
596
|
+
let (
|
|
597
|
+
Expression::Name(Name::Local(program, _)),
|
|
598
|
+
Expression::Update {
|
|
599
|
+
target: Target::Name(Name::Local(index, _)),
|
|
600
|
+
operator,
|
|
601
|
+
prefix,
|
|
602
|
+
},
|
|
603
|
+
) = (&**object, &**key)
|
|
604
|
+
else {
|
|
605
|
+
return general;
|
|
606
|
+
};
|
|
607
|
+
let (program, index, operator, prefix) = (*program, *index, *operator, *prefix);
|
|
608
|
+
code(move |run, locals| {
|
|
609
|
+
if matches!(locals[program], Some(Value::Program))
|
|
610
|
+
&& let Some(Value::Number(number)) = &mut locals[index]
|
|
611
|
+
{
|
|
612
|
+
let position = evaluate::step(operator, prefix, number);
|
|
613
|
+
let byte = convert::index(position).and_then(|position| run.program.get(position));
|
|
614
|
+
return Ok(byte.map_or(Err(Value::Undefined), |byte| Ok(f64::from(*byte))));
|
|
615
|
+
}
|
|
616
|
+
general(run, locals)
|
|
617
|
+
})
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/// Boxes compiled code, giving closures their signature.
|
|
621
|
+
fn code<'ast, T>(
|
|
622
|
+
code: impl for<'session> Fn(&mut Run<'ast, 'session>, &mut Locals<'ast>) -> Evaluation<'ast, T>
|
|
623
|
+
+ 'ast,
|
|
624
|
+
) -> Compiled<'ast, T> {
|
|
625
|
+
Box::new(code)
|
|
626
|
+
}
|