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
|
@@ -11,13 +11,14 @@ use swc_core::ecma::ast::{BinaryOp, Expr, Stmt, UpdateOp};
|
|
|
11
11
|
use super::access::{self, Key};
|
|
12
12
|
use super::plan::{self, Case, Expression, FunctionPlan, Name, Property, Statement, Target};
|
|
13
13
|
use super::run::{Evaluation, Flow, Frame, Run, Stop};
|
|
14
|
-
use super::semantics;
|
|
14
|
+
use super::{compile, semantics};
|
|
15
|
+
use crate::vm::HashMap;
|
|
15
16
|
use crate::vm::output::Term;
|
|
16
|
-
use crate::vm::value::{Env, Value};
|
|
17
|
+
use crate::vm::value::{Env, Scope, Value};
|
|
17
18
|
|
|
18
19
|
/// Maximum handler loop count. Valid constants require fewer iterations; exceeding it indicates
|
|
19
20
|
/// invalid instruction bytes.
|
|
20
|
-
const MAXIMUM_ITERATIONS: usize = 1 << 16;
|
|
21
|
+
pub(super) const MAXIMUM_ITERATIONS: usize = 1 << 16;
|
|
21
22
|
|
|
22
23
|
impl<'ast> Run<'ast, '_> {
|
|
23
24
|
pub(super) fn statement(&mut self, statement: &'ast Stmt) -> Evaluation<'ast, Flow<'ast>> {
|
|
@@ -28,31 +29,96 @@ impl<'ast> Run<'ast, '_> {
|
|
|
28
29
|
self.evaluate(&plan::scoped_expression(expr), &mut [])
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/// Calls a slot plan. Its scope holds the locals nested functions mention, inside `closed`,
|
|
33
|
+
/// the scope the function captured, if any.
|
|
31
34
|
pub(super) fn call_plan(
|
|
32
35
|
&mut self,
|
|
33
36
|
plan: &FunctionPlan<'ast>,
|
|
37
|
+
closed: Option<&Scope<'ast>>,
|
|
34
38
|
this: Value<'ast>,
|
|
35
|
-
|
|
39
|
+
arguments: Vec<Value<'ast>>,
|
|
40
|
+
) -> Evaluation<'ast, Value<'ast>> {
|
|
41
|
+
let mut locals = self.enter_plan(plan, closed, this, arguments);
|
|
42
|
+
let result = compile::run_all(&plan.code, self, &mut locals).map(returned);
|
|
43
|
+
self.frames.pop();
|
|
44
|
+
self.release_locals(locals);
|
|
45
|
+
result
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Calls a handler's slot plan as [`Run::call_plan`] does, with no captured scope. Before
|
|
49
|
+
/// each top-level statement, `before` sees the statement's index and the locals.
|
|
50
|
+
pub(super) fn call_handler(
|
|
51
|
+
&mut self,
|
|
52
|
+
plan: &FunctionPlan<'ast>,
|
|
53
|
+
this: Value<'ast>,
|
|
54
|
+
arguments: Vec<Value<'ast>>,
|
|
55
|
+
before: &mut dyn FnMut(&Self, usize, &Locals<'ast>),
|
|
36
56
|
) -> Evaluation<'ast, Value<'ast>> {
|
|
57
|
+
let mut locals = self.enter_plan(plan, None, this, arguments);
|
|
58
|
+
let result = self.handler_statements(plan, 0, &mut locals, before);
|
|
59
|
+
self.release_locals(locals);
|
|
60
|
+
result
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Binds a slot plan's parameters and pushes its frame. Returns the call's locals.
|
|
64
|
+
#[inline]
|
|
65
|
+
fn enter_plan(
|
|
66
|
+
&mut self,
|
|
67
|
+
plan: &FunctionPlan<'ast>,
|
|
68
|
+
closed: Option<&Scope<'ast>>,
|
|
69
|
+
this: Value<'ast>,
|
|
70
|
+
mut arguments: Vec<Value<'ast>>,
|
|
71
|
+
) -> Vec<Option<Value<'ast>>> {
|
|
37
72
|
let mut locals = self.spare.locals.pop().unwrap_or_default();
|
|
38
73
|
locals.resize(plan.locals, None);
|
|
39
74
|
if let Some(slot) = plan.arguments {
|
|
40
75
|
locals[slot] = Some(Value::array(arguments.clone()));
|
|
41
76
|
}
|
|
77
|
+
let scope = Env::scope(HashMap::default(), closed.cloned());
|
|
42
78
|
let mut values = arguments.drain(..);
|
|
43
|
-
for
|
|
44
|
-
|
|
79
|
+
for parameter in &plan.parameters {
|
|
80
|
+
let value = values.next().unwrap_or(Value::Undefined);
|
|
81
|
+
match *parameter {
|
|
82
|
+
Name::Local(slot, _) => locals[slot] = Some(value),
|
|
83
|
+
Name::Global(name) => {
|
|
84
|
+
scope.borrow_mut().variables.insert(name.clone(), value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
45
87
|
}
|
|
46
88
|
drop(values);
|
|
47
89
|
self.spare.arguments.push(arguments);
|
|
48
|
-
|
|
90
|
+
self.frames.push(Frame { this, scope });
|
|
91
|
+
locals
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Runs a handler's top-level statements from `start` in the frame the run holds, then
|
|
95
|
+
/// leaves that frame, as [`Run::body`] does.
|
|
96
|
+
pub(super) fn handler_statements(
|
|
97
|
+
&mut self,
|
|
98
|
+
plan: &FunctionPlan<'ast>,
|
|
99
|
+
start: usize,
|
|
100
|
+
locals: &mut Locals<'ast>,
|
|
101
|
+
before: &mut dyn FnMut(&Self, usize, &Locals<'ast>),
|
|
102
|
+
) -> Evaluation<'ast, Value<'ast>> {
|
|
103
|
+
let mut flow = Ok(Flow::Normal);
|
|
104
|
+
for (index, statement) in plan.code.iter().enumerate().skip(start) {
|
|
105
|
+
before(self, index, locals);
|
|
106
|
+
flow = statement(self, locals);
|
|
107
|
+
if !matches!(flow, Ok(Flow::Normal)) {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
self.frames.pop();
|
|
112
|
+
flow.map(returned)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Returns a call's locals to the spare buffers.
|
|
116
|
+
fn release_locals(&mut self, mut locals: Vec<Option<Value<'ast>>>) {
|
|
49
117
|
// Numeric locals need no destructor call.
|
|
50
|
-
|
|
51
|
-
for local in assigned {
|
|
118
|
+
for local in locals.drain(..).flatten() {
|
|
52
119
|
local.discard();
|
|
53
120
|
}
|
|
54
121
|
self.spare.locals.push(locals);
|
|
55
|
-
result
|
|
56
122
|
}
|
|
57
123
|
|
|
58
124
|
/// Runs a function body in `frame`.
|
|
@@ -65,10 +131,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
65
131
|
self.frames.push(frame);
|
|
66
132
|
let flow = self.execute_all(body, locals);
|
|
67
133
|
self.frames.pop();
|
|
68
|
-
|
|
69
|
-
Flow::Return(value) => Ok(value),
|
|
70
|
-
_ => Ok(Value::Undefined),
|
|
71
|
-
}
|
|
134
|
+
flow.map(returned)
|
|
72
135
|
}
|
|
73
136
|
|
|
74
137
|
fn execute_all(
|
|
@@ -86,7 +149,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
86
149
|
}
|
|
87
150
|
|
|
88
151
|
/// Checks whether `name` has already been declared in the current function.
|
|
89
|
-
fn declared(&self, name: Name<'ast>, locals: &Locals<'ast>) -> bool {
|
|
152
|
+
pub(super) fn declared(&self, name: Name<'ast>, locals: &Locals<'ast>) -> bool {
|
|
90
153
|
match name {
|
|
91
154
|
Name::Local(slot, _) => locals[slot].is_some(),
|
|
92
155
|
Name::Global(name) => self.frame().scope.borrow().variables.contains_key(name),
|
|
@@ -94,7 +157,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
94
157
|
}
|
|
95
158
|
|
|
96
159
|
/// Declares `name` with `value`.
|
|
97
|
-
fn bind(&mut self, name: Name<'ast>, value: Value<'ast>, locals: &mut Locals<'ast>) {
|
|
160
|
+
pub(super) fn bind(&mut self, name: Name<'ast>, value: Value<'ast>, locals: &mut Locals<'ast>) {
|
|
98
161
|
match name {
|
|
99
162
|
Name::Local(slot, _) => locals[slot] = Some(value),
|
|
100
163
|
Name::Global(name) => self.declare(name.clone(), value),
|
|
@@ -130,25 +193,14 @@ impl<'ast> Run<'ast, '_> {
|
|
|
130
193
|
handler,
|
|
131
194
|
finalizer,
|
|
132
195
|
} => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
outcome = self.execute_all(handler, locals);
|
|
143
|
-
}
|
|
144
|
-
// A jump from `finally` overrides the previous completion of the block.
|
|
145
|
-
if let Some(finalizer) = finalizer {
|
|
146
|
-
match self.execute_all(finalizer, locals)? {
|
|
147
|
-
Flow::Normal => {}
|
|
148
|
-
flow => return Ok(flow),
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return outcome;
|
|
196
|
+
return self.attempt(
|
|
197
|
+
block.as_slice(),
|
|
198
|
+
*parameter,
|
|
199
|
+
handler.as_deref(),
|
|
200
|
+
finalizer.as_deref(),
|
|
201
|
+
locals,
|
|
202
|
+
Self::execute_all,
|
|
203
|
+
);
|
|
152
204
|
}
|
|
153
205
|
Statement::Block(statements) => return self.execute_all(statements, locals),
|
|
154
206
|
Statement::If(test, yes, no) => {
|
|
@@ -216,6 +268,39 @@ impl<'ast> Run<'ast, '_> {
|
|
|
216
268
|
Ok(Flow::Normal)
|
|
217
269
|
}
|
|
218
270
|
|
|
271
|
+
/// Runs a `try` statement whose bodies `run` executes: `block`, then `handler` with an
|
|
272
|
+
/// exception bound to `parameter`, then `finalizer`.
|
|
273
|
+
#[inline]
|
|
274
|
+
pub(super) fn attempt<Body: ?Sized>(
|
|
275
|
+
&mut self,
|
|
276
|
+
block: &Body,
|
|
277
|
+
parameter: Option<Name<'ast>>,
|
|
278
|
+
handler: Option<&Body>,
|
|
279
|
+
finalizer: Option<&Body>,
|
|
280
|
+
locals: &mut Locals<'ast>,
|
|
281
|
+
run: impl Fn(&mut Self, &Body, &mut Locals<'ast>) -> Evaluation<'ast, Flow<'ast>>,
|
|
282
|
+
) -> Evaluation<'ast, Flow<'ast>> {
|
|
283
|
+
let mut outcome = run(self, block, locals);
|
|
284
|
+
if let Some(handler) = handler
|
|
285
|
+
&& let Err(stop) = &mut outcome
|
|
286
|
+
&& let Stop::Throw(exception) = &mut **stop
|
|
287
|
+
{
|
|
288
|
+
let exception = mem::replace(exception, Value::Undefined);
|
|
289
|
+
if let Some(parameter) = parameter {
|
|
290
|
+
self.bind(parameter, exception, locals);
|
|
291
|
+
}
|
|
292
|
+
outcome = run(self, handler, locals);
|
|
293
|
+
}
|
|
294
|
+
// A jump from `finally` overrides the previous completion of the block.
|
|
295
|
+
if let Some(finalizer) = finalizer {
|
|
296
|
+
match run(self, finalizer, locals)? {
|
|
297
|
+
Flow::Normal => {}
|
|
298
|
+
flow => return Ok(flow),
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
outcome
|
|
302
|
+
}
|
|
303
|
+
|
|
219
304
|
/// Runs from the first case strictly equal to `discriminant`, or else from the default.
|
|
220
305
|
fn switch(
|
|
221
306
|
&mut self,
|
|
@@ -504,35 +589,52 @@ impl<'ast> Run<'ast, '_> {
|
|
|
504
589
|
locals: &mut Locals<'ast>,
|
|
505
590
|
) -> Evaluation<'ast, Value<'ast>> {
|
|
506
591
|
let target = self.locate(target, locals)?;
|
|
507
|
-
let
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
592
|
+
let Some(operator) = operator else {
|
|
593
|
+
let value = self.evaluate(right, locals)?;
|
|
594
|
+
self.write(target, value.clone(), locals)?;
|
|
595
|
+
return Ok(value);
|
|
596
|
+
};
|
|
597
|
+
self.compound(target, operator, locals, |run, locals| {
|
|
598
|
+
run.evaluate(right, locals)
|
|
599
|
+
})
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/// Assigns to `target` with `operator`, as in `target += right`, where `right` evaluates
|
|
603
|
+
/// the right side.
|
|
604
|
+
#[inline]
|
|
605
|
+
pub(super) fn compound(
|
|
606
|
+
&mut self,
|
|
607
|
+
target: Location<'ast>,
|
|
608
|
+
operator: BinaryOp,
|
|
609
|
+
locals: &mut Locals<'ast>,
|
|
610
|
+
right: impl FnOnce(&mut Self, &mut Locals<'ast>) -> Evaluation<'ast, Value<'ast>>,
|
|
611
|
+
) -> Evaluation<'ast, Value<'ast>> {
|
|
612
|
+
if matches!(
|
|
613
|
+
operator,
|
|
614
|
+
BinaryOp::LogicalAnd | BinaryOp::LogicalOr | BinaryOp::NullishCoalescing
|
|
615
|
+
) {
|
|
616
|
+
return Err(Stop::unsupported("a logical assignment"));
|
|
617
|
+
}
|
|
618
|
+
// The script reapplies a new rolling mask to the program counter or key. The
|
|
619
|
+
// interpreter keeps that mask at zero; see `crate::vm::registers`.
|
|
620
|
+
if matches!(&target, Location::Member(Value::Registers, Key::Name(_))) {
|
|
621
|
+
right(self, locals)?;
|
|
622
|
+
return self.read(&target, locals);
|
|
623
|
+
}
|
|
624
|
+
// The target's old value is read before the right side runs.
|
|
625
|
+
let old = self.read(&target, locals)?;
|
|
626
|
+
let right = right(self, locals)?;
|
|
627
|
+
// Release the old value before overwriting it, allowing `s += c` to append in place.
|
|
628
|
+
match &target {
|
|
629
|
+
Location::Name(Name::Local(slot, _)) if locals[*slot].is_some() => {
|
|
630
|
+
locals[*slot] = Some(Value::Undefined);
|
|
517
631
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
let old = self.read(&target, locals)?;
|
|
521
|
-
let right = self.evaluate(right, locals)?;
|
|
522
|
-
// Release the old value before overwriting it, allowing `s += c` to append in
|
|
523
|
-
// place.
|
|
524
|
-
match &target {
|
|
525
|
-
Location::Name(Name::Local(slot, _)) if locals[*slot].is_some() => {
|
|
526
|
-
locals[*slot] = Some(Value::Undefined);
|
|
527
|
-
}
|
|
528
|
-
Location::Name(name) => {
|
|
529
|
-
Env::assign(&self.frame().scope, name.atom(), Value::Undefined);
|
|
530
|
-
}
|
|
531
|
-
Location::Member(..) => {}
|
|
532
|
-
}
|
|
533
|
-
binary_value(operator, old, right)
|
|
632
|
+
Location::Name(name) => {
|
|
633
|
+
Env::assign(&self.frame().scope, name.atom(), Value::Undefined);
|
|
534
634
|
}
|
|
535
|
-
|
|
635
|
+
Location::Member(..) => {}
|
|
636
|
+
}
|
|
637
|
+
let value = binary_value(operator, old, right);
|
|
536
638
|
self.write(target, value.clone(), locals)?;
|
|
537
639
|
Ok(value)
|
|
538
640
|
}
|
|
@@ -549,18 +651,27 @@ impl<'ast> Run<'ast, '_> {
|
|
|
549
651
|
if let Target::Name(Name::Local(slot, _)) = target
|
|
550
652
|
&& let Some(Value::Number(number)) = &mut locals[*slot]
|
|
551
653
|
{
|
|
552
|
-
|
|
553
|
-
*number = change.written;
|
|
554
|
-
return Ok(Value::Number(change.value));
|
|
654
|
+
return Ok(Value::Number(step(operator, prefix, number)));
|
|
555
655
|
}
|
|
556
656
|
let target = self.locate(target, locals)?;
|
|
657
|
+
self.update_location(target, operator, prefix, locals)
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/// Applies `++` or `--` to the number at `target`.
|
|
661
|
+
pub(super) fn update_location(
|
|
662
|
+
&mut self,
|
|
663
|
+
target: Location<'ast>,
|
|
664
|
+
operator: UpdateOp,
|
|
665
|
+
prefix: bool,
|
|
666
|
+
locals: &mut Locals<'ast>,
|
|
667
|
+
) -> Evaluation<'ast, Value<'ast>> {
|
|
557
668
|
let old = self.read(&target, locals)?;
|
|
558
|
-
let
|
|
669
|
+
let mut number = old
|
|
559
670
|
.number()
|
|
560
671
|
.ok_or_else(|| Stop::unsupported(&format!("incrementing {}", old.expr())))?;
|
|
561
|
-
let
|
|
562
|
-
self.write(target, Value::Number(
|
|
563
|
-
Ok(Value::Number(
|
|
672
|
+
let value = step(operator, prefix, &mut number);
|
|
673
|
+
self.write(target, Value::Number(number), locals)?;
|
|
674
|
+
Ok(Value::Number(value))
|
|
564
675
|
}
|
|
565
676
|
|
|
566
677
|
/// Evaluates `&&`, `||`, or `??`, evaluating `right` only when needed.
|
|
@@ -574,6 +685,22 @@ impl<'ast> Run<'ast, '_> {
|
|
|
574
685
|
locals: &mut Locals<'ast>,
|
|
575
686
|
) -> Evaluation<'ast, Value<'ast>> {
|
|
576
687
|
let left = self.evaluate(left, locals)?;
|
|
688
|
+
self.short_circuit(operator, left, pure, locals, |run, locals| {
|
|
689
|
+
run.evaluate(right, locals)
|
|
690
|
+
})
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/// Finishes `&&`, `||`, or `??` after its left side, calling `right` to evaluate the right
|
|
694
|
+
/// side only when needed.
|
|
695
|
+
#[inline]
|
|
696
|
+
pub(super) fn short_circuit(
|
|
697
|
+
&mut self,
|
|
698
|
+
operator: BinaryOp,
|
|
699
|
+
left: Value<'ast>,
|
|
700
|
+
pure: bool,
|
|
701
|
+
locals: &mut Locals<'ast>,
|
|
702
|
+
right: impl Fn(&mut Self, &mut Locals<'ast>) -> Evaluation<'ast, Value<'ast>>,
|
|
703
|
+
) -> Evaluation<'ast, Value<'ast>> {
|
|
577
704
|
// Condition controlling right-operand evaluation and the branch direction that
|
|
578
705
|
// evaluates it.
|
|
579
706
|
let (test, runs_right) = match operator {
|
|
@@ -588,7 +715,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
588
715
|
// An effect-free right operand may become a symbolic term only if evaluating it
|
|
589
716
|
// cannot introduce an exception on a skipped path.
|
|
590
717
|
let effects = self.effects.len();
|
|
591
|
-
if let Ok(right) = self
|
|
718
|
+
if let Ok(right) = right(self, locals) {
|
|
592
719
|
debug_assert_eq!(
|
|
593
720
|
effects,
|
|
594
721
|
self.effects.len(),
|
|
@@ -602,7 +729,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
602
729
|
}
|
|
603
730
|
}
|
|
604
731
|
if self.decide(&test) == runs_right {
|
|
605
|
-
self
|
|
732
|
+
right(self, locals)
|
|
606
733
|
} else {
|
|
607
734
|
Ok(left)
|
|
608
735
|
}
|
|
@@ -624,7 +751,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
624
751
|
})
|
|
625
752
|
}
|
|
626
753
|
|
|
627
|
-
fn read(
|
|
754
|
+
pub(super) fn read(
|
|
628
755
|
&mut self,
|
|
629
756
|
target: &Location<'ast>,
|
|
630
757
|
locals: &Locals<'ast>,
|
|
@@ -641,7 +768,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
641
768
|
|
|
642
769
|
/// Writes declared locals directly, since they are the most common assignment target.
|
|
643
770
|
#[inline]
|
|
644
|
-
fn write(
|
|
771
|
+
pub(super) fn write(
|
|
645
772
|
&mut self,
|
|
646
773
|
target: Location<'ast>,
|
|
647
774
|
value: Value<'ast>,
|
|
@@ -667,7 +794,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
667
794
|
|
|
668
795
|
/// Clones a value, using a fast path for numbers.
|
|
669
796
|
#[inline(always)]
|
|
670
|
-
fn copied<'a>(value: &Value<'a>) -> Value<'a> {
|
|
797
|
+
pub(super) fn copied<'a>(value: &Value<'a>) -> Value<'a> {
|
|
671
798
|
match value {
|
|
672
799
|
Value::Number(number) => Value::Number(*number),
|
|
673
800
|
value => value.clone(),
|
|
@@ -676,21 +803,21 @@ fn copied<'a>(value: &Value<'a>) -> Value<'a> {
|
|
|
676
803
|
|
|
677
804
|
/// Extracts the value from a [`Run::number`] result.
|
|
678
805
|
#[inline(always)]
|
|
679
|
-
fn number_value(number: Result<f64, Value<'_>>) -> Value<'_> {
|
|
806
|
+
pub(super) fn number_value(number: Result<f64, Value<'_>>) -> Value<'_> {
|
|
680
807
|
number.map_or_else(|value| value, Value::Number)
|
|
681
808
|
}
|
|
682
809
|
|
|
683
810
|
/// Slot-indexed call locals; `None` until declared or written.
|
|
684
|
-
type Locals<'ast> = [Option<Value<'ast>>];
|
|
811
|
+
pub(super) type Locals<'ast> = [Option<Value<'ast>>];
|
|
685
812
|
|
|
686
|
-
enum Location<'ast> {
|
|
813
|
+
pub(super) enum Location<'ast> {
|
|
687
814
|
Name(Name<'ast>),
|
|
688
815
|
Member(Value<'ast>, Key<'ast, 'ast>),
|
|
689
816
|
}
|
|
690
817
|
|
|
691
818
|
/// Applies [`semantics::binary_value`] to numeric operands without coercion or symbolic terms.
|
|
692
819
|
#[inline(always)]
|
|
693
|
-
fn binary_value<'a>(operator: BinaryOp, left: Value<'a>, right: Value<'a>) -> Value<'a> {
|
|
820
|
+
pub(super) fn binary_value<'a>(operator: BinaryOp, left: Value<'a>, right: Value<'a>) -> Value<'a> {
|
|
694
821
|
let (&Value::Number(a), &Value::Number(b)) = (&left, &right) else {
|
|
695
822
|
return semantics::binary_value(operator, left, right);
|
|
696
823
|
};
|
|
@@ -710,24 +837,24 @@ fn binary_value<'a>(operator: BinaryOp, left: Value<'a>, right: Value<'a>) -> Va
|
|
|
710
837
|
})
|
|
711
838
|
}
|
|
712
839
|
|
|
713
|
-
///
|
|
714
|
-
|
|
715
|
-
/// The number written back.
|
|
716
|
-
written: f64,
|
|
717
|
-
/// Expression result: the new number for prefix form, or the old number for postfix form.
|
|
718
|
-
value: f64,
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
/// Computes the result and stored value for `++` or `--` applied to `old`.
|
|
840
|
+
/// Applies `++` or `--` to `number` in place. Returns the expression's result: the new number
|
|
841
|
+
/// in prefix form, or the old number in postfix form.
|
|
722
842
|
#[inline(always)]
|
|
723
|
-
fn
|
|
724
|
-
let
|
|
843
|
+
pub(super) fn step(operator: UpdateOp, prefix: bool, number: &mut f64) -> f64 {
|
|
844
|
+
let old = *number;
|
|
845
|
+
*number = match operator {
|
|
725
846
|
UpdateOp::PlusPlus => old + 1.0,
|
|
726
847
|
UpdateOp::MinusMinus => old - 1.0,
|
|
727
848
|
};
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
849
|
+
if prefix { *number } else { old }
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/// The result of a call whose body completed with `flow`.
|
|
853
|
+
#[inline]
|
|
854
|
+
fn returned(flow: Flow<'_>) -> Value<'_> {
|
|
855
|
+
match flow {
|
|
856
|
+
Flow::Return(value) => value,
|
|
857
|
+
_ => Value::Undefined,
|
|
731
858
|
}
|
|
732
859
|
}
|
|
733
860
|
|