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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +10 -9
  4. data/UPSTREAM.md +6 -6
  5. data/lib/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
  6. data/lib/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
  7. data/lib/cfsolve/profiles/safari-26-ios.json +0 -8
  8. data/lib/cfsolve/version.rb +2 -2
  9. data/vendor/cfsolve/README.md +0 -35
  10. data/vendor/cfsolve/examples/build_profile.rs +17 -15
  11. data/vendor/cfsolve/examples/capture_session.rs +37 -0
  12. data/vendor/cfsolve/examples/check_answers.rs +66 -0
  13. data/vendor/cfsolve/examples/compare_session.rs +190 -0
  14. data/vendor/cfsolve/examples/stage2_snapshot.rs +4 -1
  15. data/vendor/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
  16. data/vendor/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
  17. data/vendor/cfsolve/profiles/safari-26-ios.json +0 -8
  18. data/vendor/cfsolve/scripts/capture_cssom.mjs +42 -0
  19. data/vendor/cfsolve/scripts/serve_cssom.py +111 -0
  20. data/vendor/cfsolve/src/capture/profile.rs +7 -18
  21. data/vendor/cfsolve/src/capture/recorded.rs +42 -7
  22. data/vendor/cfsolve/src/challenge/installed/audit.rs +131 -0
  23. data/vendor/cfsolve/src/challenge/installed.rs +122 -271
  24. data/vendor/cfsolve/src/challenge/payload.rs +107 -5
  25. data/vendor/cfsolve/src/challenge/state.rs +98 -16
  26. data/vendor/cfsolve/src/deobfuscate/encoded_strings.rs +3 -2
  27. data/vendor/cfsolve/src/deobfuscate/helper_objects.rs +17 -32
  28. data/vendor/cfsolve/src/deobfuscate/register_arrays.rs +18 -5
  29. data/vendor/cfsolve/src/embedding_page/extra_params.rs +0 -14
  30. data/vendor/cfsolve/src/embedding_page/page.rs +28 -2
  31. data/vendor/cfsolve/src/http/client.rs +11 -0
  32. data/vendor/cfsolve/src/js/ast.rs +12 -3
  33. data/vendor/cfsolve/src/js/objects.rs +9 -2
  34. data/vendor/cfsolve/src/listing/fold.rs +0 -83
  35. data/vendor/cfsolve/src/listing/query.rs +25 -244
  36. data/vendor/cfsolve/src/listing.rs +0 -1
  37. data/vendor/cfsolve/src/session/initial.rs +14 -5
  38. data/vendor/cfsolve/src/session/probes.rs +160 -125
  39. data/vendor/cfsolve/src/session/timing.rs +58 -53
  40. data/vendor/cfsolve/src/session/widget.rs +5 -6
  41. data/vendor/cfsolve/src/stage2/analysis/analyze.rs +50 -33
  42. data/vendor/cfsolve/src/stage2/analysis/discovery.rs +61 -66
  43. data/vendor/cfsolve/src/stage2/analysis/envelope.rs +31 -21
  44. data/vendor/cfsolve/src/stage2/analysis/recognize.rs +3 -5
  45. data/vendor/cfsolve/src/stage2/analysis.rs +0 -1
  46. data/vendor/cfsolve/src/stage2/audit_globals.rs +310 -0
  47. data/vendor/cfsolve/src/stage2/benchmark.rs +5 -8
  48. data/vendor/cfsolve/src/stage2/call_stack.rs +72 -16
  49. data/vendor/cfsolve/src/stage2/interaction/native.rs +2 -1
  50. data/vendor/cfsolve/src/stage2/interaction/plan.rs +0 -3
  51. data/vendor/cfsolve/src/stage2/payload/recover.rs +54 -23
  52. data/vendor/cfsolve/src/stage2/probes/ice.rs +11 -10
  53. data/vendor/cfsolve/src/stage2/probes/media.rs +47 -33
  54. data/vendor/cfsolve/src/stage2/probes/performance.rs +1 -0
  55. data/vendor/cfsolve/src/stage2/probes/rects.rs +12 -17
  56. data/vendor/cfsolve/src/stage2/probes/stack.rs +50 -103
  57. data/vendor/cfsolve/src/stage2/probes/websocket_echo.rs +17 -86
  58. data/vendor/cfsolve/src/stage2/probes.rs +0 -1
  59. data/vendor/cfsolve/src/stage2/records.rs +4 -13
  60. data/vendor/cfsolve/src/stage2/reference.json +1 -1
  61. data/vendor/cfsolve/src/stage2/reference.rs +98 -100
  62. data/vendor/cfsolve/src/stage2/registry.rs +41 -34
  63. data/vendor/cfsolve/src/stage2/replay.rs +182 -254
  64. data/vendor/cfsolve/src/stage2/stylesheets.rs +176 -0
  65. data/vendor/cfsolve/src/stage2/trace.rs +18 -0
  66. data/vendor/cfsolve/src/stage2/vocabulary.rs +111 -401
  67. data/vendor/cfsolve/src/stage2.rs +2 -1
  68. data/vendor/cfsolve/src/vm/disassemble.rs +39 -10
  69. data/vendor/cfsolve/src/vm/interpret/access.rs +5 -21
  70. data/vendor/cfsolve/src/vm/interpret/builtin.rs +6 -53
  71. data/vendor/cfsolve/src/vm/interpret/call.rs +86 -9
  72. data/vendor/cfsolve/src/vm/interpret/compile.rs +626 -0
  73. data/vendor/cfsolve/src/vm/interpret/copies.rs +232 -120
  74. data/vendor/cfsolve/src/vm/interpret/evaluate/tests.rs +96 -8
  75. data/vendor/cfsolve/src/vm/interpret/evaluate.rs +219 -92
  76. data/vendor/cfsolve/src/vm/interpret/explore.rs +155 -39
  77. data/vendor/cfsolve/src/vm/interpret/frozen.rs +171 -0
  78. data/vendor/cfsolve/src/vm/interpret/intrinsic.rs +350 -0
  79. data/vendor/cfsolve/src/vm/interpret/plan.rs +71 -44
  80. data/vendor/cfsolve/src/vm/interpret/run.rs +202 -13
  81. data/vendor/cfsolve/src/vm/interpret.rs +7 -2
  82. data/vendor/cfsolve/src/vm/machine.rs +6 -2
  83. data/vendor/cfsolve/src/vm/packed_names.rs +307 -0
  84. data/vendor/cfsolve/src/vm/session.rs +36 -14
  85. data/vendor/cfsolve/src/vm/value.rs +13 -0
  86. data/vendor/cfsolve/src/vm.rs +1 -0
  87. data/vendor/cfsolve/vendor/btls-sys/src/lib.rs +3 -0
  88. metadata +13 -6
  89. data/vendor/cfsolve/src/listing/normalize.rs +0 -264
  90. data/vendor/cfsolve/src/stage2/analysis/outputs.rs +0 -327
  91. data/vendor/cfsolve/src/stage2/probes/stylesheets.rs +0 -249
  92. data/vendor/cfsolve/src/stage2/reference-expanded.json +0 -42
  93. data/vendor/cfsolve/src/stage2/session_names.rs +0 -470
@@ -2,8 +2,8 @@
2
2
  //! layout, instance fields, and opcode handlers. [`Session::constructed`] adds the constructor
3
3
  //! fields that depend on a program.
4
4
  //!
5
- //! Each handler run copies mutable values; see [`super::interpret`]. Only decrypted strings
6
- //! ([`Session::decrypted`]) and lazily compiled plans are cached between runs.
5
+ //! Each handler run copies mutable values; see [`super::interpret`]. Only lazily compiled plans
6
+ //! are cached between runs, besides the decrypted strings a loaded program caches.
7
7
 
8
8
  use std::cell::RefCell;
9
9
  use std::collections::BTreeSet;
@@ -17,7 +17,7 @@ use swc_core::ecma::ast::{
17
17
  };
18
18
 
19
19
  use super::extract::{self, RunMethod};
20
- use super::interpret::{self, Plans, Run, Spare};
20
+ use super::interpret::{self, Branching, Frozen, Intrinsics, Plans, Run, Spare};
21
21
  use super::output::{Constant, Position, Term};
22
22
  use super::registers::Member;
23
23
  use super::value::Value;
@@ -33,6 +33,10 @@ pub(crate) struct Session<'ast> {
33
33
  /// Lazily prepared handlers and helpers, including unsupported functions.
34
34
  pub compiled: Plans<'ast>,
35
35
  pub spare: RefCell<Spare<'ast>>,
36
+ /// Session binding objects handler runs share instead of copying.
37
+ pub frozen: Frozen,
38
+ /// What exploration has learned about each handler's paths, by handler address.
39
+ pub branching: RefCell<HashMap<usize, Branching>>,
36
40
  pub bindings: HashMap<Atom, Value<'ast>>,
37
41
  /// Names interrupted setup statements may have written, with their errors. Reading them
38
42
  /// stops interpretation.
@@ -43,9 +47,9 @@ pub(crate) struct Session<'ast> {
43
47
  /// Initial register stacks created by the constructor for handler execution.
44
48
  pub initial: Vec<(u8, Value<'ast>)>,
45
49
  /// Function used to decrypt string records stored in registers.
46
- pub record_decoder: Option<Atom>,
47
- /// Decrypted strings cached by record position, length, and key.
48
- pub decrypted: RefCell<HashMap<(u32, u32, u32), Value<'ast>>>,
50
+ pub record_decoder: Option<&'ast Function>,
51
+ /// Script functions run natively.
52
+ pub intrinsics: Intrinsics,
49
53
  /// The symbol the instance keeps its program under, if any.
50
54
  pub program_key: Option<Rc<()>>,
51
55
  /// Instance objects stored under the remaining symbols.
@@ -81,16 +85,22 @@ impl<'ast> Session<'ast> {
81
85
  let functions = extract::functions(&body.stmts);
82
86
  let run = extract::run_method(&functions, run)?;
83
87
  let handlers = extract::handlers(&functions, &run.dispatch)?;
88
+ let record_decoder =
89
+ extract::record_decoder(body).and_then(|name| functions.get(&name).copied());
84
90
  let mut session = Self {
85
91
  functions,
86
92
  handlers,
87
93
  entry: extract::entry(body)?,
88
- record_decoder: extract::record_decoder(body),
94
+ record_decoder,
89
95
  ..Self::unevaluated(constructor)
90
96
  };
91
97
  (session.bindings, session.unavailable) = interpret::setup(&session, prelude, &body.stmts);
92
98
  let constructed = construct(&mut Run::method(&session), constructor);
93
- (session.state, session.layout, session.initial) = state(&run, &constructed)?;
99
+ Recovered {
100
+ state: session.state,
101
+ layout: session.layout,
102
+ initial: session.initial,
103
+ } = state(&run, &constructed)?;
94
104
  session.symbol_fields = constructed.objects;
95
105
  session.program_key = match &constructed.programs[..] {
96
106
  [] => None,
@@ -98,6 +108,8 @@ impl<'ast> Session<'ast> {
98
108
  symbols => bail!("the VM constructor keys {} values by symbol", symbols.len()),
99
109
  };
100
110
  session.fields = concrete(constructed.fields);
111
+ session.intrinsics = interpret::intrinsics(&session);
112
+ session.frozen = Frozen::of(&session.bindings);
101
113
  Ok(session)
102
114
  }
103
115
 
@@ -107,6 +119,8 @@ impl<'ast> Session<'ast> {
107
119
  function_values: RefCell::default(),
108
120
  compiled: Default::default(),
109
121
  spare: RefCell::default(),
122
+ frozen: Frozen::default(),
123
+ branching: RefCell::default(),
110
124
  bindings: HashMap::default(),
111
125
  unavailable: HashMap::default(),
112
126
  layout: Layout::default(),
@@ -115,7 +129,7 @@ impl<'ast> Session<'ast> {
115
129
  symbol_fields: Vec::new(),
116
130
  initial: Vec::new(),
117
131
  record_decoder: None,
118
- decrypted: RefCell::default(),
132
+ intrinsics: Intrinsics::default(),
119
133
  fields: HashMap::default(),
120
134
  constructor,
121
135
  handlers: HashMap::default(),
@@ -345,13 +359,17 @@ fn concrete<'a>(mut fields: HashMap<String, Value<'a>>) -> HashMap<String, Value
345
359
  fields
346
360
  }
347
361
 
362
+ /// The handler state, register layout and initial register stacks [`state`] recovers.
363
+ struct Recovered<'a> {
364
+ state: HashMap<String, Value<'a>>,
365
+ layout: Layout,
366
+ initial: Vec<(u8, Value<'a>)>,
367
+ }
368
+
348
369
  /// Recovers state-field values and register layout. The constructor computes indices as
349
370
  /// `this.<mask> ^ <register>` with a random mask per run. Uses zero for the mask during
350
371
  /// analysis.
351
- fn state<'a>(
352
- names: &RunMethod<'_>,
353
- constructed: &Constructed<'a>,
354
- ) -> Result<(HashMap<String, Value<'a>>, Layout, Vec<(u8, Value<'a>)>)> {
372
+ fn state<'a>(names: &RunMethod<'_>, constructed: &Constructed<'a>) -> Result<Recovered<'a>> {
355
373
  let fields = &constructed.fields;
356
374
  let pc = constructed
357
375
  .pc
@@ -402,7 +420,11 @@ fn state<'a>(
402
420
  .map(|write| (write.register, write.value.clone()))
403
421
  .collect();
404
422
  state.extend(mask.map(|mask| (mask, Value::Number(0.0))));
405
- Ok((state, layout, initial))
423
+ Ok(Recovered {
424
+ state,
425
+ layout,
426
+ initial,
427
+ })
406
428
  }
407
429
 
408
430
  /// Extracts the register and optional mask field from `this.<mask> ^ <register>` or a bare
@@ -215,6 +215,19 @@ impl<'ast> Value<'ast> {
215
215
  }
216
216
  }
217
217
 
218
+ /// Whether nothing a run does can change the value or its [`Value::expr`]: it is not a
219
+ /// container or a function with captured state.
220
+ pub(super) fn is_immutable(&self) -> bool {
221
+ match self {
222
+ Self::Array(_) | Self::Object(_) | Self::Set(_) | Self::Map(_) => false,
223
+ Self::Function(callable) => matches!(
224
+ &**callable,
225
+ Callable::Builtin(_) | Callable::Standard(_) | Callable::Script { scope: None, .. }
226
+ ),
227
+ _ => true,
228
+ }
229
+ }
230
+
218
231
  pub(super) fn is_symbolic(&self) -> bool {
219
232
  matches!(self, Self::Symbolic(_))
220
233
  }
@@ -18,6 +18,7 @@ mod extract;
18
18
  mod interpret;
19
19
  mod machine;
20
20
  mod output;
21
+ mod packed_names;
21
22
  mod registers;
22
23
  mod session;
23
24
  mod value;
@@ -1,4 +1,7 @@
1
+ // cfsolve patch: vendored code is not ours to lint; keep `cargo clippy` output to cfsolve.
1
2
  #![allow(
3
+ clippy::all,
4
+ unnecessary_transmutes,
2
5
  clippy::missing_safety_doc,
3
6
  clippy::redundant_static_lifetimes,
4
7
  clippy::too_many_arguments,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfsolve
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Morgan
@@ -684,6 +684,9 @@ files:
684
684
  - vendor/cfsolve/LICENSE
685
685
  - vendor/cfsolve/README.md
686
686
  - vendor/cfsolve/examples/build_profile.rs
687
+ - vendor/cfsolve/examples/capture_session.rs
688
+ - vendor/cfsolve/examples/check_answers.rs
689
+ - vendor/cfsolve/examples/compare_session.rs
687
690
  - vendor/cfsolve/examples/disassemble.rs
688
691
  - vendor/cfsolve/examples/fill_profile.rs
689
692
  - vendor/cfsolve/examples/probe_plan.rs
@@ -698,6 +701,8 @@ files:
698
701
  - vendor/cfsolve/profiles/safari-26-ios/challenge-page-answers.json
699
702
  - vendor/cfsolve/rust-toolchain.toml
700
703
  - vendor/cfsolve/rustfmt.toml
704
+ - vendor/cfsolve/scripts/capture_cssom.mjs
705
+ - vendor/cfsolve/scripts/serve_cssom.py
701
706
  - vendor/cfsolve/src/bin/capture-proxy/cli.rs
702
707
  - vendor/cfsolve/src/bin/capture-proxy/io.rs
703
708
  - vendor/cfsolve/src/bin/capture-proxy/main.rs
@@ -722,6 +727,7 @@ files:
722
727
  - vendor/cfsolve/src/challenge/codec.rs
723
728
  - vendor/cfsolve/src/challenge/expression.rs
724
729
  - vendor/cfsolve/src/challenge/installed.rs
730
+ - vendor/cfsolve/src/challenge/installed/audit.rs
725
731
  - vendor/cfsolve/src/challenge/merge.rs
726
732
  - vendor/cfsolve/src/challenge/orchestrator.rs
727
733
  - vendor/cfsolve/src/challenge/payload.rs
@@ -789,7 +795,6 @@ files:
789
795
  - vendor/cfsolve/src/listing.rs
790
796
  - vendor/cfsolve/src/listing/fold.rs
791
797
  - vendor/cfsolve/src/listing/index.rs
792
- - vendor/cfsolve/src/listing/normalize.rs
793
798
  - vendor/cfsolve/src/listing/query.rs
794
799
  - vendor/cfsolve/src/listing/strings.rs
795
800
  - vendor/cfsolve/src/main.rs
@@ -816,8 +821,8 @@ files:
816
821
  - vendor/cfsolve/src/stage2/analysis/discovery.rs
817
822
  - vendor/cfsolve/src/stage2/analysis/envelope.rs
818
823
  - vendor/cfsolve/src/stage2/analysis/outcome.rs
819
- - vendor/cfsolve/src/stage2/analysis/outputs.rs
820
824
  - vendor/cfsolve/src/stage2/analysis/recognize.rs
825
+ - vendor/cfsolve/src/stage2/audit_globals.rs
821
826
  - vendor/cfsolve/src/stage2/benchmark.rs
822
827
  - vendor/cfsolve/src/stage2/call_stack.rs
823
828
  - vendor/cfsolve/src/stage2/challenge_page.rs
@@ -851,18 +856,16 @@ files:
851
856
  - vendor/cfsolve/src/stage2/probes/private_token.rs
852
857
  - vendor/cfsolve/src/stage2/probes/rects.rs
853
858
  - vendor/cfsolve/src/stage2/probes/stack.rs
854
- - vendor/cfsolve/src/stage2/probes/stylesheets.rs
855
859
  - vendor/cfsolve/src/stage2/probes/websocket_echo.rs
856
860
  - vendor/cfsolve/src/stage2/probes/worker_fetch.rs
857
861
  - vendor/cfsolve/src/stage2/probes/worker_navigator.rs
858
862
  - vendor/cfsolve/src/stage2/proof.rs
859
863
  - vendor/cfsolve/src/stage2/records.rs
860
- - vendor/cfsolve/src/stage2/reference-expanded.json
861
864
  - vendor/cfsolve/src/stage2/reference.json
862
865
  - vendor/cfsolve/src/stage2/reference.rs
863
866
  - vendor/cfsolve/src/stage2/registry.rs
864
867
  - vendor/cfsolve/src/stage2/replay.rs
865
- - vendor/cfsolve/src/stage2/session_names.rs
868
+ - vendor/cfsolve/src/stage2/stylesheets.rs
866
869
  - vendor/cfsolve/src/stage2/trace.rs
867
870
  - vendor/cfsolve/src/stage2/vocabulary.rs
868
871
  - vendor/cfsolve/src/stage2/worker_timing.rs
@@ -876,16 +879,20 @@ files:
876
879
  - vendor/cfsolve/src/vm/interpret/access.rs
877
880
  - vendor/cfsolve/src/vm/interpret/builtin.rs
878
881
  - vendor/cfsolve/src/vm/interpret/call.rs
882
+ - vendor/cfsolve/src/vm/interpret/compile.rs
879
883
  - vendor/cfsolve/src/vm/interpret/copies.rs
880
884
  - vendor/cfsolve/src/vm/interpret/evaluate.rs
881
885
  - vendor/cfsolve/src/vm/interpret/evaluate/tests.rs
882
886
  - vendor/cfsolve/src/vm/interpret/explore.rs
887
+ - vendor/cfsolve/src/vm/interpret/frozen.rs
888
+ - vendor/cfsolve/src/vm/interpret/intrinsic.rs
883
889
  - vendor/cfsolve/src/vm/interpret/plan.rs
884
890
  - vendor/cfsolve/src/vm/interpret/run.rs
885
891
  - vendor/cfsolve/src/vm/interpret/semantics.rs
886
892
  - vendor/cfsolve/src/vm/interpret/setup.rs
887
893
  - vendor/cfsolve/src/vm/machine.rs
888
894
  - vendor/cfsolve/src/vm/output.rs
895
+ - vendor/cfsolve/src/vm/packed_names.rs
889
896
  - vendor/cfsolve/src/vm/registers.rs
890
897
  - vendor/cfsolve/src/vm/session.rs
891
898
  - vendor/cfsolve/src/vm/value.rs
@@ -1,264 +0,0 @@
1
- //! Exposes constant expressions to structural readers without executing calls.
2
-
3
- use std::collections::BTreeSet;
4
- use std::convert::Infallible;
5
- use std::ops::ControlFlow;
6
-
7
- use super::Index;
8
- use super::fold::{self, Frame, Known};
9
- use crate::js::JsValue;
10
- use crate::vm::{Constant, Effect, Listing, Term, dataflow};
11
-
12
- fn evaluate(frame: &Frame, term: &Term) -> Option<Known> {
13
- if let Some(known) = frame.resolve(term) {
14
- return Some(known);
15
- }
16
- let value = fold::constant(frame, term);
17
- if matches!(term, Term::Binary(_, _, _)) && matches!(value, Some(JsValue::String(_))) {
18
- return None;
19
- }
20
- value.map(|value| {
21
- if matches!(value, JsValue::String(_)) {
22
- Known::Decoded(value)
23
- } else {
24
- Known::Literal(value)
25
- }
26
- })
27
- }
28
-
29
- /// Fold only values agreed on by every incoming path. Function and dispatcher
30
- /// entries start with common header facts, never another function's registers.
31
- pub(crate) fn constants(listing: &mut Listing) {
32
- let setup = Frame::seeded(Index::new(listing).header());
33
- let mut entries: BTreeSet<_> = listing
34
- .functions
35
- .iter()
36
- .map(|entry| entry.pc)
37
- .chain(listing.hidden.iter().map(|branch| branch.target.pc))
38
- .collect();
39
- for instruction in listing.instructions.values() {
40
- for effect in instruction.effects() {
41
- effect.visit_terms(&mut |term| {
42
- if let Term::Array(frame) = term {
43
- entries.extend(crate::vm::catch_frame(frame).map(|handler| handler.pc));
44
- }
45
- });
46
- }
47
- }
48
- let entries = entries.into_iter().map(|pc| (pc, setup.clone()));
49
- let ControlFlow::<Infallible, _>::Continue(incoming) =
50
- dataflow::forward(listing, entries, |pc, path, frame| {
51
- frame.begin();
52
- if !fold::feasible(path, |term| evaluate(frame, term)) {
53
- return ControlFlow::Continue(false);
54
- }
55
- frame.follow(pc, &path.effects, evaluate, |_, _| {});
56
- ControlFlow::Continue(true)
57
- });
58
- for (&pc, instruction) in &mut listing.instructions {
59
- let Some(entry) = incoming.get(&pc) else {
60
- continue;
61
- };
62
- for path in &mut instruction.paths {
63
- let mut frame = entry.clone();
64
- frame.begin();
65
- for effect in &mut path.effects {
66
- if let Effect::SetProperty { key, .. } = effect
67
- && let Some(Known::Decoded(JsValue::String(text))) = frame.resolve(key)
68
- {
69
- **key = Term::Const(Constant::String(text));
70
- }
71
- let folded = match &*effect {
72
- Effect::Let(_, value) | Effect::Set(_, value) => evaluate(&frame, value),
73
- _ => None,
74
- };
75
- frame.follow(pc, std::slice::from_ref(effect), evaluate, |_, _| {});
76
- if let Effect::Let(_, value) | Effect::Set(_, value) = effect {
77
- match folded {
78
- Some(Known::Decoded(JsValue::String(text))) => {
79
- *value = Term::Const(Constant::String(text))
80
- }
81
- Some(Known::Literal(JsValue::Number(number)))
82
- if matches!(value, Term::Call { .. } | Term::Binary(_, _, _)) =>
83
- {
84
- *value = Term::Const(Constant::Number(number))
85
- }
86
- _ => {}
87
- }
88
- }
89
- }
90
- }
91
- }
92
- }
93
-
94
- #[cfg(test)]
95
- mod tests {
96
- use super::*;
97
- use crate::vm::{Exit, Instruction, Path, Position};
98
-
99
- fn text(value: &str) -> Term {
100
- Term::Const(Constant::String(value.into()))
101
- }
102
-
103
- fn slice(register: u8) -> Term {
104
- Term::Call {
105
- callee: Box::new(Term::Get(
106
- Box::new(Term::Reg(register)),
107
- Box::new(text("slice")),
108
- )),
109
- this: Some(Box::new(Term::Reg(register))),
110
- arguments: vec![
111
- Term::Const(Constant::Number(2.0)),
112
- Term::Const(Constant::Number(7.0)),
113
- ],
114
- }
115
- }
116
-
117
- fn path(effects: Vec<Effect>, exit: Exit) -> Path {
118
- Path {
119
- conditions: vec![],
120
- effects,
121
- exit,
122
- }
123
- }
124
-
125
- fn program(paths: Vec<Vec<Path>>) -> Listing {
126
- let mut listing = Listing::default();
127
- listing.functions.push(Position::new(0, 0));
128
- for (pc, paths) in (0..).zip(paths) {
129
- listing.instructions.insert(
130
- pc,
131
- Instruction {
132
- pc,
133
- key: 0,
134
- opcode: 0,
135
- paths,
136
- },
137
- );
138
- }
139
- // End the common header before any branch-specific constants.
140
- listing.instructions.get_mut(&0).unwrap().paths[0]
141
- .effects
142
- .insert(
143
- 0,
144
- Effect::Set(
145
- 255,
146
- Term::Closure {
147
- pc: 100,
148
- key: 0,
149
- parameters: vec![],
150
- arity: 0,
151
- },
152
- ),
153
- );
154
- listing
155
- }
156
-
157
- #[test]
158
- fn decoded_strings_survive_temporaries_and_instruction_boundaries() {
159
- let mut listing = program(vec![
160
- vec![path(
161
- vec![Effect::Set(1, text("\0\u{5}freshpadding"))],
162
- Exit::Next { pc: 1, key: 0 },
163
- )],
164
- vec![path(
165
- vec![Effect::Let(0, slice(1)), Effect::Set(2, Term::Temp(0))],
166
- Exit::Next { pc: 2, key: 0 },
167
- )],
168
- vec![path(vec![Effect::Set(3, Term::Reg(2))], Exit::Return)],
169
- ]);
170
- constants(&mut listing);
171
- assert_eq!(
172
- listing.instructions[&1].paths[0].effects[1],
173
- Effect::Set(2, text("fresh"))
174
- );
175
- assert_eq!(
176
- listing.instructions[&2].paths[0].effects[0],
177
- Effect::Set(3, text("fresh"))
178
- );
179
- }
180
-
181
- #[test]
182
- fn disagreement_and_unknown_writes_leave_calls_unfolded() {
183
- let mut listing = program(vec![
184
- vec![path(vec![], Exit::Next { pc: 1, key: 0 })],
185
- vec![
186
- path(
187
- vec![Effect::Set(1, text("\0\u{5}first"))],
188
- Exit::Next { pc: 2, key: 0 },
189
- ),
190
- path(
191
- vec![Effect::Set(1, text("\0\u{5}other"))],
192
- Exit::Next { pc: 2, key: 0 },
193
- ),
194
- ],
195
- vec![path(
196
- vec![
197
- Effect::Set(2, slice(1)),
198
- Effect::Set(1, Term::Global("unknown".into())),
199
- Effect::Set(3, slice(1)),
200
- ],
201
- Exit::Return,
202
- )],
203
- ]);
204
- let expected = listing.instructions[&2].paths[0].effects.clone();
205
- constants(&mut listing);
206
- assert_eq!(listing.instructions[&2].paths[0].effects, expected);
207
- }
208
-
209
- #[test]
210
- fn ordinary_register_copies_and_string_assembly_keep_their_shape() {
211
- use swc_core::ecma::ast::BinaryOp;
212
- let mut listing = program(vec![vec![path(
213
- vec![
214
- Effect::Set(1, text("window")),
215
- Effect::Set(2, Term::Reg(1)),
216
- Effect::Set(
217
- 3,
218
- Term::Binary(
219
- BinaryOp::Add,
220
- Box::new(text("prefix")),
221
- Box::new(text("suffix")),
222
- ),
223
- ),
224
- ],
225
- Exit::Return,
226
- )]]);
227
- let expected = listing.instructions[&0].paths[0].effects.clone();
228
- constants(&mut listing);
229
- assert_eq!(listing.instructions[&0].paths[0].effects, expected);
230
- }
231
-
232
- #[test]
233
- fn exception_handlers_start_with_header_facts_and_decode_their_own_strings() {
234
- let frame = Term::Array(vec![
235
- Term::Const(Constant::Number(1.0)),
236
- Term::Const(Constant::Number(0.0)),
237
- Term::Cells,
238
- Term::Const(Constant::Number(0.0)),
239
- ]);
240
- let mut listing = program(vec![
241
- vec![path(
242
- vec![Effect::Set(2, text("\0\u{5}prior")), Effect::Let(0, frame)],
243
- Exit::Return,
244
- )],
245
- vec![path(
246
- vec![
247
- Effect::Set(3, slice(2)),
248
- Effect::Set(1, text("\0\u{5}catch")),
249
- ],
250
- Exit::Next { pc: 2, key: 0 },
251
- )],
252
- vec![path(vec![Effect::Set(4, slice(1))], Exit::Return)],
253
- ]);
254
- constants(&mut listing);
255
- assert_eq!(
256
- listing.instructions[&1].paths[0].effects[0],
257
- Effect::Set(3, slice(2))
258
- );
259
- assert_eq!(
260
- listing.instructions[&2].paths[0].effects[0],
261
- Effect::Set(4, text("catch"))
262
- );
263
- }
264
- }