Skip to main content

rapx/analysis/path/
mod.rs

1pub mod default;
2pub mod graph;
3
4use crate::utils::source::get_fn_name_byid;
5use rustc_hir::def_id::DefId;
6use std::fmt::{self, Display};
7
8use crate::compat::{FxHashMap, FxHashSet};
9use graph::{InlineBinding, PathGraph};
10
11/// Format a path slice with cleanup-block annotations.
12///
13/// Cleanup blocks (MIR unwind/drop paths) are marked with a `*` suffix.
14/// Example: `[0, 1, 2*, 3]` where block 2 is a cleanup block.
15pub fn format_path_annotated(path: &[usize], graph: &PathGraph<'_>) -> String {
16    let blocks: Vec<String> = path
17        .iter()
18        .map(|&b| {
19            if graph.is_cleanup_block(b) {
20                format!("{}*", b)
21            } else {
22                b.to_string()
23            }
24        })
25        .collect();
26    format!("[{}]", blocks.join(", "))
27}
28
29/// A prefix-tree (trie) of whole-CFG paths sharing common prefixes.
30///
31/// Paths are sequences of MIR block indices.  The trie compresses shared
32/// prefixes — if two paths `[0,1,2,5]` and `[0,1,2,6]` are both inserted,
33/// blocks 0→1→2 are stored once and branch at block 2.
34///
35/// Each node is a [`PathNode`]; a node with `is_path_end == true` marks the
36/// end of a complete path that exists in the tree.  The `len` field tracks
37/// the number of complete paths stored.
38///
39/// # Invariants
40/// - All paths inserted into a given tree start with the same root block
41///   (by construction, block 0 — the CFG entry).
42/// - A path is only stored if it passed reachability filtering
43///   (see [`PathGraph::check_transition`]).
44#[derive(Debug, Clone)]
45pub struct PathTree {
46    root: Option<PathNode>,
47    len: usize,
48    /// Per-global-block function ownership: `(def_id, local_index)`. Empty for
49    /// single-function trees; populated after CFG inlining so consumers can
50    /// resolve each block's owning function and its MIR block index.
51    block_fn: Vec<(DefId, usize)>,
52    /// Argument/return bindings for inlined callee entry blocks.
53    inline_bindings: FxHashMap<usize, InlineBinding>,
54    /// Per-inlined-callee parent: entry block (global index) → the `DefId` of
55    /// the function that called it. Lets the boundary injector distinguish a
56    /// *nested* callee (its body is split around a further-inlined callee) from
57    /// a *sibling* call when both appear as consecutive non-caller def_ids.
58    inline_parents: FxHashMap<usize, DefId>,
59    /// Caller blocks whose `Call` terminator was inlined.
60    inlined_call_blocks: FxHashSet<usize>,
61    /// Set when enumeration stopped at a path or depth limit, so the tree
62    /// holds only some of the paths.
63    truncated: bool,
64}
65
66/// A node in a [`PathTree`] trie.
67///
68/// `block` is the MIR block index for this node.  `children` holds
69/// successor blocks that appear after `block` in at least one stored
70/// path.  `is_path_end` is `true` when some path terminates at this
71/// node (i.e. this block is a CFG terminator for that path).
72#[derive(Debug, Clone)]
73pub struct PathNode {
74    pub block: usize,
75    pub children: Vec<PathNode>,
76    pub is_path_end: bool,
77}
78
79impl PathNode {
80    fn from_path(path: &[usize]) -> Self {
81        let mut node = PathNode {
82            block: path[0],
83            children: Vec::new(),
84            is_path_end: path.len() == 1,
85        };
86        if path.len() > 1 {
87            node.children.push(PathNode::from_path(&path[1..]));
88        }
89        node
90    }
91}
92
93impl PathTree {
94    pub fn new() -> Self {
95        PathTree {
96            root: None,
97            len: 0,
98            block_fn: Vec::new(),
99            inline_bindings: FxHashMap::default(),
100            inline_parents: FxHashMap::default(),
101            inlined_call_blocks: FxHashSet::default(),
102            truncated: false,
103        }
104    }
105
106    pub fn len(&self) -> usize {
107        self.len
108    }
109
110    pub fn is_truncated(&self) -> bool {
111        self.truncated
112    }
113
114    pub fn mark_truncated(&mut self) {
115        self.truncated = true;
116    }
117
118    pub fn is_empty(&self) -> bool {
119        self.len == 0
120    }
121
122    pub fn root(&self) -> Option<&PathNode> {
123        self.root.as_ref()
124    }
125
126    /// Set the per-global-block function ownership map `(def_id, local_index)`
127    /// and the inlined callee argument bindings.
128    pub fn set_block_fn(
129        &mut self,
130        map: Vec<(DefId, usize)>,
131        bindings: FxHashMap<usize, InlineBinding>,
132        parents: FxHashMap<usize, DefId>,
133        inlined_calls: FxHashSet<usize>,
134    ) {
135        self.block_fn = map;
136        self.inline_bindings = bindings;
137        self.inline_parents = parents;
138        self.inlined_call_blocks = inlined_calls;
139    }
140
141    /// Resolve `block` to `(def_id, local_index)`, or `None` for single-function
142    /// trees (where `block` is already a local index of the single function).
143    pub fn block_fn_of(&self, block: usize) -> Option<(DefId, usize)> {
144        self.block_fn.get(block).copied()
145    }
146
147    /// All `(def_id, local_index)` entries, indexed by global block number.
148    pub fn block_fns(&self) -> &[(DefId, usize)] {
149        &self.block_fn
150    }
151
152    /// Argument/return binding for an inlined callee entry block.
153    pub fn inline_binding(&self, block: usize) -> Option<&InlineBinding> {
154        self.inline_bindings.get(&block)
155    }
156
157    /// The `DefId` of the function that called an inlined callee entry block.
158    pub fn inline_parent(&self, block: usize) -> Option<DefId> {
159        self.inline_parents.get(&block).copied()
160    }
161
162    /// Whether `block` (a caller block) had its `Call` terminator inlined.
163    pub fn is_inlined_call(&self, block: usize) -> bool {
164        self.inlined_call_blocks.contains(&block)
165    }
166
167    /// Insert a path into the tree. Returns `true` if the path was
168    /// newly added (not already present as a terminal path).
169    pub fn insert(&mut self, path: &[usize]) -> bool {
170        if path.is_empty() {
171            return false;
172        }
173
174        match &mut self.root {
175            None => {
176                self.root = Some(PathNode::from_path(path));
177                self.len = 1;
178                true
179            }
180            Some(root) => {
181                if root.block != path[0] {
182                    return false;
183                }
184                if Self::insert_into(root, &path[1..]) {
185                    self.len += 1;
186                    true
187                } else {
188                    false
189                }
190            }
191        }
192    }
193
194    fn insert_into(node: &mut PathNode, suffix: &[usize]) -> bool {
195        if suffix.is_empty() {
196            if node.is_path_end {
197                return false;
198            }
199            node.is_path_end = true;
200            return true;
201        }
202
203        let target = suffix[0];
204        for child in &mut node.children {
205            if child.block == target {
206                return Self::insert_into(child, &suffix[1..]);
207            }
208        }
209
210        node.children.push(PathNode::from_path(suffix));
211        true
212    }
213
214    /// Check whether the given path exists as a complete path in the tree.
215    pub fn contains(&self, path: &[usize]) -> bool {
216        let mut node = match self.root.as_ref() {
217            Some(n) => n,
218            None => return false,
219        };
220        if node.block != path[0] {
221            return false;
222        }
223        for &block in &path[1..] {
224            node = match node.children.iter().find(|c| c.block == block) {
225                Some(n) => n,
226                None => return false,
227            };
228        }
229        node.is_path_end
230    }
231
232    /// Enumerate all paths as owned `Vec<usize>`.
233    pub fn iter(&self) -> PathTreeIter<'_> {
234        PathTreeIter {
235            stack: self
236                .root
237                .as_ref()
238                .map(|r| vec![(r, vec![r.block])])
239                .unwrap_or_default(),
240        }
241    }
242
243    /// Collect all paths into a flat `Vec<Vec<usize>>`.
244    pub fn to_vecs(&self) -> Vec<Vec<usize>> {
245        self.iter().collect()
246    }
247
248    /// Walk the tree and call `f` with each unique prefix that ends at
249    /// `target_block`. The walk stops at `target_block` (does not recurse
250    /// into its children), so the callback receives the path from the root
251    /// up to and including `target_block`.
252    ///
253    /// Returns `Ok(())` if the walk completed, or `Err(())` if `f` returned
254    /// `false` to request early termination.
255    pub fn walk_prefixes<F>(&self, target_block: usize, f: &mut F) -> Result<(), ()>
256    where
257        F: FnMut(&[usize]) -> bool,
258    {
259        let Some(root) = self.root.as_ref() else {
260            return Ok(());
261        };
262        let mut path = Vec::new();
263        Self::walk_prefixes_impl(root, &mut path, target_block, false, f)
264    }
265
266    /// Like [`walk_prefixes`] but continues past the target block into
267    /// children, finding ALL occurrences (e.g. multiple iterations of the
268    /// same checkpoint block in a loop).
269    pub fn walk_all_prefixes<F>(&self, target_block: usize, f: &mut F) -> Result<(), ()>
270    where
271        F: FnMut(&[usize]) -> bool,
272    {
273        let Some(root) = self.root.as_ref() else {
274            return Ok(());
275        };
276        let mut path = Vec::new();
277        Self::walk_prefixes_impl(root, &mut path, target_block, true, f)
278    }
279
280    fn walk_prefixes_impl<F>(
281        node: &PathNode,
282        path: &mut Vec<usize>,
283        target_block: usize,
284        continue_past_target: bool,
285        f: &mut F,
286    ) -> Result<(), ()>
287    where
288        F: FnMut(&[usize]) -> bool,
289    {
290        path.push(node.block);
291        if node.block == target_block {
292            let cont = f(path);
293            if !cont {
294                path.pop();
295                return Err(());
296            }
297            if !continue_past_target {
298                path.pop();
299                return Ok(());
300            }
301        }
302        for child in &node.children {
303            Self::walk_prefixes_impl(child, path, target_block, continue_past_target, f)?;
304        }
305        path.pop();
306        Ok(())
307    }
308}
309
310impl Default for PathTree {
311    fn default() -> Self {
312        Self::new()
313    }
314}
315
316/// DFS iterator over all complete paths in a [`PathTree`].
317///
318/// Yields each path as an owned `Vec<usize>`.  Internal nodes (where
319/// `is_path_end == false`) are skipped; only terminal path nodes are
320/// emitted.
321pub struct PathTreeIter<'a> {
322    stack: Vec<(&'a PathNode, Vec<usize>)>,
323}
324
325impl<'a> Iterator for PathTreeIter<'a> {
326    type Item = Vec<usize>;
327
328    fn next(&mut self) -> Option<Self::Item> {
329        loop {
330            let (node, path) = self.stack.pop()?;
331            for child in node.children.iter().rev() {
332                let mut child_path = path.clone();
333                child_path.push(child.block);
334                self.stack.push((child, child_path));
335            }
336            if node.is_path_end {
337                return Some(path);
338            }
339        }
340    }
341}
342
343/// Display wrapper that prints all paths for every function, annotated
344/// with cleanup-block markers via [`format_path_annotated`].
345pub struct PathMapWrapper<'a, 'tcx>(
346    pub FxHashMap<DefId, PathTree>,
347    pub &'a FxHashMap<DefId, PathGraph<'tcx>>,
348);
349
350impl Display for PathMapWrapper<'_, '_> {
351    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352        writeln!(f, "=== Print path analysis results ===")?;
353        for (def_id, paths) in &self.0 {
354            let fn_name = get_fn_name_byid(def_id);
355            if fn_name.contains("__raw_ptr_deref_dummy") {
356                continue;
357            }
358            writeln!(f, "Function: {:?}:", fn_name)?;
359            let graph = self.1.get(def_id);
360            for path in paths.iter() {
361                if let Some(g) = graph {
362                    writeln!(f, "  Path {}", format_path_annotated(&path, g))?;
363                } else {
364                    writeln!(f, "  Path {:?}", path)?;
365                }
366            }
367        }
368        Ok(())
369    }
370}