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
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
//! Explores feasible paths through an instruction handler.
|
|
2
2
|
|
|
3
|
-
use std::mem;
|
|
3
|
+
use std::{mem, ptr};
|
|
4
4
|
|
|
5
5
|
use swc_core::ecma::ast::Function;
|
|
6
6
|
|
|
7
7
|
use super::copies::Copies;
|
|
8
|
-
use super::
|
|
8
|
+
use super::intrinsic;
|
|
9
|
+
use super::run::{Decrypted, Run, Snapshot, Stop};
|
|
9
10
|
use crate::vm::dispatch::Decoded;
|
|
10
11
|
use crate::vm::output::Path;
|
|
11
12
|
use crate::vm::session::Session;
|
|
@@ -26,6 +27,8 @@ pub(crate) struct Invocation<'session, 'ast> {
|
|
|
26
27
|
pub handler: &'ast Function,
|
|
27
28
|
/// The instance fields the constructor derives from the program.
|
|
28
29
|
pub fields: &'session HashMap<String, Value<'ast>>,
|
|
30
|
+
/// Strings decrypted from the program so far.
|
|
31
|
+
pub decrypted: &'session Decrypted<'ast>,
|
|
29
32
|
/// Registers whose values are known, such as a candidate string.
|
|
30
33
|
pub known: &'session [(u8, Value<'ast>)],
|
|
31
34
|
}
|
|
@@ -45,41 +48,147 @@ pub(crate) struct Way<'ast> {
|
|
|
45
48
|
|
|
46
49
|
/// Runs the handler for each feasible combination of symbolic branches, depth-first with true
|
|
47
50
|
/// before false. Returns `None` if there are more than [`MAXIMUM_PATHS`].
|
|
51
|
+
///
|
|
52
|
+
/// Paths that differ only after the top-level statement where the handler's first branch was
|
|
53
|
+
/// seen resume from a [`Snapshot`] taken before it rather than rerunning the whole handler.
|
|
54
|
+
/// Each path still sees exactly the state a rerun would.
|
|
48
55
|
#[must_use]
|
|
49
56
|
pub(crate) fn explore<'ast>(invocation: &Invocation<'_, 'ast>) -> Option<Ways<'ast>> {
|
|
50
|
-
let
|
|
51
|
-
let handler =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
let session = invocation.session;
|
|
58
|
+
let handler = invocation.handler;
|
|
59
|
+
let address = intrinsic::address(handler);
|
|
60
|
+
// `Run::script` runs these natively or caches them, rather than through a slot plan.
|
|
61
|
+
let special = session.intrinsics.contains_key(&address)
|
|
62
|
+
|| session
|
|
63
|
+
.record_decoder
|
|
64
|
+
.is_some_and(|decoder| ptr::eq(decoder, handler));
|
|
65
|
+
let plan = if special {
|
|
66
|
+
None
|
|
67
|
+
} else {
|
|
68
|
+
session.compiled.slots(handler)
|
|
55
69
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
// A run that thaws a shared session object leaves no results; explore again with the
|
|
71
|
+
// object copied.
|
|
72
|
+
'explore: loop {
|
|
73
|
+
let branching = session
|
|
74
|
+
.branching
|
|
75
|
+
.borrow()
|
|
76
|
+
.get(&address)
|
|
77
|
+
.copied()
|
|
78
|
+
.unwrap_or_default();
|
|
79
|
+
// Snapshots usually repay their copying only for handlers whose instructions have
|
|
80
|
+
// averaged at least 1.5 runs.
|
|
81
|
+
let split = branching
|
|
82
|
+
.statement
|
|
83
|
+
.filter(|&statement| statement > 0 && 2 * branching.runs >= 3 * branching.instructions);
|
|
84
|
+
let mut ways = Ways::default();
|
|
85
|
+
let mut decisions = Vec::new();
|
|
86
|
+
let mut snapshot: Option<Snapshot<'ast>> = None;
|
|
87
|
+
for _ in 0..MAXIMUM_PATHS {
|
|
88
|
+
let resumed = snapshot
|
|
89
|
+
.as_ref()
|
|
90
|
+
.filter(|snapshot| snapshot.resumes(&decisions));
|
|
91
|
+
let (mut run, outcome, copied) = match (plan.as_deref(), resumed) {
|
|
92
|
+
(Some(plan), Some(snapshot)) => {
|
|
93
|
+
let (mut run, mut locals, copied) =
|
|
94
|
+
Run::resume(invocation, snapshot, decisions);
|
|
95
|
+
// Snapshot locals are dropped rather than returned to the spare buffers.
|
|
96
|
+
let outcome = run.handler_statements(
|
|
97
|
+
plan,
|
|
98
|
+
snapshot.statement,
|
|
99
|
+
&mut locals,
|
|
100
|
+
&mut |_, _, _| {},
|
|
101
|
+
);
|
|
102
|
+
(run, outcome, copied)
|
|
103
|
+
}
|
|
104
|
+
(Some(plan), None) => {
|
|
105
|
+
let mut run = Run::handler(invocation, decisions);
|
|
106
|
+
let mut taken = None;
|
|
107
|
+
// The last top-level statement started before any decision was consumed.
|
|
108
|
+
let mut undecided = None;
|
|
109
|
+
let outcome = run.call_handler(
|
|
110
|
+
plan,
|
|
111
|
+
Value::Undefined,
|
|
112
|
+
vec![Value::Machine],
|
|
113
|
+
&mut |run, statement, locals| {
|
|
114
|
+
if run.branches.decided == 0 {
|
|
115
|
+
undecided = Some(statement);
|
|
116
|
+
}
|
|
117
|
+
if split == Some(statement) {
|
|
118
|
+
taken = run.snapshot(statement, locals);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
if branching.statement.is_none()
|
|
123
|
+
&& !run.thawed
|
|
124
|
+
&& run.branches.decided > 0
|
|
125
|
+
&& let Some(statement) = undecided
|
|
126
|
+
{
|
|
127
|
+
let mut learned = session.branching.borrow_mut();
|
|
128
|
+
learned
|
|
129
|
+
.entry(address)
|
|
130
|
+
.or_default()
|
|
131
|
+
.statement
|
|
132
|
+
.get_or_insert(statement);
|
|
133
|
+
}
|
|
134
|
+
snapshot = taken;
|
|
135
|
+
(run, outcome, Vec::new())
|
|
136
|
+
}
|
|
137
|
+
(None, _) => {
|
|
138
|
+
let mut run = Run::handler(invocation, decisions);
|
|
139
|
+
let handler = Callable::Script {
|
|
140
|
+
name: None,
|
|
141
|
+
function: handler,
|
|
142
|
+
scope: None,
|
|
143
|
+
};
|
|
144
|
+
let outcome = run.invoke(&handler, Value::Undefined, vec![Value::Machine]);
|
|
145
|
+
(run, outcome, Vec::new())
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
decisions = mem::take(&mut run.branches.decisions);
|
|
149
|
+
decisions.truncate(run.branches.decided);
|
|
150
|
+
let thawed = run.thawed;
|
|
151
|
+
let scopes = run.scopes();
|
|
152
|
+
let fields = mem::take(&mut run.fields);
|
|
153
|
+
let way = run.finish(outcome);
|
|
154
|
+
// Clear captured scopes to break cycles between functions and the scopes holding
|
|
155
|
+
// them.
|
|
156
|
+
for scope in scopes.into_iter().chain(copied) {
|
|
157
|
+
scope.borrow_mut().variables.clear();
|
|
158
|
+
}
|
|
159
|
+
if thawed {
|
|
160
|
+
continue 'explore;
|
|
161
|
+
}
|
|
162
|
+
match way {
|
|
163
|
+
Ok(path) => ways.taken.push(Way { path, fields }),
|
|
164
|
+
Err(fault) => ways.faults.push(fault),
|
|
165
|
+
}
|
|
166
|
+
// Retry the last branch taken as `true` that has not yet been tried as `false`.
|
|
167
|
+
while decisions.last() == Some(&false) {
|
|
168
|
+
decisions.pop();
|
|
169
|
+
}
|
|
170
|
+
let Some(last) = decisions.last_mut() else {
|
|
171
|
+
let mut learned = session.branching.borrow_mut();
|
|
172
|
+
let learned = learned.entry(address).or_default();
|
|
173
|
+
learned.instructions += 1;
|
|
174
|
+
learned.runs += ways.taken.len() + ways.faults.len();
|
|
175
|
+
return Some(ways);
|
|
176
|
+
};
|
|
177
|
+
*last = false;
|
|
80
178
|
}
|
|
179
|
+
break None;
|
|
81
180
|
}
|
|
82
|
-
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// What exploration has learned about a handler's paths.
|
|
184
|
+
#[derive(Clone, Copy, Default)]
|
|
185
|
+
pub(crate) struct Branching {
|
|
186
|
+
/// The top-level statement where its first decision was seen.
|
|
187
|
+
statement: Option<usize>,
|
|
188
|
+
/// Instructions explored.
|
|
189
|
+
instructions: usize,
|
|
190
|
+
/// Runs the explored instructions took.
|
|
191
|
+
runs: usize,
|
|
83
192
|
}
|
|
84
193
|
|
|
85
194
|
/// Runs `decoder` after setting the program counter and dispatch key registers. Returns the decoded
|
|
@@ -92,18 +201,25 @@ pub(crate) fn decode<'ast>(
|
|
|
92
201
|
pc: u32,
|
|
93
202
|
key: u8,
|
|
94
203
|
) -> Result<Decoded, String> {
|
|
95
|
-
let mut run = Run::new(session, program, Vec::new(), Some(Copies::default()));
|
|
96
|
-
run.start_at(pc, key);
|
|
97
204
|
let decoder = Callable::Script {
|
|
98
205
|
name: None,
|
|
99
206
|
function: decoder,
|
|
100
207
|
scope: None,
|
|
101
208
|
};
|
|
102
|
-
|
|
103
|
-
let next =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
209
|
+
// A run that thaws a shared session object decodes again with the object copied.
|
|
210
|
+
let (outcome, next) = loop {
|
|
211
|
+
let mut run = Run::new(session, program, Vec::new(), Some(Copies::default()));
|
|
212
|
+
run.sharing = true;
|
|
213
|
+
run.start_at(pc, key);
|
|
214
|
+
let outcome = run.invoke(&decoder, Value::Undefined, arguments.clone());
|
|
215
|
+
let next = run.register_value(session.layout.key);
|
|
216
|
+
for scope in run.scopes() {
|
|
217
|
+
scope.borrow_mut().variables.clear();
|
|
218
|
+
}
|
|
219
|
+
if !run.thawed {
|
|
220
|
+
break (outcome, next);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
107
223
|
let opcode = outcome.map_err(|stop| match *stop {
|
|
108
224
|
Stop::Throw(value) => format!("throws {}", value.expr()),
|
|
109
225
|
Stop::Unsupported(reason) => reason,
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
//! Session objects that handler runs share instead of copying while no run changes them.
|
|
2
|
+
//!
|
|
3
|
+
//! Runs copy every session object they reach (see [`super::copies`]), but most session
|
|
4
|
+
//! bindings are tables that handlers only read. Objects reachable from bindings form groups:
|
|
5
|
+
//! bindings that share an object belong to one group. Handler and decoder runs share a
|
|
6
|
+
//! group's objects as they are. The first run that writes to one thaws its group, so later
|
|
7
|
+
//! runs copy it as before, and stops; the caller then reruns the instruction from the start.
|
|
8
|
+
//! Every completed run therefore sees what a run that copied everything would.
|
|
9
|
+
//!
|
|
10
|
+
//! Groups that reach a function with captured state are never shared: calling it would write
|
|
11
|
+
//! to a session scope.
|
|
12
|
+
|
|
13
|
+
use std::cell::RefCell;
|
|
14
|
+
use std::rc::Rc;
|
|
15
|
+
|
|
16
|
+
use rustc_hash::FxHashSet as HashSet;
|
|
17
|
+
use swc_core::atoms::Atom;
|
|
18
|
+
|
|
19
|
+
use crate::vm::HashMap;
|
|
20
|
+
use crate::vm::value::Value;
|
|
21
|
+
|
|
22
|
+
#[derive(Default)]
|
|
23
|
+
pub(crate) struct Frozen {
|
|
24
|
+
/// Addresses of the objects runs share.
|
|
25
|
+
shared: RefCell<HashSet<usize>>,
|
|
26
|
+
/// Each shared object's group root, by address.
|
|
27
|
+
groups: HashMap<usize, usize>,
|
|
28
|
+
/// Each group's objects, by root.
|
|
29
|
+
members: HashMap<usize, Vec<usize>>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl Frozen {
|
|
33
|
+
/// Groups the objects reachable from `bindings` and shares those of groups that reach no
|
|
34
|
+
/// function with captured state.
|
|
35
|
+
pub(crate) fn of(bindings: &HashMap<Atom, Value<'_>>) -> Self {
|
|
36
|
+
let mut groups = Groups::default();
|
|
37
|
+
for value in bindings.values() {
|
|
38
|
+
let mut reached = Reached::default();
|
|
39
|
+
reached.visit(value);
|
|
40
|
+
groups.join(&reached);
|
|
41
|
+
}
|
|
42
|
+
groups.frozen()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Whether runs share the object at `address`.
|
|
46
|
+
pub(super) fn contains(&self, address: usize) -> bool {
|
|
47
|
+
let shared = self.shared.borrow();
|
|
48
|
+
!shared.is_empty() && shared.contains(&address)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Stops sharing the group of the object at `address`.
|
|
52
|
+
pub(super) fn thaw(&self, address: usize) {
|
|
53
|
+
if let Some(&group) = self.groups.get(&address) {
|
|
54
|
+
let mut shared = self.shared.borrow_mut();
|
|
55
|
+
for member in &self.members[&group] {
|
|
56
|
+
shared.remove(member);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// The address of `value` if it is an object a run can change.
|
|
63
|
+
pub(super) fn address(value: &Value<'_>) -> Option<usize> {
|
|
64
|
+
Some(match value {
|
|
65
|
+
Value::Array(elements) => Rc::as_ptr(elements).addr(),
|
|
66
|
+
Value::Object(properties) => Rc::as_ptr(properties).addr(),
|
|
67
|
+
Value::Set(members) => Rc::as_ptr(members).addr(),
|
|
68
|
+
Value::Map(entries) => Rc::as_ptr(entries).addr(),
|
|
69
|
+
_ => return None,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// The objects reachable from one binding.
|
|
74
|
+
#[derive(Default)]
|
|
75
|
+
struct Reached {
|
|
76
|
+
seen: HashSet<usize>,
|
|
77
|
+
/// Whether a function with captured state is reachable.
|
|
78
|
+
stateful: bool,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
impl Reached {
|
|
82
|
+
fn visit(&mut self, value: &Value<'_>) {
|
|
83
|
+
if let Value::Function(_) = value {
|
|
84
|
+
self.stateful |= !value.is_immutable();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
let Some(address) = address(value) else {
|
|
88
|
+
return;
|
|
89
|
+
};
|
|
90
|
+
if !self.seen.insert(address) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
match value {
|
|
94
|
+
Value::Array(elements) | Value::Set(elements) => {
|
|
95
|
+
for element in elements.borrow().iter() {
|
|
96
|
+
self.visit(element);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
Value::Object(properties) => {
|
|
100
|
+
for (_, property) in properties.borrow().iter() {
|
|
101
|
+
self.visit(property);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
Value::Map(entries) => {
|
|
105
|
+
for (key, entry) in entries.borrow().iter() {
|
|
106
|
+
self.visit(key);
|
|
107
|
+
self.visit(entry);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
_ => {}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Union-find over objects, joining the objects each binding reaches.
|
|
116
|
+
#[derive(Default)]
|
|
117
|
+
struct Groups {
|
|
118
|
+
/// Each object's parent in the union-find forest, by address.
|
|
119
|
+
parents: HashMap<usize, usize>,
|
|
120
|
+
/// Roots whose group reaches a function with captured state.
|
|
121
|
+
stateful: HashSet<usize>,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
impl Groups {
|
|
125
|
+
fn root(&mut self, mut address: usize) -> usize {
|
|
126
|
+
self.parents.entry(address).or_insert(address);
|
|
127
|
+
while let Some(&parent) = self.parents.get(&address)
|
|
128
|
+
&& parent != address
|
|
129
|
+
{
|
|
130
|
+
let grandparent = self.parents[&parent];
|
|
131
|
+
self.parents.insert(address, grandparent);
|
|
132
|
+
address = grandparent;
|
|
133
|
+
}
|
|
134
|
+
address
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fn join(&mut self, reached: &Reached) {
|
|
138
|
+
let mut objects = reached.seen.iter().copied();
|
|
139
|
+
let Some(first) = objects.next() else {
|
|
140
|
+
return;
|
|
141
|
+
};
|
|
142
|
+
let root = self.root(first);
|
|
143
|
+
for object in objects {
|
|
144
|
+
let other = self.root(object);
|
|
145
|
+
if other != root {
|
|
146
|
+
self.parents.insert(other, root);
|
|
147
|
+
if self.stateful.remove(&other) {
|
|
148
|
+
self.stateful.insert(root);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if reached.stateful {
|
|
153
|
+
self.stateful.insert(root);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fn frozen(mut self) -> Frozen {
|
|
158
|
+
let objects: Vec<_> = self.parents.keys().copied().collect();
|
|
159
|
+
let mut frozen = Frozen::default();
|
|
160
|
+
for object in objects {
|
|
161
|
+
let root = self.root(object);
|
|
162
|
+
if self.stateful.contains(&root) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
frozen.members.entry(root).or_default().push(object);
|
|
166
|
+
frozen.groups.insert(object, root);
|
|
167
|
+
frozen.shared.get_mut().insert(object);
|
|
168
|
+
}
|
|
169
|
+
frozen
|
|
170
|
+
}
|
|
171
|
+
}
|