rapx/verify/slicer/
visitor.rs

1//! Backward path visitor — walks a finite path backward from a checkpoint and
2//! keeps only MIR items that can affect the required property.
3//!
4//! The def-use layer lives in [`super::super::def_use`]; this module focuses on
5//! the path-level control flow decisions: calls, SCC exits, and path-condition
6//! branches.
7
8use rustc_hir::def_id::DefId;
9use rustc_middle::mir::Body;
10use rustc_middle::mir::{BasicBlock, StatementKind, TerminatorKind};
11use rustc_middle::ty::TyCtxt;
12
13use crate::analysis::dataflow::graph::build_dataflow_graph;
14use crate::analysis::dataflow::types::DataflowGraph;
15
16use super::super::{
17    contract,
18    def_use::{RelevantPlaces, bind_callsite_roots, operand_uses, terminator_use_def},
19    helpers::{Checkpoint, CheckpointLocation},
20    path_extractor::{Path, PathStep},
21};
22
23use crate::analysis::path_analysis::{PathNode, PathTree};
24
25use super::{
26    call_visit,
27    types::{BackwardItem, ForgetReason, KeepReason, RelevantMirItems},
28};
29
30/// Entry point for backward path visiting.
31pub struct BackwardSlicer<'tcx> {
32    tcx: TyCtxt<'tcx>,
33}
34
35impl<'tcx> BackwardSlicer<'tcx> {
36    /// Create a backward visitor over the current compiler type context.
37    pub fn new(tcx: TyCtxt<'tcx>) -> Self {
38        Self { tcx }
39    }
40
41    /// Return the compiler type context owned by this visitor.
42    pub fn tcx(&self) -> TyCtxt<'tcx> {
43        self.tcx
44    }
45
46    /// Visit a path tree in post-order, sharing backward analysis across
47    /// common prefixes. Merges child-relevance sets at branch nodes (the
48    /// union is a sound over-approximation). Returns per-leaf results.
49    ///
50    /// Callee parameter roots are bound at checkpoint nodes.
51    pub fn visit_path_tree(
52        &self,
53        tree: &PathTree,
54        target_block: usize,
55        checkpoint: &Checkpoint<'tcx>,
56        property: &contract::Property<'tcx>,
57    ) -> Vec<RelevantMirItems<'tcx>> {
58        self.visit_path_tree_impl(
59            tree,
60            target_block,
61            checkpoint.caller,
62            checkpoint.block,
63            Some(checkpoint),
64            property,
65        )
66    }
67
68    /// Like [`visit_path_tree`] but without callee-root binding (used for
69    /// struct-invariant checks where property places are already in the
70    /// caller's local namespace).
71    pub fn visit_path_tree_for_checkpoint(
72        &self,
73        tree: &PathTree,
74        target_block: usize,
75        caller: DefId,
76        checkpoint_loc: CheckpointLocation,
77        property: &contract::Property<'tcx>,
78    ) -> Vec<RelevantMirItems<'tcx>> {
79        self.visit_path_tree_impl(
80            tree,
81            target_block,
82            caller,
83            checkpoint_loc.block,
84            None,
85            property,
86        )
87    }
88
89    /// Internal: post-order recursion returning per-leaf
90    /// `(block_path, backward_items)`.
91    fn visit_path_tree_impl(
92        &self,
93        tree: &PathTree,
94        target_block: usize,
95        caller: DefId,
96        checkpoint_block: BasicBlock,
97        bind_checkpoint: Option<&Checkpoint<'tcx>>,
98        property: &contract::Property<'tcx>,
99    ) -> Vec<RelevantMirItems<'tcx>> {
100        let Some(root) = tree.root() else {
101            return Vec::new();
102        };
103        let checkpoint_loc = CheckpointLocation {
104            caller,
105            block: checkpoint_block,
106        };
107        let body = self.tcx.optimized_mir(caller);
108        let flow = build_dataflow_graph(self.tcx, caller);
109        let keep_alloc = matches!(
110            property.kind,
111            contract::PropertyKind::Allocated
112                | contract::PropertyKind::Deref
113                | contract::PropertyKind::ValidPtr
114        );
115
116        let leaf_results = Self::build_leaf_items(
117            self,
118            root,
119            target_block,
120            checkpoint_block,
121            bind_checkpoint,
122            property,
123            &body,
124            &flow,
125            keep_alloc,
126        );
127
128        let mut results = Vec::new();
129        for (block_path, backward_items, _relevant) in leaf_results {
130            let mut items = backward_items;
131            items.reverse();
132            let steps: Vec<PathStep> = block_path
133                .iter()
134                .map(|&b| PathStep::Block(BasicBlock::from(b)))
135                .chain(std::iter::once(PathStep::Checkpoint(checkpoint_loc)))
136                .collect();
137            results.push(RelevantMirItems {
138                checkpoint: checkpoint_loc,
139                property: property.clone(),
140                path: Path {
141                    target: checkpoint_loc,
142                    steps,
143                },
144                items,
145                roots: RelevantPlaces::from_property(property),
146            });
147        }
148        results
149    }
150
151    /// Post-order recursion: returns one `(block_path, backward_items,
152    /// relevant_before_block)` per checkpoint leaf. Each leaf is independent
153    /// — no merging, no HashMap collision.
154    fn build_leaf_items(
155        visitor: &Self,
156        node: &PathNode,
157        target_block: usize,
158        checkpoint_block: BasicBlock,
159        bind_checkpoint: Option<&Checkpoint<'tcx>>,
160        property: &contract::Property<'tcx>,
161        body: &'tcx rustc_middle::mir::Body<'tcx>,
162        flow: &DataflowGraph,
163        keep_alloc: bool,
164    ) -> Vec<(Vec<usize>, Vec<BackwardItem<'tcx>>, RelevantPlaces)> {
165        let block = BasicBlock::from(node.block);
166        let block_data = &body.basic_blocks[block];
167        let mut results = Vec::new();
168
169        // Build the checkpoint-layer items when this block IS the target.
170        let (checkpoint_items, checkpoint_relevant) = if node.block == target_block {
171            let mut relevant = RelevantPlaces::from_property(property);
172            if let Some(cs) = bind_checkpoint {
173                bind_callsite_roots(visitor.tcx, &mut relevant, cs);
174            }
175            let mut items = Vec::new();
176            items.push(BackwardItem::Terminator {
177                block: checkpoint_block,
178                kind: KeepReason::Checkpoint,
179            });
180            for (si, stmt) in block_data.statements.iter().enumerate().rev() {
181                visitor.visit_statement(
182                    checkpoint_block,
183                    si,
184                    stmt,
185                    flow,
186                    &mut relevant,
187                    &mut items,
188                    keep_alloc,
189                );
190            }
191            (items, relevant)
192        } else {
193            (Vec::new(), RelevantPlaces::new())
194        };
195
196        // Process children — even when this is the target block,
197        // deeper checkpoint occurrences may hide below.
198        for child in &node.children {
199            let child_results = Self::build_leaf_items(
200                visitor,
201                child,
202                target_block,
203                checkpoint_block,
204                bind_checkpoint,
205                property,
206                body,
207                flow,
208                keep_alloc,
209            );
210            for (mut child_path, child_items, child_relevant) in child_results {
211                let mut relevant = child_relevant;
212                let mut items = child_items;
213                // Always thread through so the backward chain reaches
214                // function entry even for child (deeper SCC) paths,
215                // otherwise allocation/initialization facts are missing.
216                visitor.visit_terminator(
217                    block,
218                    block_data.terminator(),
219                    flow,
220                    body,
221                    &mut relevant,
222                    &mut items,
223                    keep_alloc,
224                );
225                for (si, stmt) in block_data.statements.iter().enumerate().rev() {
226                    visitor.visit_statement(
227                        block,
228                        si,
229                        stmt,
230                        flow,
231                        &mut relevant,
232                        &mut items,
233                        keep_alloc,
234                    );
235                }
236                child_path.insert(0, node.block);
237                results.push((child_path, items, relevant));
238            }
239        }
240
241        // Produce a leaf for this checkpoint occurrence.
242        if !checkpoint_items.is_empty() {
243            results.push((vec![node.block], checkpoint_items, checkpoint_relevant));
244        }
245
246        results
247    }
248
249    /// Visit one MIR statement against the current relevance frontier.
250    fn visit_statement(
251        &self,
252        block: BasicBlock,
253        statement_index: usize,
254        statement: &'tcx rustc_middle::mir::Statement<'tcx>,
255        flow: &DataflowGraph,
256        relevant: &mut RelevantPlaces,
257        items: &mut Vec<BackwardItem<'tcx>>,
258        keep_allocation_invalidations: bool,
259    ) {
260        if keep_allocation_invalidations && matches!(statement.kind, StatementKind::StorageDead(_))
261        {
262            items.push(BackwardItem::Statement {
263                block,
264                statement_index,
265                kind: KeepReason::Invalidation,
266            });
267            return;
268        }
269
270        let mut defs = RelevantPlaces::new();
271        match &statement.kind {
272            StatementKind::Assign(assign) => {
273                let (place, _) = &**assign;
274                defs.insert_mir_place(place);
275            }
276            StatementKind::StorageDead(local) => {
277                defs.insert_local(*local);
278            }
279            _ => {}
280        }
281
282        if defs.intersects(relevant) {
283            let uses = collect_statement_uses(statement, block, statement_index, flow);
284            items.push(BackwardItem::Statement {
285                block,
286                statement_index,
287                kind: statement_keep_reason(statement),
288            });
289            relevant.remove_all(&defs);
290            relevant.extend(uses);
291            return;
292        }
293
294        if statement_invalidates_relevant(statement, relevant) {
295            items.push(BackwardItem::Statement {
296                block,
297                statement_index,
298                kind: KeepReason::Invalidation,
299            });
300        } else if statement_can_refine(statement) {
301            let mut uses = RelevantPlaces::new();
302            for &local in &defs.locals {
303                for &edge_idx in &flow.node(local).in_edges {
304                    let edge = &flow.edges[edge_idx];
305                    if edge.block == block.as_usize() && edge.statement_index == statement_index {
306                        uses.insert_local(edge.src);
307                    }
308                }
309            }
310            if uses.intersects(relevant) {
311                items.push(BackwardItem::Statement {
312                    block,
313                    statement_index,
314                    kind: KeepReason::RuntimeCheck,
315                });
316            }
317        }
318    }
319
320    /// Visit one MIR terminator against the current relevance frontier.
321    fn visit_terminator(
322        &self,
323        block: BasicBlock,
324        terminator: &rustc_middle::mir::Terminator<'tcx>,
325        flow: &DataflowGraph,
326        body: &Body<'tcx>,
327        relevant: &mut RelevantPlaces,
328        items: &mut Vec<BackwardItem<'tcx>>,
329        keep_allocation_invalidations: bool,
330    ) {
331        if keep_allocation_invalidations && matches!(terminator.kind, TerminatorKind::Drop { .. }) {
332            items.push(BackwardItem::Terminator {
333                block,
334                kind: KeepReason::Invalidation,
335            });
336            return;
337        }
338
339        if let TerminatorKind::Call {
340            func,
341            args,
342            destination,
343            ..
344        } = &terminator.kind
345        {
346            call_visit::visit(
347                self.tcx,
348                block,
349                func,
350                args,
351                destination,
352                flow,
353                body,
354                relevant,
355                items,
356            );
357            return;
358        }
359
360        let use_def = terminator_use_def(terminator);
361        if terminator_is_path_condition(terminator) {
362            items.push(BackwardItem::Terminator {
363                block,
364                kind: KeepReason::PathCondition,
365            });
366            relevant.extend(use_def.uses.clone());
367            return;
368        }
369
370        if use_def.defs.intersects(relevant) {
371            if terminator_may_havoc(terminator) {
372                items.push(BackwardItem::Forget {
373                    reason: ForgetReason::UnknownCall,
374                });
375            }
376            items.push(BackwardItem::Terminator {
377                block,
378                kind: terminator_definition_reason(terminator),
379            });
380            relevant.remove_all(&use_def.defs);
381            relevant.extend(use_def.uses);
382            return;
383        }
384
385        if use_def.uses.intersects(relevant) {
386            if terminator_may_havoc(terminator) {
387                items.push(BackwardItem::Forget {
388                    reason: ForgetReason::UnknownCall,
389                });
390            }
391            items.push(BackwardItem::Terminator {
392                block,
393                kind: terminator_use_reason(terminator),
394            });
395        }
396    }
397}
398
399// ── classification helpers ──────────────────────────────────────────────
400
401fn statement_keep_reason(statement: &rustc_middle::mir::Statement<'_>) -> KeepReason {
402    match &statement.kind {
403        StatementKind::Assign(assign) => {
404            let (_, rvalue) = &**assign;
405            match rvalue {
406                rustc_middle::mir::Rvalue::Ref(_, _, _)
407                | rustc_middle::mir::Rvalue::RawPtr(_, _)
408                | rustc_middle::mir::Rvalue::Cast(_, _, _)
409                | rustc_middle::mir::Rvalue::CopyForDeref(_)
410                | rustc_middle::mir::Rvalue::BinaryOp(_, _) => KeepReason::PointerFlow,
411                _ => KeepReason::Definition,
412            }
413        }
414        StatementKind::StorageDead(_) => KeepReason::Invalidation,
415        _ => KeepReason::Definition,
416    }
417}
418
419fn statement_can_refine(statement: &rustc_middle::mir::Statement<'_>) -> bool {
420    matches!(&statement.kind, StatementKind::Assign(assign) if matches!(
421        &**assign,
422        (
423            _,
424            rustc_middle::mir::Rvalue::BinaryOp(_, _)
425            | rustc_middle::mir::Rvalue::UnaryOp(_, _)
426            | rustc_middle::mir::Rvalue::Cast(_, _, _),
427        )
428    ))
429}
430
431fn statement_invalidates_relevant(
432    statement: &rustc_middle::mir::Statement<'_>,
433    relevant: &RelevantPlaces,
434) -> bool {
435    match &statement.kind {
436        StatementKind::StorageDead(local) => relevant.locals.contains(local),
437        _ => false,
438    }
439}
440
441fn terminator_is_path_condition(terminator: &rustc_middle::mir::Terminator<'_>) -> bool {
442    matches!(
443        terminator.kind,
444        TerminatorKind::SwitchInt { .. } | TerminatorKind::Assert { .. }
445    )
446}
447
448fn terminator_definition_reason(terminator: &rustc_middle::mir::Terminator<'_>) -> KeepReason {
449    match terminator.kind {
450        TerminatorKind::Call { .. } => KeepReason::UnknownEffect,
451        _ => KeepReason::Definition,
452    }
453}
454
455fn terminator_use_reason(terminator: &rustc_middle::mir::Terminator<'_>) -> KeepReason {
456    match terminator.kind {
457        TerminatorKind::SwitchInt { .. } | TerminatorKind::Assert { .. } => {
458            KeepReason::PathCondition
459        }
460        TerminatorKind::Drop { .. } => KeepReason::Invalidation,
461        TerminatorKind::Call { .. } => KeepReason::UnknownEffect,
462        _ => KeepReason::UnknownEffect,
463    }
464}
465
466fn terminator_may_havoc(terminator: &rustc_middle::mir::Terminator<'_>) -> bool {
467    matches!(terminator.kind, TerminatorKind::Call { .. })
468}
469
470/// Collect all place-uses for a statement from dataflow edges and operands.
471fn collect_statement_uses<'tcx>(
472    statement: &'tcx rustc_middle::mir::Statement<'tcx>,
473    block: BasicBlock,
474    statement_index: usize,
475    flow: &DataflowGraph,
476) -> RelevantPlaces {
477    let mut uses = RelevantPlaces::new();
478
479    // Collect def locals (we know there are defs — caller already checked)
480    let def_locals = match &statement.kind {
481        StatementKind::Assign(assign) => {
482            let (place, _) = &**assign;
483            vec![place.local]
484        }
485        StatementKind::StorageDead(local) => vec![*local],
486        _ => Vec::new(),
487    };
488
489    for &local in &def_locals {
490        for &edge_idx in &flow.node(local).in_edges {
491            let edge = &flow.edges[edge_idx];
492            if edge.block == block.as_usize() && edge.statement_index == statement_index {
493                uses.insert_local(edge.src);
494            }
495        }
496    }
497
498    // Also collect uses directly from operands — the dataflow graph
499    // creates synthetic nodes for field projections (e.g. _13.0),
500    // so we need the direct operand uses to reach through.
501    if let StatementKind::Assign(assign) = &statement.kind {
502        let (_, rvalue) = &**assign;
503        for operand in super::super::def_use::rvalue_operands(rvalue) {
504            uses.extend(operand_uses(operand));
505        }
506    }
507
508    uses
509}