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
|
@@ -21,7 +21,7 @@ use super::interpret::Way;
|
|
|
21
21
|
use super::machine::{Loaded, Machine};
|
|
22
22
|
use super::output::{Constant, Effect, Exit, Hidden, Instruction, Listing, Path, Position, Term};
|
|
23
23
|
use super::value::Value;
|
|
24
|
-
use super::{HashMap, convert, dataflow};
|
|
24
|
+
use super::{HashMap, convert, dataflow, packed_names};
|
|
25
25
|
use crate::candidates;
|
|
26
26
|
|
|
27
27
|
impl Machine<'_> {
|
|
@@ -57,7 +57,7 @@ impl Machine<'_> {
|
|
|
57
57
|
Err(failure) => walk.listing.failures.push(failure),
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
if !walk.retry(self, &loaded)?
|
|
60
|
+
if !(walk.retry(self, &loaded)? || walk.drop_shortcuts()) {
|
|
61
61
|
break;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -69,6 +69,7 @@ impl Machine<'_> {
|
|
|
69
69
|
});
|
|
70
70
|
listing.failures.sort_unstable_by_key(|failure| failure.pc);
|
|
71
71
|
fuse_method_calls(&mut listing);
|
|
72
|
+
packed_names::fold(&mut listing);
|
|
72
73
|
Ok(listing)
|
|
73
74
|
}
|
|
74
75
|
}
|
|
@@ -387,6 +388,23 @@ pub fn catch_frame(elements: &[Term]) -> Option<Position> {
|
|
|
387
388
|
}
|
|
388
389
|
}
|
|
389
390
|
|
|
391
|
+
/// Exception continuations enter without the registers known on the normal path.
|
|
392
|
+
pub(super) fn exception_entries(listing: &Listing) -> BTreeSet<u32> {
|
|
393
|
+
let mut entries = BTreeSet::new();
|
|
394
|
+
for instruction in listing.instructions.values() {
|
|
395
|
+
for effect in instruction.effects() {
|
|
396
|
+
effect.visit_terms(&mut |term| {
|
|
397
|
+
if let Term::Array(frame) = term
|
|
398
|
+
&& let Some(position) = catch_frame(frame)
|
|
399
|
+
{
|
|
400
|
+
entries.insert(position.pc);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
entries
|
|
406
|
+
}
|
|
407
|
+
|
|
390
408
|
/// Rewrites method calls to the form listing readers expect. Some builds call `object[key]`
|
|
391
409
|
/// directly; others load the method into a register and later call that register with `object`
|
|
392
410
|
/// as receiver.
|
|
@@ -394,15 +412,11 @@ fn fuse_method_calls(listing: &mut Listing) {
|
|
|
394
412
|
// Unrecorded entries, such as functions and hidden targets, start with no known loads.
|
|
395
413
|
let functions = listing.functions.iter().map(|function| function.pc);
|
|
396
414
|
let hidden = listing.hidden.iter().map(|hidden| hidden.target.pc);
|
|
397
|
-
let
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
update_loads(loads, effect);
|
|
401
|
-
}
|
|
402
|
-
ControlFlow::<Infallible, bool>::Continue(true)
|
|
403
|
-
});
|
|
415
|
+
let incoming = incoming_loads(listing, functions.chain(hidden));
|
|
416
|
+
// Exception continuations also start with no known loads, without erasing the normal path's.
|
|
417
|
+
let exceptional = incoming_loads(listing, exception_entries(listing));
|
|
404
418
|
for (pc, instruction) in &mut listing.instructions {
|
|
405
|
-
let Some(entry) = incoming.get(pc) else {
|
|
419
|
+
let Some(entry) = incoming.get(pc).or_else(|| exceptional.get(pc)) else {
|
|
406
420
|
continue;
|
|
407
421
|
};
|
|
408
422
|
for path in &mut instruction.paths {
|
|
@@ -417,6 +431,21 @@ fn fuse_method_calls(listing: &mut Listing) {
|
|
|
417
431
|
}
|
|
418
432
|
}
|
|
419
433
|
|
|
434
|
+
/// The loads known on entry to each instruction reachable from `entries`.
|
|
435
|
+
fn incoming_loads(
|
|
436
|
+
listing: &Listing,
|
|
437
|
+
entries: impl IntoIterator<Item = u32>,
|
|
438
|
+
) -> BTreeMap<u32, Loads> {
|
|
439
|
+
let entries = entries.into_iter().map(|pc| (pc, Loads::default()));
|
|
440
|
+
let ControlFlow::Continue(incoming) = dataflow::forward(listing, entries, |_, path, loads| {
|
|
441
|
+
for effect in &path.effects {
|
|
442
|
+
update_loads(loads, effect);
|
|
443
|
+
}
|
|
444
|
+
ControlFlow::<Infallible, bool>::Continue(true)
|
|
445
|
+
});
|
|
446
|
+
incoming
|
|
447
|
+
}
|
|
448
|
+
|
|
420
449
|
/// Tracks registers holding `object[key]` while the object and key registers remain unchanged.
|
|
421
450
|
/// Saves facts at each instruction's entry. Uses a vector because these sets are small.
|
|
422
451
|
#[derive(Clone, Default)]
|
|
@@ -271,6 +271,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
271
271
|
self.set_field(name.to_owned(), value);
|
|
272
272
|
}
|
|
273
273
|
Value::Array(elements) if key.text() == Some("length") => {
|
|
274
|
+
self.writable(object)?;
|
|
274
275
|
match value.number() {
|
|
275
276
|
Some(length) => {
|
|
276
277
|
let length = convert::length(length)
|
|
@@ -294,6 +295,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
294
295
|
.and_then(convert::length)
|
|
295
296
|
.filter(|&index| index < MAXIMUM_LENGTH)
|
|
296
297
|
.ok_or_else(|| unsupported("array index", key))?;
|
|
298
|
+
self.writable(object)?;
|
|
297
299
|
let mut elements = elements.borrow_mut();
|
|
298
300
|
if elements.len() <= index {
|
|
299
301
|
elements.resize(index + 1, Value::Undefined);
|
|
@@ -304,6 +306,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
304
306
|
let name = key
|
|
305
307
|
.property()
|
|
306
308
|
.ok_or_else(|| unsupported("object key", key))?;
|
|
309
|
+
self.writable(object)?;
|
|
307
310
|
let mut properties = properties.borrow_mut();
|
|
308
311
|
match properties
|
|
309
312
|
.iter_mut()
|
|
@@ -348,33 +351,14 @@ impl<'ast> Run<'ast, '_> {
|
|
|
348
351
|
|
|
349
352
|
/// Decrypts a string record using the same logic as the register file's `read` method.
|
|
350
353
|
fn decrypted(&mut self, record: Value<'ast>) -> Evaluation<'ast, Value<'ast>> {
|
|
351
|
-
|
|
354
|
+
if !matches!(record, Value::Array(_)) {
|
|
352
355
|
return Ok(record);
|
|
353
|
-
};
|
|
354
|
-
let field = |index: usize| elements.borrow().get(index).and_then(Value::number);
|
|
355
|
-
let key = match (field(1), field(2), field(3)) {
|
|
356
|
-
(Some(pc), Some(length), Some(key)) => Some((pc as u32, length as u32, key as u32)),
|
|
357
|
-
_ => None,
|
|
358
|
-
};
|
|
359
|
-
if let Some(key) = key
|
|
360
|
-
&& let Some(text) = self.session.decrypted.borrow().get(&key)
|
|
361
|
-
{
|
|
362
|
-
return Ok(text.clone());
|
|
363
356
|
}
|
|
364
357
|
let decoder = self
|
|
365
358
|
.session
|
|
366
359
|
.record_decoder
|
|
367
|
-
.as_ref()
|
|
368
360
|
.ok_or_else(|| Stop::unsupported("an encrypted string with no decoder"))?;
|
|
369
|
-
|
|
370
|
-
let text = self.apply(decoder, Value::Undefined, vec![record])?;
|
|
371
|
-
if let (Some(key), Value::String(_)) = (key, &text) {
|
|
372
|
-
self.session
|
|
373
|
-
.decrypted
|
|
374
|
-
.borrow_mut()
|
|
375
|
-
.insert(key, text.clone());
|
|
376
|
-
}
|
|
377
|
-
Ok(text)
|
|
361
|
+
self.script(decoder, None, Value::Undefined, vec![record])
|
|
378
362
|
}
|
|
379
363
|
|
|
380
364
|
/// A member of the register file (see [`Member`]).
|
|
@@ -216,17 +216,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
216
216
|
let Value::Array(elements) = &this else {
|
|
217
217
|
return self.apply(Value::symbolic(global()), this, arguments);
|
|
218
218
|
};
|
|
219
|
-
|
|
220
|
-
// constructor-created stack is concrete. Other pushes need no symbolic effect.
|
|
221
|
-
for value in &arguments {
|
|
222
|
-
if let Value::Array(frame) = value
|
|
223
|
-
&& frame.borrow().len() == 4
|
|
224
|
-
&& let Term::Array(frame) = value.expr()
|
|
225
|
-
&& crate::vm::catch_frame(&frame).is_some()
|
|
226
|
-
{
|
|
227
|
-
self.temp(Term::Array(frame));
|
|
228
|
-
}
|
|
229
|
-
}
|
|
219
|
+
self.writable(&this)?;
|
|
230
220
|
let mut elements = elements.borrow_mut();
|
|
231
221
|
elements.extend(arguments);
|
|
232
222
|
Ok(Value::Number(elements.len() as f64))
|
|
@@ -238,6 +228,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
238
228
|
let Value::Array(elements) = &this else {
|
|
239
229
|
return self.apply(Value::symbolic(global()), this, arguments);
|
|
240
230
|
};
|
|
231
|
+
self.writable(&this)?;
|
|
241
232
|
let popped = elements.borrow_mut().pop();
|
|
242
233
|
match popped {
|
|
243
234
|
Some(value) => Ok(value),
|
|
@@ -377,11 +368,13 @@ impl<'ast> Run<'ast, '_> {
|
|
|
377
368
|
},
|
|
378
369
|
Builtin::Add => {
|
|
379
370
|
if contains(&members.borrow(), &key) == Some(false) {
|
|
371
|
+
self.writable(&this)?;
|
|
380
372
|
members.borrow_mut().push(key);
|
|
381
373
|
}
|
|
382
374
|
Ok(this.clone())
|
|
383
375
|
}
|
|
384
376
|
_ => {
|
|
377
|
+
self.writable(&this)?;
|
|
385
378
|
let mut members = members.borrow_mut();
|
|
386
379
|
let before = members.len();
|
|
387
380
|
members.retain(|member| {
|
|
@@ -414,6 +407,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
414
407
|
Builtin::MapHas => Ok(Value::Bool(position.is_some())),
|
|
415
408
|
Builtin::MapSet => {
|
|
416
409
|
let value = argument(1);
|
|
410
|
+
self.writable(&this)?;
|
|
417
411
|
let mut entries = entries.borrow_mut();
|
|
418
412
|
match position {
|
|
419
413
|
Some(position) => entries[position].1 = value,
|
|
@@ -423,6 +417,7 @@ impl<'ast> Run<'ast, '_> {
|
|
|
423
417
|
}
|
|
424
418
|
_ => {
|
|
425
419
|
if let Some(position) = position {
|
|
420
|
+
self.writable(&this)?;
|
|
426
421
|
entries.borrow_mut().remove(position);
|
|
427
422
|
}
|
|
428
423
|
Ok(Value::Bool(position.is_some()))
|
|
@@ -608,48 +603,6 @@ mod tests {
|
|
|
608
603
|
assert!(call(Builtin::Floor, &[]).is_nan());
|
|
609
604
|
}
|
|
610
605
|
|
|
611
|
-
#[test]
|
|
612
|
-
fn concrete_stack_pushes_expose_exception_frames_to_disassembly() {
|
|
613
|
-
let session = Session::empty();
|
|
614
|
-
let mut run = Run::method(&session);
|
|
615
|
-
let stack = Value::array(vec![]);
|
|
616
|
-
let frame = Value::array(vec![
|
|
617
|
-
Value::Number(42.0),
|
|
618
|
-
Value::Number(7.0),
|
|
619
|
-
Value::Cells,
|
|
620
|
-
Value::Number(0.0),
|
|
621
|
-
]);
|
|
622
|
-
let expected = frame.expr();
|
|
623
|
-
assert_eq!(
|
|
624
|
-
number(run.builtin(Builtin::Push, stack.clone(), vec![frame])),
|
|
625
|
-
1.0
|
|
626
|
-
);
|
|
627
|
-
assert_eq!(
|
|
628
|
-
run.effects,
|
|
629
|
-
vec![crate::vm::Effect::Let(0, expected.clone())]
|
|
630
|
-
);
|
|
631
|
-
assert_eq!(stack.expr(), Term::Array(vec![expected]));
|
|
632
|
-
for (length, value) in [
|
|
633
|
-
(2.0, Value::Number(9.0)),
|
|
634
|
-
(3.0, Value::array(vec![Value::Number(42.0)])),
|
|
635
|
-
(
|
|
636
|
-
4.0,
|
|
637
|
-
Value::array(vec![
|
|
638
|
-
Value::Number(42.0),
|
|
639
|
-
Value::Number(256.0),
|
|
640
|
-
Value::Cells,
|
|
641
|
-
Value::Number(0.0),
|
|
642
|
-
]),
|
|
643
|
-
),
|
|
644
|
-
] {
|
|
645
|
-
assert_eq!(
|
|
646
|
-
number(run.builtin(Builtin::Push, stack.clone(), vec![value])),
|
|
647
|
-
length
|
|
648
|
-
);
|
|
649
|
-
assert_eq!(run.effects.len(), 1);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
606
|
/// Checks `SameValueZero` membership: the set contains 1, "1", NaN, and 0 after duplicate
|
|
654
607
|
/// removal.
|
|
655
608
|
#[test]
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
//! Calls and constructions of script functions, arrows, builtins, and bound functions.
|
|
2
2
|
|
|
3
|
+
use std::ptr;
|
|
4
|
+
|
|
3
5
|
use swc_core::atoms::{Atom, atom};
|
|
4
6
|
use swc_core::ecma::ast::{ArrowExpr, Decl, Function, Pat, Stmt};
|
|
5
7
|
|
|
6
8
|
use super::access::Key;
|
|
7
|
-
use super::
|
|
8
|
-
use super::
|
|
9
|
+
use super::run::{Evaluation, Frame, RecordKey, RecordPart, Run, Stop};
|
|
10
|
+
use super::{intrinsic, plan};
|
|
9
11
|
use crate::vm::HashMap;
|
|
10
12
|
use crate::vm::output::Term;
|
|
11
13
|
use crate::vm::value::{Callable, Env, Scope, Value};
|
|
@@ -112,15 +114,24 @@ impl<'ast> Run<'ast, '_> {
|
|
|
112
114
|
function,
|
|
113
115
|
scope,
|
|
114
116
|
} if arguments.iter().any(Value::is_symbolic) => {
|
|
115
|
-
|
|
117
|
+
// The call records the arguments as they were before the attempt. Only
|
|
118
|
+
// containers can change during it, so calls without them are kept (`Err`) and
|
|
119
|
+
// recorded only when the attempt fails.
|
|
120
|
+
let call = |arguments: &[Value<'ast>]| Term::Call {
|
|
116
121
|
callee: Box::new(Term::Global(name.to_string())),
|
|
117
122
|
this: None,
|
|
118
123
|
arguments: arguments.iter().map(Value::expr).collect(),
|
|
119
124
|
};
|
|
125
|
+
let recorded = if arguments.iter().all(Value::is_immutable) {
|
|
126
|
+
Err(arguments.clone())
|
|
127
|
+
} else {
|
|
128
|
+
Ok(call(&arguments))
|
|
129
|
+
};
|
|
120
130
|
let checkpoint = self.checkpoint();
|
|
121
131
|
match self.script(function, scope.as_ref(), this, arguments) {
|
|
122
132
|
Err(stop) if matches!(*stop, Stop::Unsupported(_)) => {
|
|
123
133
|
self.restore(checkpoint);
|
|
134
|
+
let call = recorded.unwrap_or_else(|kept| call(&kept));
|
|
124
135
|
Ok(self.temp(call))
|
|
125
136
|
}
|
|
126
137
|
outcome => {
|
|
@@ -191,20 +202,59 @@ impl<'ast> Run<'ast, '_> {
|
|
|
191
202
|
}
|
|
192
203
|
}
|
|
193
204
|
|
|
194
|
-
/// Calls a function
|
|
195
|
-
///
|
|
196
|
-
|
|
205
|
+
/// Calls a script function: natively if it has an intrinsic that accepts the arguments,
|
|
206
|
+
/// from the program's cache if it decrypts a string record decrypted before, or else
|
|
207
|
+
/// interpreted.
|
|
208
|
+
pub(super) fn script(
|
|
209
|
+
&mut self,
|
|
210
|
+
function: &'ast Function,
|
|
211
|
+
scope: Option<&Scope<'ast>>,
|
|
212
|
+
this: Value<'ast>,
|
|
213
|
+
arguments: Vec<Value<'ast>>,
|
|
214
|
+
) -> Evaluation<'ast, Value<'ast>> {
|
|
215
|
+
if let Some(intrinsic) = self.session.intrinsics.get(&intrinsic::address(function)) {
|
|
216
|
+
// Intrinsics may write to their arguments.
|
|
217
|
+
for argument in &arguments {
|
|
218
|
+
self.writable(argument)?;
|
|
219
|
+
}
|
|
220
|
+
if let Some(value) = intrinsic.call(&arguments) {
|
|
221
|
+
return Ok(value);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Handlers decrypt strings both by reading registers and by calling the decoder.
|
|
225
|
+
if self
|
|
226
|
+
.session
|
|
227
|
+
.record_decoder
|
|
228
|
+
.is_some_and(|decoder| ptr::eq(decoder, function))
|
|
229
|
+
&& let Some(decrypted) = self.decrypted
|
|
230
|
+
&& let [record] = &arguments[..]
|
|
231
|
+
&& let Some(key) = record_key(record)
|
|
232
|
+
{
|
|
233
|
+
if let Some(text) = decrypted.borrow().get(&key) {
|
|
234
|
+
return Ok(text.clone());
|
|
235
|
+
}
|
|
236
|
+
let text = self.interpret(function, scope, this, arguments)?;
|
|
237
|
+
// A run that thawed a shared object may have computed the text differently.
|
|
238
|
+
if matches!(text, Value::String(_)) && !self.thawed {
|
|
239
|
+
decrypted.borrow_mut().insert(key, text.clone());
|
|
240
|
+
}
|
|
241
|
+
return Ok(text);
|
|
242
|
+
}
|
|
243
|
+
self.interpret(function, scope, this, arguments)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// Runs a function through its slot plan if it has one, or else through its scoped plan.
|
|
247
|
+
fn interpret(
|
|
197
248
|
&mut self,
|
|
198
249
|
function: &'ast Function,
|
|
199
250
|
scope: Option<&Scope<'ast>>,
|
|
200
251
|
this: Value<'ast>,
|
|
201
252
|
arguments: Vec<Value<'ast>>,
|
|
202
253
|
) -> Evaluation<'ast, Value<'ast>> {
|
|
203
|
-
if
|
|
204
|
-
&& self.setup.is_none()
|
|
254
|
+
if self.setup.is_none()
|
|
205
255
|
&& let Some(plan) = self.session.compiled.slots(function)
|
|
206
256
|
{
|
|
207
|
-
return self.call_plan(&plan, this, arguments);
|
|
257
|
+
return self.call_plan(&plan, scope, this, arguments);
|
|
208
258
|
}
|
|
209
259
|
let body = function
|
|
210
260
|
.body
|
|
@@ -252,6 +302,33 @@ impl<'ast> Run<'ast, '_> {
|
|
|
252
302
|
}
|
|
253
303
|
}
|
|
254
304
|
|
|
305
|
+
/// Identifies a string record: the program, then numbers and arrays of numbers. A program's
|
|
306
|
+
/// records with the same contents decrypt to the same string.
|
|
307
|
+
fn record_key(record: &Value<'_>) -> Option<RecordKey> {
|
|
308
|
+
let Value::Array(elements) = record else {
|
|
309
|
+
return None;
|
|
310
|
+
};
|
|
311
|
+
let elements = elements.borrow();
|
|
312
|
+
let (Value::Program, rest) = elements.split_first()? else {
|
|
313
|
+
return None;
|
|
314
|
+
};
|
|
315
|
+
let bits = |value: &Value<'_>| match value {
|
|
316
|
+
Value::Number(number) => Some(number.to_bits()),
|
|
317
|
+
_ => None,
|
|
318
|
+
};
|
|
319
|
+
rest.iter()
|
|
320
|
+
.map(|element| match element {
|
|
321
|
+
Value::Array(numbers) => numbers
|
|
322
|
+
.borrow()
|
|
323
|
+
.iter()
|
|
324
|
+
.map(bits)
|
|
325
|
+
.collect::<Option<_>>()
|
|
326
|
+
.map(RecordPart::Numbers),
|
|
327
|
+
element => bits(element).map(RecordPart::Number),
|
|
328
|
+
})
|
|
329
|
+
.collect()
|
|
330
|
+
}
|
|
331
|
+
|
|
255
332
|
/// Binds call arguments to parameters and reserves `room` additional local slots.
|
|
256
333
|
fn parameters<'ast, 'pattern>(
|
|
257
334
|
patterns: impl Iterator<Item = &'pattern Pat>,
|