Skip to main content

rapx/verify/
path_extractor.rs

1//! Path extraction for verification targets.
2//!
3//! This module builds finite, acyclic paths from a function CFG to each unsafe checkpoint
4//! so that the verifier can reason about pointer properties along concrete execution
5//! traces without unrolling loops or recursive cycles.
6//!
7//! # Path reachability
8//!
9//! Each path is validated against a `PathGraph` (an SCC-aware path enumeration
10//! structure) to ensure the computed block sequence is actually reachable. Paths
11//! that fail this check are silently discarded.
12//!
13//! # Path limit
14//!
15//! To prevent exponential blow-up, path enumeration is capped at
16//! [`crate::limit::path_limit`] (the `--path-limit` CLI value, or
17//! [`crate::limit::PATH_LIMIT`]). Enumeration stops producing new paths once
18//! the limit is reached and the tree is marked truncated.
19
20use crate::compat::FxHashMap;
21use rustc_hir::def_id::DefId;
22use rustc_middle::{mir::BasicBlock, ty::TyCtxt};
23
24use crate::analysis::path::{
25    PathTree,
26    graph::{PathEnumerator, PathGraph},
27};
28
29use crate::helpers::mir_scan::{Checkpoint, CheckpointLocation};
30
31/// Enumerates finite, SCC-aware verification paths from the function entry
32/// to each unsafe checkpoint in a single function body.
33///
34/// `PathExtractor` is the **first stage** of the verification pipeline.  It
35/// takes a function's MIR control-flow graph and a list of unsafe checkpoints,
36/// then produces a [`CheckpointPaths`] value that maps every checkpoint to a set
37/// of acyclic block-level paths reaching it.
38///
39/// # Pipeline role
40///
41/// ```text
42/// PathExtractor ──► BackwardSlicer ──► ForwardVerifier ──► SmtChecker
43///    (paths)          (relevant items)    (abstract facts)    (satisfiability)
44/// ```
45///
46/// The paths produced here determine *which* MIR instructions the slicer
47/// will inspect and in what order, so path quality directly affects
48/// verification precision.
49///
50/// # SCC handling
51///
52/// Loops (strongly connected components) are detected and collapsed by
53/// [`PathGraph`].  The `allow_repeat` parameter controls how many extra
54/// iterations of each SCC postfix are appended beyond the first — useful
55/// for modeling loop-carried effects without unrolling indefinitely.
56///
57/// # Path limit
58///
59/// Whole-CFG path enumeration is capped at
60/// [`crate::limit::path_limit`] (the `--path-limit` CLI value, or
61/// [`crate::limit::PATH_LIMIT`]) to prevent exponential blow-up in
62/// functions with many branches.
63pub(crate) struct PathExtractor<'tcx> {
64    /// Compiler type context used for MIR access and name resolution.
65    tcx: TyCtxt<'tcx>,
66    /// The function whose MIR body is being analyzed.
67    def_id: DefId,
68    /// Unsafe checkpoints (call terminators, raw-ptr derefs, static mut accesses)
69    /// discovered in the function's MIR body.
70    checkpoints: Vec<Checkpoint<'tcx>>,
71    /// Number of extra SCC postfix repetitions allowed (default 0).
72    allow_repeat: usize,
73}
74
75impl<'tcx> PathExtractor<'tcx> {
76    /// Create a path extractor for `def_id` and the checkpoints found in that body.
77    ///
78    /// Path extraction (SCC detection, enumeration, filtering) is deferred
79    /// until [`run`] is called.
80    ///
81    /// `allow_repeat` controls how many times a repeated SCC postfix segment
82    /// is allowed beyond the first occurrence. Default is 0 (no extra repeats).
83    pub(crate) fn new(
84        tcx: TyCtxt<'tcx>,
85        def_id: DefId,
86        checkpoints: Vec<Checkpoint<'tcx>>,
87        allow_repeat: usize,
88    ) -> Self {
89        Self {
90            tcx,
91            def_id,
92            checkpoints,
93            allow_repeat,
94        }
95    }
96
97    /// Run path extraction, consuming the extractor.
98    ///
99    /// Returns a `Vec<CallGroup>` — one entry per unique callee DefId.
100    /// Checkpoints that target the same callee share a single `PathTree`,
101    /// avoiding redundant subtree construction.
102    pub(crate) fn run(self) -> Vec<CallGroup<'tcx>> {
103        let mut graph = PathGraph::new(self.tcx, self.def_id);
104        graph.inline_callees();
105        graph.find_scc();
106        let mut tree = {
107            let mut enumerator = PathEnumerator::new(&graph);
108            enumerator.enumerate_paths_repeat(self.allow_repeat)
109        };
110        tree.set_block_fn(
111            graph
112                .cfg
113                .blocks
114                .iter()
115                .map(|b| (b.def_id, b.local_index))
116                .collect(),
117            graph.inline_bindings.clone(),
118            graph.inline_parents.clone(),
119            graph.inlined_call_blocks.clone(),
120        );
121        group_by_callee(self.checkpoints, &tree)
122    }
123}
124
125/// Checkpoints targeting the same callee, grouped for shared path analysis.
126///
127/// One `CallGroup` is produced per unique callee `DefId` in the function.
128/// All checkpoints in the group share the same `PathTree`; individual paths
129/// are filtered by `checkpoint.block` at query time.
130pub(crate) struct CallGroup<'tcx> {
131    /// Shared full-CFG prefix tree, built once for all checkpoints in the group.
132    pub tree: PathTree,
133    /// Checkpoints that target this callee.
134    pub checkpoints: Vec<Checkpoint<'tcx>>,
135}
136
137fn group_by_callee<'tcx>(
138    checkpoints: Vec<Checkpoint<'tcx>>,
139    tree: &PathTree,
140) -> Vec<CallGroup<'tcx>> {
141    let mut groups: FxHashMap<Option<DefId>, Vec<Checkpoint<'tcx>>> = FxHashMap::default();
142    for cs in checkpoints {
143        groups.entry(cs.callee).or_default().push(cs);
144    }
145    groups
146        .into_iter()
147        .map(|(_callee, checkpoints)| CallGroup {
148            tree: tree.clone(),
149            checkpoints,
150        })
151        .collect()
152}
153
154/// One finite, acyclic execution trace from the function entry to a target
155/// checkpoint, represented as an ordered sequence of MIR basic blocks.
156///
157/// A `Path` is the core currency flowing through the verification pipeline.
158/// It is produced by [`PathExtractor`], then consumed by the
159/// [`BackwardSlicer`](super::slicer::BackwardSlicer) to determine which MIR
160/// instructions are relevant to the safety property being checked.
161///
162/// # Structure
163///
164/// Each path consists of:
165/// - **`target`** — the [`CheckpointLocation`] identifying the specific call
166///   terminator (or synthetic checkpoint) this path reaches.  Redundant
167///   with the final step but stored separately for fast lookup.
168/// - **`steps`** — an ordered list of [`PathStep`] values.  Intermediate
169///   steps are MIR basic blocks; the final step is always
170///   `PathStep::Checkpoint(target)`.  The path always starts at function
171///   entry (block `bb0`).
172///
173/// # Reachability
174///
175/// Every `Path` returned by `PathExtractor` is guaranteed to be *reachable*
176/// in the function's CFG.  Paths that cannot be validated against the
177/// SCC-aware [`PathGraph`] are silently discarded during extraction.
178///
179/// # Example (compact notation)
180///
181/// ```text
182/// bb2 -> bb3 -> bb4 -> checkpoint@bb5::unsafe_fn
183/// ```
184///
185/// In the path above, blocks 2, 3, and 4 are intermediate MIR blocks;
186/// block 5 contains the unsafecall terminator that the path targets.
187#[derive(Clone, Debug)]
188pub(crate) struct Path {
189    /// The checkpoint location (function + basic block) reached by this path.
190    pub target: CheckpointLocation,
191    /// Ordered sequence of basic blocks from function entry to `target`,
192    /// terminated by a `PathStep::Checkpoint`.
193    pub steps: Vec<PathStep>,
194}
195
196impl Path {
197    /// Render this path as a compact array of block indices.
198    pub(crate) fn describe_indices(&self) -> String {
199        let mut indices: Vec<usize> = Vec::new();
200        for step in &self.steps {
201            match step {
202                PathStep::Block(b) => indices.push(b.as_usize()),
203                PathStep::Checkpoint(l) => {
204                    let bb = l.block.as_usize();
205                    if indices.last() != Some(&bb) {
206                        indices.push(bb);
207                    }
208                }
209            }
210        }
211        format!("{:?}", indices)
212    }
213
214    /// Whether this path re-enters a block it already visited (a loop-unrolled
215    /// iteration, not a genuinely distinct step).
216    pub(crate) fn reenters(&self) -> bool {
217        let mut seen = std::collections::HashSet::new();
218        self.steps.iter().any(|s| match s {
219            PathStep::Block(b) => !seen.insert(b.as_usize()),
220            _ => false,
221        })
222    }
223
224}
225
226/// One step in a finite verification path.
227#[derive(Clone, Debug)]
228pub(crate) enum PathStep {
229    /// A normal MIR basic block.
230    Block(BasicBlock),
231    /// The target checkpoint that terminates the path.
232    Checkpoint(CheckpointLocation),
233}