rapx/verify/
driver.rs

1//! Driver utilities for the staged verifier pipeline.
2//!
3//! The target collector owns selected functions and their callee requirements.
4//! The path extractor upgrades a function CFG into SCC-aware path metadata.
5//! `VerifyDriver` prepares paths for two kinds of checks (unsafe checkpoints and
6//! struct invariants) and delegates the actual backward/forward/SMT work to
7//! the shared `VerifyEngine`.
8
9use crate::analysis::Analysis;
10use crate::analysis::path_analysis::{
11    PathTree,
12    graph::{PathEnumerator, PathGraph},
13};
14use crate::cli::VerifyMode;
15use crate::helpers::fn_info::{FnKind, get_cons, get_mutated_fields, get_muts, get_type};
16use crate::verify::contract::PropertyKind;
17use crate::verify::target::get_contract_from_annotation;
18
19use crate::compat::{FxHashMap, FxHashSet};
20use indexmap::IndexMap;
21use rustc_middle::mir::BasicBlock;
22use rustc_middle::ty::TyCtxt;
23use std::panic::{AssertUnwindSafe, catch_unwind};
24
25use super::{
26    contract::Property,
27    engine::VerifyEngine,
28    helpers::{Checkpoint, CheckpointKind, CheckpointLocation, collect_return_block_indices},
29    path_extractor::{CallGroup, PATH_LIMIT, PathExtractor},
30    report::{PropertyCheckResult, VerificationReport, VisitDiagnostics},
31    slicer::BackwardItem,
32    target::{FunctionTarget, VerifyTargetCollector},
33};
34
35/// Orchestrates the three-stage verification pipeline (backward data-dependency
36/// analysis → forward state simulation → SMT checking) for a single function
37/// under analysis.
38///
39/// Each `VerifyDriver` instance bundles together:
40///
41/// 1. The **problem statement** (`target`) — which unsafe checkpoints and
42///    raw-pointer dereferences exist, what safety contracts they demand, and
43///    what entry assumptions (from `#[rapx::requires]`) and struct invariants
44///    apply.
45///
46/// 2. The **reachability model** (`path_info`) — SCC-aware acyclic paths
47///    from function entry to each checkpoint, produced by flattening the MIR
48///    control-flow graph with bounded loop unrolling.
49///
50/// 3. The **verification engine** (`engine`) — a stateless pipeline shared
51///    across all (checkpoint, path, property) triples.
52///
53/// 4. The **loop-unrolling budget** (`allow_repeat`) — caps how many extra
54///    iterations a loop body may appear beyond its first occurrence, trading
55///    completeness against path enumeration cost.
56///
57/// Verification proceeds in two phases per driver instance:
58/// - [`verify_function`](Self::verify_function): checks safety properties at
59///   each unsafe checkpoint (callee `#[rapx::requires]` contracts).
60/// - [`verify_struct_invariants`](Self::verify_struct_invariants): checks
61///   struct invariants at return-block checkpoints (constructors) or at all
62///   path endpoints (non-constructor methods).
63pub struct VerifyDriver<'target, 'tcx> {
64    /// Compiler type-context handle — gateway to MIR bodies, type definitions,
65    /// HIR attributes, and def-path strings used throughout the pipeline.
66    tcx: TyCtxt<'tcx>,
67
68    /// The function being verified: its identity (`def_id`), the unsafe
69    /// operations inside it (`checkpoints`, `raw_ptr_deref_checks`), the
70    /// contracts those operations demand (`callee_requires`), the contracts
71    /// the function itself requires as entry assumptions
72    /// (`caller_requires`), and any struct invariants to be enforced
73    /// (`struct_invariants`).
74    target: &'target FunctionTarget<'tcx>,
75
76    /// SCC-aware path metadata for this function.
77    ///
78    /// Per-callee call groups with shared path trees.
79    path_info: Vec<CallGroup<'tcx>>,
80
81    /// Stateless three-stage verification pipeline: backward data-dependency
82    /// analysis → forward state simulation → SMT constraint checking.
83    /// Shared across all (checkpoint, path, property) triples for this target.
84    engine: VerifyEngine<'tcx>,
85
86    /// Loop-unrolling depth for SCC-aware path enumeration.
87    ///
88    /// Controls how many **extra** times a repeated SCC postfix segment
89    /// (loop-body) is allowed to appear beyond its first occurrence.
90    /// - `0` = each distinct postfix segment at most once (no loop repeats).
91    /// - `1` = allow one repeat (loop body appears up to twice).
92    /// - `n` = allow n repeats (loop body appears up to n+1 times).
93    ///
94    /// Higher values increase path coverage but risk exponential blow-up in
95    /// path count.  The CLI driver iterates `repeat` from 0 to the configured
96    /// maximum, accumulating results incrementally.
97    allow_repeat: usize,
98}
99
100impl<'target, 'tcx> VerifyDriver<'target, 'tcx> {
101    /// Build a driver for one collected function target.
102    pub fn new(tcx: TyCtxt<'tcx>, target: &'target FunctionTarget<'tcx>) -> Self {
103        Self::new_with_repeat(tcx, target, 0)
104    }
105
106    /// Build a driver with control over SCC postfix repeat count.
107    pub fn new_with_repeat(
108        tcx: TyCtxt<'tcx>,
109        target: &'target FunctionTarget<'tcx>,
110        allow_repeat: usize,
111    ) -> Self {
112        let mut all_checkpoints = target.checkpoints.clone();
113        for (checkpoint, _) in &target.raw_ptr_deref_checks {
114            all_checkpoints.push(checkpoint.clone());
115        }
116        for (checkpoint, _) in &target.static_mut_checks {
117            all_checkpoints.push(checkpoint.clone());
118        }
119        let path_info = PathExtractor::new(tcx, target.def_id, all_checkpoints, allow_repeat).run();
120        let engine = VerifyEngine::new(tcx);
121        Self {
122            tcx,
123            target,
124            path_info,
125            engine,
126            allow_repeat,
127        }
128    }
129
130    /// Return the compiler type context owned by this driver.
131    pub fn tcx(&self) -> TyCtxt<'tcx> {
132        self.tcx
133    }
134
135    /// Return the function target managed by this driver.
136    pub fn target(&self) -> &'target FunctionTarget<'tcx> {
137        self.target
138    }
139
140    /// Return the per-callee call groups managed by this driver.
141    pub fn path_info(&self) -> &[CallGroup<'tcx>] {
142        &self.path_info
143    }
144
145    /// Run unsafe-checkpoint verification for the managed function target.
146    pub fn verify_function(&self) -> VerificationReport<'tcx> {
147        let mut report = VerificationReport::new(self.target.def_id);
148
149        for view in self.iter_callsite_checks() {
150            for (property_index, property) in view.properties.iter().enumerate() {
151                let bulk = self.engine.check_callsite_from_tree(
152                    view.tree,
153                    view.checkpoint,
154                    property,
155                    &self.target.caller_requires,
156                );
157                for (path_index, (forward, smt_check)) in bulk.iter().enumerate() {
158                    let check_diagnostics =
159                        format!("{}\n{}", forward.describe(), smt_check.describe());
160                    report.push(PropertyCheckResult {
161                        checkpoint: view.checkpoint.location(),
162                        checkpoint_index: view.checkpoint_index,
163                        path_index,
164                        property_index,
165                        property: property.clone(),
166                        result: smt_check.result.clone(),
167                        diagnostics: Some(VisitDiagnostics::new(String::new(), check_diagnostics)),
168                        path_description: forward.path.describe_indices(),
169                        callee_name: view.checkpoint.callee_name(self.tcx),
170                    });
171                }
172            }
173        }
174
175        report
176    }
177
178    /// Return the required properties for a concrete unsafe checkpoint.
179    ///
180    /// Dispatches on [`CheckpointKind`]: synthetic checkpoints (raw pointer
181    /// dereference, static mut access) carry their properties in
182    /// `target.raw_ptr_deref_checks` / `target.static_mut_checks`; real
183    /// unsafe calls look up `target.callee_requires` by callee `DefId`.
184    pub fn properties_for_callsite(
185        &self,
186        checkpoint: &Checkpoint<'tcx>,
187    ) -> &'target [Property<'tcx>] {
188        let loc = checkpoint.location();
189        match checkpoint.kind {
190            CheckpointKind::RawPtrDeref => {
191                for (cs, props) in &self.target.raw_ptr_deref_checks {
192                    if cs.location() == loc {
193                        return props.as_slice();
194                    }
195                }
196                &[]
197            }
198            CheckpointKind::StaticMutAccess => {
199                for (cs, props) in &self.target.static_mut_checks {
200                    if cs.location() == loc {
201                        return props.as_slice();
202                    }
203                }
204                &[]
205            }
206            CheckpointKind::UnsafeCall => {
207                if let Some(callee) = checkpoint.callee {
208                    self.target
209                        .callee_requires
210                        .get(&callee)
211                        .map(Vec::as_slice)
212                        .unwrap_or(&[])
213                } else {
214                    &[]
215                }
216            }
217        }
218    }
219
220    /// Iterate over checkpoints together with their shared path tree and properties.
221    pub fn iter_callsite_checks(
222        &self,
223    ) -> impl Iterator<Item = CheckpointCheckView<'_, 'target, 'tcx>> + '_ {
224        let mut checkpoint_index = 0usize;
225        self.path_info.iter().flat_map(move |group| {
226            group.checkpoints.iter().filter_map(move |checkpoint| {
227                let properties = self.properties_for_callsite(checkpoint);
228                if properties.is_empty() {
229                    return None;
230                }
231                let view = CheckpointCheckView {
232                    checkpoint_index,
233                    checkpoint,
234                    tree: &group.tree,
235                    properties,
236                };
237                checkpoint_index += 1;
238                Some(view)
239            })
240        })
241    }
242
243    /// Run struct invariant verification for the managed function target.
244    ///
245    /// For constructors (functions returning `Self`), paths are filtered to
246    /// return blocks to avoid unwinding paths where the struct may not be
247    /// fully initialised. For methods, all whole-CFG paths from
248    /// `PathGraph::enumerate_paths_repeat` are used directly.
249    pub fn verify_struct_invariants(&self) -> VerificationReport<'tcx> {
250        let mut report = VerificationReport::new(self.target.def_id);
251        let invariants = &self.target.struct_invariants;
252        if invariants.is_empty() {
253            return report;
254        }
255
256        let is_constructor = get_type(self.tcx, self.target.def_id) == FnKind::Constructor;
257        let caller_contracts = &self.target.caller_requires;
258
259        let entry_facts: Vec<BackwardItem<'tcx>> = if is_constructor {
260            caller_contracts
261                .iter()
262                .filter(|c| !matches!(c.kind, PropertyKind::Unknown))
263                .map(|c| BackwardItem::ContractFact {
264                    property: c.clone(),
265                })
266                .collect()
267        } else {
268            invariants
269                .iter()
270                .map(|inv| BackwardItem::ContractFact {
271                    property: inv.clone(),
272                })
273                .collect()
274        };
275
276        for (checkpoint, tree) in self.build_invariant_trees(is_constructor) {
277            rap_debug!(
278                "[rapx::verify] struct invariant checkpoint bb{}: {} tree node(s)",
279                checkpoint.block.as_usize(),
280                tree.len()
281            );
282
283            for (property_index, invariant) in invariants.iter().enumerate() {
284                let results = self.engine.check_invariant_from_tree(
285                    self.target.def_id,
286                    &tree,
287                    checkpoint,
288                    invariant,
289                    &entry_facts,
290                );
291
292                for (path_index, check) in results.iter().enumerate() {
293                    report.push(PropertyCheckResult {
294                        checkpoint: checkpoint,
295                        checkpoint_index: checkpoint.block.as_usize(),
296                        path_index,
297                        property_index,
298                        property: invariant.clone(),
299                        result: check.result.clone(),
300                        diagnostics: Some(VisitDiagnostics::new(
301                            check.slicing_diag.clone(),
302                            check.verification_diag.clone(),
303                        )),
304                        path_description: String::new(),
305                        callee_name: format!("struct-invariant(bb{})", checkpoint.block.as_usize()),
306                    });
307                }
308            }
309        }
310
311        report
312    }
313
314    fn build_invariant_trees(
315        &self,
316        is_constructor: bool,
317    ) -> FxHashMap<CheckpointLocation, PathTree> {
318        let mut pg = PathGraph::new(self.tcx, self.target.def_id);
319        pg.find_scc();
320        let mut enumerator = PathEnumerator::new(&pg);
321        let all_paths = enumerator.enumerate_paths_repeat(self.allow_repeat);
322
323        let kind_label = if is_constructor {
324            "constructor"
325        } else {
326            "method"
327        };
328        rap_debug!(
329            "[rapx::verify] struct invariant ({kind_label}): {} whole-cfg path(s) for {}",
330            all_paths.len(),
331            self.tcx.def_path_str(self.target.def_id),
332        );
333
334        let mut trees_by_checkpoint: FxHashMap<CheckpointLocation, PathTree> = FxHashMap::default();
335
336        if is_constructor {
337            let return_blocks = collect_return_block_indices(self.tcx, self.target.def_id);
338            for &return_block in &return_blocks {
339                let checkpoint = CheckpointLocation {
340                    caller: self.target.def_id,
341                    block: return_block,
342                };
343                let mut tree = PathTree::new();
344                let _ = all_paths.walk_prefixes(
345                    return_block.as_usize(),
346                    &mut |prefix: &[usize]| -> bool {
347                        if tree.len() >= PATH_LIMIT {
348                            return false;
349                        }
350                        tree.insert(prefix);
351                        true
352                    },
353                );
354                if !tree.is_empty() {
355                    trees_by_checkpoint.insert(checkpoint, tree);
356                }
357            }
358        } else {
359            let mut seen_paths = FxHashSet::default();
360            for path in all_paths.iter() {
361                if path.is_empty() {
362                    continue;
363                }
364                if !seen_paths.insert(path.clone()) {
365                    continue;
366                }
367                let last_block = BasicBlock::from(*path.last().unwrap());
368                let checkpoint = CheckpointLocation {
369                    caller: self.target.def_id,
370                    block: last_block,
371                };
372                trees_by_checkpoint
373                    .entry(checkpoint)
374                    .or_insert_with(PathTree::new)
375                    .insert(path.as_slice());
376            }
377        }
378
379        trees_by_checkpoint
380    }
381}
382
383/// Returns whether a function returns the owning struct type (i.e. is a constructor).
384/// Borrowed view of all verification inputs for one unsafe checkpoint.
385pub struct CheckpointCheckView<'view, 'target, 'tcx> {
386    /// Position among checkpoints that have properties to verify.
387    pub checkpoint_index: usize,
388    /// The concrete unsafe checkpoint in the caller MIR body.
389    pub checkpoint: &'view Checkpoint<'tcx>,
390    /// Per-checkpoint prefix tree of all verification paths to this checkpoint.
391    pub tree: &'view PathTree,
392    /// Required safety properties for the unsafe callee.
393    pub properties: &'target [Property<'tcx>],
394}
395
396/// Analysis pass that runs verification and emits function-level summaries.
397pub struct VerifyRun<'tcx> {
398    tcx: TyCtxt<'tcx>,
399    postfix_repeat: usize,
400    mode: VerifyMode,
401    crate_filter: Option<String>,
402    module_filter: Option<String>,
403}
404
405impl<'tcx> VerifyRun<'tcx> {
406    /// Create the default verify pass for the current compiler type context.
407    pub fn new(
408        tcx: TyCtxt<'tcx>,
409        postfix_repeat: usize,
410        mode: VerifyMode,
411        crate_filter: Option<String>,
412        module_filter: Option<String>,
413    ) -> Self {
414        Self {
415            tcx,
416            postfix_repeat,
417            mode,
418            crate_filter,
419            module_filter,
420        }
421    }
422
423    /// In invless mode, generate verification sequences for each read method
424    /// that chain through constructors and mutators.
425    ///
426    /// Produces sequences like:
427    /// - `constructor → method`
428    /// - `constructor → mutator → method`
429    ///
430    /// Each sequence propagates the constructor's `#[rapx::requires]` through
431    /// the mutator chain to serve as entry assumptions for the read method.
432    fn run_invless_sequences(&self, targets: &[FunctionTarget<'tcx>]) {
433        for target in targets {
434            let read_def_id = target.def_id;
435            let cons = get_cons(self.tcx, read_def_id);
436            if cons.is_empty() {
437                continue;
438            }
439            let muts = get_muts(self.tcx, read_def_id);
440
441            for &con_id in &cons {
442                let con_target = self.build_virtual_target(target, read_def_id, con_id, &[]);
443                self.verify_and_emit_sequence(target, read_def_id, &con_target, con_id, &[], 0);
444
445                for (mut_idx, &mut_id) in muts.iter().enumerate() {
446                    let con_target =
447                        self.build_virtual_target(target, read_def_id, con_id, &[mut_id]);
448                    self.verify_and_emit_sequence(
449                        target,
450                        read_def_id,
451                        &con_target,
452                        con_id,
453                        &[mut_id],
454                        1 + mut_idx,
455                    );
456                }
457            }
458        }
459    }
460
461    fn build_virtual_target(
462        &self,
463        read_target: &FunctionTarget<'tcx>,
464        read_def_id: rustc_hir::def_id::DefId,
465        con_id: rustc_hir::def_id::DefId,
466        mut_ids: &[rustc_hir::def_id::DefId],
467    ) -> FunctionTarget<'tcx> {
468        let mut accumulated_requires: Vec<Property<'tcx>> = Vec::new();
469
470        // Start with the constructor's requires
471        let con_contracts = get_contract_from_annotation(self.tcx, con_id);
472        accumulated_requires.extend(con_contracts);
473
474        // Remove contracts that are invalidated by mutators
475        if !mut_ids.is_empty() {
476            let mut mutated_fields: Vec<usize> = Vec::new();
477            for &mut_id in mut_ids {
478                for field_idx in get_mutated_fields(self.tcx, mut_id) {
479                    if !mutated_fields.contains(&field_idx) {
480                        mutated_fields.push(field_idx);
481                    }
482                }
483            }
484            if !mutated_fields.is_empty() {
485                accumulated_requires.retain(|prop| {
486                    let prop_fields = property_field_indices(prop);
487                    !prop_fields.iter().any(|f| mutated_fields.contains(f))
488                });
489            }
490        }
491
492        // Also include the read method's own requires
493        let own_requires = get_contract_from_annotation(self.tcx, read_def_id);
494        for req in own_requires {
495            if !accumulated_requires.iter().any(|p| same_property(p, &req)) {
496                accumulated_requires.push(req);
497            }
498        }
499
500        FunctionTarget {
501            def_id: read_def_id,
502            owner_struct_def_id: read_target.owner_struct_def_id,
503            checkpoints: read_target.checkpoints.clone(),
504            callee_requires: read_target.callee_requires.clone(),
505            caller_requires: accumulated_requires,
506            struct_invariants: Vec::new(),
507            raw_ptr_deref_checks: read_target.raw_ptr_deref_checks.clone(),
508            static_mut_checks: read_target.static_mut_checks.clone(),
509        }
510    }
511
512    fn verify_and_emit_sequence(
513        &self,
514        _read_target: &FunctionTarget<'tcx>,
515        read_def_id: rustc_hir::def_id::DefId,
516        con_target: &FunctionTarget<'tcx>,
517        con_id: rustc_hir::def_id::DefId,
518        mut_ids: &[rustc_hir::def_id::DefId],
519        _seq_index: usize,
520    ) {
521        let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
522
523        for repeat in 0..=self.postfix_repeat {
524            let driver = VerifyDriver::new_with_repeat(self.tcx, con_target, repeat);
525            let result = catch_unwind(AssertUnwindSafe(|| driver.verify_function()));
526            match result {
527                Ok(report) => {
528                    rap_debug!("{}", report.describe());
529                    all_results.extend(report.results);
530                }
531                Err(e) => {
532                    let msg = e
533                        .downcast_ref::<String>()
534                        .map(|s| s.as_str())
535                        .or_else(|| e.downcast_ref::<&str>().copied())
536                        .unwrap_or("<rustc ICE>");
537                    rap_warn!(
538                        "Skipping invless constructor {} (repeat {}): {}",
539                        self.tcx.def_path_str(con_id),
540                        repeat,
541                        msg
542                    );
543                    all_results.clear();
544                    break;
545                }
546            }
547        }
548
549        let read_name = short_fn_name(self.tcx, read_def_id);
550        let con_name = short_fn_name(self.tcx, con_id);
551        let mut chain_parts: Vec<String> = vec![con_name];
552        for &mut_id in mut_ids {
553            chain_parts.push(short_fn_name(self.tcx, mut_id));
554        }
555        chain_parts.push(read_name);
556        let chain_label = chain_parts.join(" -> ");
557
558        rap_info!("============================================================");
559        rap_info!("[rapx::verify] sequence: {chain_label}");
560        rap_info!("============================================================");
561
562        let unproved = all_results
563            .iter()
564            .filter(|r| !matches!(r.result, super::report::CheckResult::Proved))
565            .count();
566
567        let mut groups: IndexMap<(CheckpointLocation, String), Vec<&PropertyCheckResult<'_>>> =
568            IndexMap::new();
569        for r in &all_results {
570            groups
571                .entry((r.checkpoint, r.callee_name.clone()))
572                .or_default()
573                .push(r);
574        }
575
576        let checkpoint_groups: Vec<_> = groups
577            .iter()
578            .filter(|((_, name), _)| !name.starts_with("struct-invariant"))
579            .collect();
580
581        if !checkpoint_groups.is_empty() {
582            rap_info!("  --- unsafe checkpoints ---");
583            for ((checkpoint, callee_name), results) in &checkpoint_groups {
584                rap_info!(
585                    "      unsafe checkpoint: bb{} -> {callee_name}",
586                    checkpoint.block.as_usize(),
587                );
588                emit_property_rows(results);
589            }
590        }
591
592        if unproved == 0 && !all_results.is_empty() {
593            rap_info!(green,"  result: SOUND");
594        } else {
595            rap_warn!("  result: UNSOUND ({unproved} unproved)");
596        }
597        rap_info!("");
598    }
599}
600
601impl<'tcx> Analysis for VerifyRun<'tcx> {
602    fn name(&self) -> &'static str {
603        "Verify Driver"
604    }
605
606    /// Collect verify targets, run the staged driver, and emit a compact summary.
607    ///
608    /// For each target, extracts paths with increasing `postfix-repeat`
609    /// levels from 0 to the configured maximum, running verification at each
610    /// level. Earlier rounds use fewer loop unrollings; later rounds incrementally
611    /// add deeper paths.
612    fn run(&mut self) {
613        let mut collector = VerifyTargetCollector::new(
614            self.tcx,
615            self.mode,
616            self.crate_filter.clone(),
617            self.module_filter.clone(),
618        );
619        self.tcx.hir_visit_all_item_likes_in_crate(&mut collector);
620        collector.check_module_filter_result();
621
622        for target in &collector.function_targets {
623            let target_path = self.tcx.def_path_str(target.def_id);
624            let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
625
626            // Phase 1: unsafe checkpoint verification
627            for repeat in 0..=self.postfix_repeat {
628                let driver = VerifyDriver::new_with_repeat(self.tcx, target, repeat);
629                let result = catch_unwind(AssertUnwindSafe(|| driver.verify_function()));
630                match result {
631                    Ok(report) => {
632                        rap_debug!("{}", report.describe());
633                        all_results.extend(report.results);
634                    }
635                    Err(e) => {
636                        let msg = e
637                            .downcast_ref::<String>()
638                            .map(|s| s.as_str())
639                            .or_else(|| e.downcast_ref::<&str>().copied())
640                            .unwrap_or("<rustc ICE>");
641                        rap_warn!(
642                            "Skipping function {} (repeat {}): {}",
643                            target_path,
644                            repeat,
645                            msg
646                        );
647                        all_results.clear();
648                        break;
649                    }
650                }
651            }
652
653            // Phase 1.5: InBound loop-depth detector
654            //
655            // When postfix_repeat == 0, a loop body appears at most once per path,
656            // which can miss off-by-one bugs that only manifest on later iterations.
657            //
658            // The detector tries progressively deeper unrolling and tracks the
659            // set of Proved InBound properties at each level.  Three stopping criteria:
660            //
661            //  1. Unproven InBound found → unsound, propagate results, stop.
662            //  2. State converged (same Proven set as a previous level) → stop.
663            //  3. State oscillates (Proven set alternates between two states) → stop.
664            const MAX_LOOP_INBOUND_UNROLL: usize = 16;
665
666            if self.postfix_repeat == 0 && !all_results.is_empty() {
667                let has_inbound = all_results
668                    .iter()
669                    .any(|r| matches!(r.property.kind, PropertyKind::InBound));
670                let all_inbound_proven = all_results
671                    .iter()
672                    .filter(|r| matches!(r.property.kind, PropertyKind::InBound))
673                    .all(|r| matches!(r.result, super::report::CheckResult::Proved));
674
675                if has_inbound && all_inbound_proven {
676                    // Proven-set history: each entry is a sorted vector of
677                    // (checkpoint_index, property_index) for Proved InBound results.
678                    let mut proven_history: Vec<Vec<(usize, usize)>> = Vec::new();
679
680                    // Seed with the repeat=0 Proven set.
681                    let seed: Vec<_> = {
682                        let mut v: Vec<_> = all_results
683                            .iter()
684                            .filter(|r| {
685                                matches!(r.property.kind, PropertyKind::InBound)
686                                    && matches!(r.result, super::report::CheckResult::Proved)
687                            })
688                            .map(|r| (r.checkpoint_index, r.property_index))
689                            .collect();
690                        v.sort();
691                        v
692                    };
693                    proven_history.push(seed);
694
695                    for repeat in 1..=MAX_LOOP_INBOUND_UNROLL {
696                        let detector_driver =
697                            VerifyDriver::new_with_repeat(self.tcx, target, repeat);
698                        let result =
699                            catch_unwind(AssertUnwindSafe(|| detector_driver.verify_function()));
700                        match result {
701                            Ok(report) => {
702                                // Collect genuinely non-Proved InBound (excluding
703                                // SMT precision-loss noise).
704                                let current_unproven: FxHashSet<_> = report
705                                    .results
706                                    .iter()
707                                    .filter(|r| {
708                                        matches!(r.property.kind, PropertyKind::InBound)
709                                            && !matches!(
710                                                r.result,
711                                                super::report::CheckResult::Proved
712                                            )
713                                            && !r
714                                                .diagnostics
715                                                .as_ref()
716                                                .is_some_and(|d| {
717                                                    d.forward.contains("path has precision loss")
718                                                        || d.forward
719                                                            .contains("could not connect")
720                                                })
721                                    })
722                                    .map(|r| (r.checkpoint_index, r.property_index))
723                                    .collect();
724
725                                // Pattern 1: violation found.
726                                if !current_unproven.is_empty() {
727                                    all_results.extend(report.results);
728                                    break;
729                                }
730
731                                // Collect Proven InBound set for this level.
732                                let mut current_proven: Vec<_> = report
733                                    .results
734                                    .iter()
735                                    .filter(|r| {
736                                        matches!(r.property.kind, PropertyKind::InBound)
737                                            && matches!(
738                                                r.result,
739                                                super::report::CheckResult::Proved
740                                            )
741                                    })
742                                    .map(|r| (r.checkpoint_index, r.property_index))
743                                    .collect();
744                                current_proven.sort();
745
746                                // Pattern 2: same Proven set as any prior level.
747                                if proven_history.contains(&current_proven) {
748                                    break;
749                                }
750
751                                // Pattern 3: oscillation — alternation between two sets.
752                                let len = proven_history.len();
753                                if len >= 3
754                                    && proven_history[len - 1] == proven_history[len - 3]
755                                    && current_proven == proven_history[len - 2]
756                                {
757                                    break;
758                                }
759
760                                proven_history.push(current_proven);
761                            }
762                            Err(_) => break,
763                        }
764                    }
765                }
766            }
767
768            // Phase 2: struct invariant verification
769            if !target.struct_invariants.is_empty() && !matches!(self.mode, VerifyMode::Invless) {
770                let driver = VerifyDriver::new_with_repeat(self.tcx, target, self.postfix_repeat);
771                let struct_report = driver.verify_struct_invariants();
772                rap_debug!("{}", struct_report.describe());
773                all_results.extend(struct_report.results);
774            }
775
776            if all_results.is_empty() {
777                if target.checkpoints.is_empty()
778                    && target.raw_ptr_deref_checks.is_empty()
779                    && target.static_mut_checks.is_empty()
780                    && target.struct_invariants.is_empty()
781                {
782                    rap_info!("============================================================");
783                    rap_info!("[rapx::verify] function: {target_path}");
784                    rap_info!("============================================================");
785                    if matches!(self.mode, VerifyMode::Invless) {
786                        let cons = get_cons(self.tcx, target.def_id);
787                        for con in &cons {
788                            rap_info!("  + constructor: {}", self.tcx.def_path_str(*con));
789                        }
790                    }
791                    rap_info!("  --- unsafe checkpoints ---");
792                    rap_info!("      <none>");
793                    rap_info!("        <none>");
794                    rap_info!("  result: SOUND (no unsafe checkpoints)");
795                    rap_info!("");
796                }
797                continue;
798            }
799
800            // In invless mode, skip standalone emission for methods that
801            // have constructors — sequences will generate dedicated entries.
802            if matches!(self.mode, VerifyMode::Invless)
803                && !get_cons(self.tcx, target.def_id).is_empty()
804            {
805                continue;
806            }
807
808            emit_verify_summary(
809                self.tcx,
810                &target_path,
811                target.def_id,
812                &all_results,
813                self.mode,
814            );
815        }
816
817        // Emit detected unsafe trait impls (verification deferred)
818        if !collector.trait_targets.is_empty() {
819            let mut trait_ids: Vec<_> = collector.trait_targets.keys().copied().collect();
820            trait_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
821            for trait_def_id in trait_ids {
822                let Some(trait_target) = collector.trait_targets.get(&trait_def_id) else {
823                    continue;
824                };
825                rap_info!("============================================================");
826                rap_info!(
827                    "[rapx::verify] unsafe trait impl: {}",
828                    self.tcx.def_path_str(trait_target.def_id)
829                );
830                rap_info!("============================================================");
831                if let Some(self_ty) = trait_target.self_ty_def_id {
832                    rap_info!("  impl for: {}", self.tcx.def_path_str(self_ty));
833                }
834                if trait_target.ensures.is_empty() {
835                    rap_info!("  ensures: <none>");
836                } else {
837                    rap_info!("  ensures (implementor must satisfy):");
838                    for (method_name, contracts) in &trait_target.ensures {
839                        rap_info!("    fn {}:", method_name);
840                        for property in contracts {
841                            rap_info!("      - {:?}, args={:?}", property.kind, property.args);
842                        }
843                    }
844                }
845                rap_info!("  verification: deferred");
846                rap_info!("");
847            }
848        }
849
850        // Invless mode: generate constructor-mutator-method sequences
851        if matches!(self.mode, VerifyMode::Invless) {
852            self.run_invless_sequences(&collector.function_targets);
853        }
854    }
855
856    fn reset(&mut self) {}
857}
858
859fn emit_verify_summary<'tcx>(
860    tcx: TyCtxt<'tcx>,
861    target_path: &str,
862    def_id: rustc_hir::def_id::DefId,
863    all_results: &[PropertyCheckResult<'tcx>],
864    mode: VerifyMode,
865) {
866    let unproved = all_results
867        .iter()
868        .filter(|r| !matches!(r.result, super::report::CheckResult::Proved))
869        .count();
870
871    rap_info!("============================================================");
872    rap_info!("[rapx::verify] function: {target_path}");
873    rap_info!("============================================================");
874
875    if matches!(mode, VerifyMode::Invless) {
876        let cons = get_cons(tcx, def_id);
877        for con in &cons {
878            rap_info!("  + constructor: {}", tcx.def_path_str(*con));
879        }
880    }
881
882    // Group results by (checkpoint, callee_name)
883    let mut groups: IndexMap<(CheckpointLocation, String), Vec<&PropertyCheckResult<'_>>> =
884        IndexMap::new();
885    for r in all_results {
886        groups
887            .entry((r.checkpoint, r.callee_name.clone()))
888            .or_default()
889            .push(r);
890    }
891
892    // Separate into checkpoint groups and struct-invariant groups
893    let checkpoint_groups: Vec<_> = groups
894        .iter()
895        .filter(|((_, name), _)| !name.starts_with("struct-invariant"))
896        .collect();
897    let invariant_groups: Vec<_> = groups
898        .iter()
899        .filter(|((_, name), _)| name.starts_with("struct-invariant"))
900        .collect();
901
902    // Print unsafe checkpoint results
903    if !checkpoint_groups.is_empty() {
904        rap_info!("  --- unsafe checkpoints ---");
905        for ((checkpoint, callee_name), results) in &checkpoint_groups {
906            rap_info!(
907                "      unsafe checkpoint: bb{} -> {callee_name}",
908                checkpoint.block.as_usize(),
909            );
910            emit_property_rows(results);
911        }
912    }
913
914    // Print struct invariant results
915    if !invariant_groups.is_empty() {
916        rap_info!("  --- struct invariants ---");
917        for ((checkpoint, _), results) in &invariant_groups {
918            rap_info!("      checkpoint bb{}:", checkpoint.block.as_usize(),);
919            emit_property_rows(results);
920        }
921    }
922
923    if unproved == 0 {
924        rap_info!(green,"  result: SOUND");
925    } else {
926        rap_warn!("  result: UNSOUND ({unproved} unproved)");
927    }
928
929    rap_info!("");
930}
931
932fn emit_property_rows(results: &[&PropertyCheckResult<'_>]) {
933    let mut path_groups: FxHashMap<&str, Vec<_>> = FxHashMap::default();
934    for r in results.iter() {
935        path_groups
936            .entry(r.path_description.as_str())
937            .or_default()
938            .push(r);
939    }
940    for (path_desc, props) in &path_groups {
941        rap_info!("        path {path_desc}:");
942        for r in props.iter() {
943            let line = format!("          {:?} | {:?}", r.property.kind, r.result);
944            if matches!(r.result, super::report::CheckResult::Proved) {
945                rap_info!(green,"{line}");
946            } else {
947                rap_warn!("{line}");
948            }
949        }
950    }
951}
952
953/// Analysis pass that dumps backward and forward visitor diagnostics.
954pub struct VerifyVisitDump<'tcx> {
955    tcx: TyCtxt<'tcx>,
956    postfix_repeat: usize,
957    mode: VerifyMode,
958}
959
960/// Extract the last segment of a def-path (the bare function name).
961fn short_fn_name(tcx: TyCtxt<'_>, def_id: rustc_hir::def_id::DefId) -> String {
962    let path = tcx.def_path_str(def_id);
963    path.rsplit("::").next().unwrap_or(&path).to_string()
964}
965
966/// Return true when two properties have the same kind.
967fn same_property(
968    a: &crate::verify::contract::Property<'_>,
969    b: &crate::verify::contract::Property<'_>,
970) -> bool {
971    matches!(
972        (&a.kind, &b.kind),
973        (PropertyKind::Align, PropertyKind::Align)
974            | (PropertyKind::InBound, PropertyKind::InBound)
975            | (PropertyKind::Init, PropertyKind::Init)
976            | (PropertyKind::NonNull, PropertyKind::NonNull)
977            | (PropertyKind::ValidPtr, PropertyKind::ValidPtr)
978    )
979}
980
981/// Collect struct field indices referenced by a property's contract places.
982///
983/// Used to determine which invariants are invalidated when a mutator writes
984/// to specific struct fields.
985fn property_field_indices(property: &crate::verify::contract::Property<'_>) -> Vec<usize> {
986    use crate::verify::contract::{ContractExpr, PropertyArg};
987    let mut indices = Vec::new();
988    for arg in &property.args {
989        let place = match arg {
990            PropertyArg::Place(p) => Some(p),
991            PropertyArg::Expr(ContractExpr::Place(p)) => Some(p),
992            _ => None,
993        };
994        if let Some(place) = place {
995            for proj in &place.projections {
996                let crate::verify::contract::ContractProjection::Field { index, .. } = proj;
997                let idx = *index;
998                if !indices.contains(&idx) {
999                    indices.push(idx);
1000                }
1001            }
1002        }
1003    }
1004    indices
1005}
1006
1007impl<'tcx> VerifyVisitDump<'tcx> {
1008    /// Create a diagnostic dump pass for the current compiler type context.
1009    pub fn new(tcx: TyCtxt<'tcx>, postfix_repeat: usize, mode: VerifyMode) -> Self {
1010        Self {
1011            tcx,
1012            postfix_repeat,
1013            mode,
1014        }
1015    }
1016}
1017
1018impl<'tcx> Analysis for VerifyVisitDump<'tcx> {
1019    fn name(&self) -> &'static str {
1020        "Verify Visitor Diagnostic Dump"
1021    }
1022
1023    /// Collect verify targets and print the current staged visitor output.
1024    fn run(&mut self) {
1025        rap_debug!("======== #[rapx::verify] visitor diagnostics ========");
1026        let mut collector = VerifyTargetCollector::new(self.tcx, self.mode, None, None);
1027        self.tcx.hir_visit_all_item_likes_in_crate(&mut collector);
1028
1029        for target in &collector.function_targets {
1030            let target_path = self.tcx.def_path_str(target.def_id);
1031            rap_debug!(
1032                "[rapx::verify::diagnostics] target: {} (DefId: {:?})",
1033                target_path,
1034                target.def_id
1035            );
1036
1037            for repeat in 0..=self.postfix_repeat {
1038                if self.postfix_repeat > 0 {
1039                    rap_debug!(
1040                        "[rapx::verify::diagnostics] round {}/{}: postfix-repeat={}",
1041                        repeat,
1042                        self.postfix_repeat,
1043                        repeat
1044                    );
1045                }
1046                let driver = VerifyDriver::new_with_repeat(self.tcx, target, repeat);
1047                let result = catch_unwind(AssertUnwindSafe(|| driver.verify_function()));
1048                match result {
1049                    Ok(report) => {
1050                        rap_debug!("{}", report.describe());
1051                    }
1052                    Err(_) => {
1053                        rap_debug!(
1054                            "[rapx::verify::diagnostics] function {} skipped due to ICE",
1055                            self.tcx.def_path_str(target.def_id)
1056                        );
1057                    }
1058                }
1059            }
1060        }
1061
1062        rap_debug!("=======================================");
1063    }
1064
1065    fn reset(&mut self) {}
1066}