Skip to main content

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::{
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, returns_wrapped_self};
16use crate::verify::contract::PropertyKind;
17use crate::verify::target::get_contract_from_annotation;
18
19use crate::compat::{FxHashMap, FxHashSet};
20use rustc_middle::mir::BasicBlock;
21use rustc_middle::ty::TyCtxt;
22
23use super::{
24    contract::Property,
25    display::{
26        dedup_compound_props, emit_results_and_verdict, emit_verify_summary,
27        fmt_contract_expanded, fmt_fn_path_with_bounds, fmt_fn_path_with_generics,
28        fmt_fn_with_params,
29    },
30    engine::VerifyEngine,
31    loop_sensitivity::{LoopSensitivityAnalyzer, RepeatStrategy},
32    path_extractor::{CallGroup, PATH_LIMIT, PathExtractor},
33    report::{CheckResult, PropertyCheckResult, VerificationReport},
34    slicer::RelevantItem,
35    target::{FunctionTarget, VerifyTargetCollector},
36};
37
38use crate::helpers::mir_utils::collect_return_block_indices;
39
40use crate::helpers::mir_scan::{Checkpoint, CheckpointLocation};
41
42/// Orchestrates the three-stage verification pipeline (backward data-dependency
43/// analysis → forward state simulation → SMT checking) for a single function
44/// under analysis.
45///
46/// Each `VerifyDriver` instance bundles together:
47///
48/// 1. The **problem statement** (`target`) — which unsafe checkpoints and
49///    raw-pointer dereferences exist, what safety contracts they demand, and
50///    what entry assumptions (from `#[rapx::requires]`) and struct invariants
51///    apply.
52///
53/// 2. The **reachability model** (`path_info`) — SCC-aware acyclic paths
54///    from function entry to each checkpoint, produced by flattening the MIR
55///    control-flow graph with bounded loop unrolling.
56///
57/// 3. The **verification engine** (`engine`) — a stateless pipeline shared
58///    across all (checkpoint, path, property) triples.
59///
60/// 4. The **loop-unrolling budget** (`allow_repeat`) — caps how many extra
61///    iterations a loop body may appear beyond its first occurrence, trading
62///    completeness against path enumeration cost.
63///
64/// Verification proceeds in two phases per driver instance:
65/// - [`verify_function`](Self::verify_function): checks safety properties at
66///   each unsafe checkpoint (callee `#[rapx::requires]` contracts).
67/// - [`verify_struct_invariants`](Self::verify_struct_invariants): checks
68///   struct invariants at return-block checkpoints (constructors) or at all
69///   path endpoints (non-constructor methods).
70pub struct VerifyDriver<'target, 'tcx> {
71    tcx: TyCtxt<'tcx>,
72
73    target: &'target FunctionTarget<'tcx>,
74
75    path_info: Vec<CallGroup<'tcx>>,
76
77    engine: VerifyEngine<'tcx>,
78
79    allow_repeat: usize,
80}
81
82impl<'target, 'tcx> VerifyDriver<'target, 'tcx> {
83    pub fn new(tcx: TyCtxt<'tcx>, target: &'target FunctionTarget<'tcx>) -> Self {
84        Self::new_with_repeat(tcx, target, 0)
85    }
86
87    pub fn new_with_repeat(
88        tcx: TyCtxt<'tcx>,
89        target: &'target FunctionTarget<'tcx>,
90        allow_repeat: usize,
91    ) -> Self {
92        let all_checkpoints: Vec<_> = target.all_checkpoints().into_iter().cloned().collect();
93        let path_info = PathExtractor::new(tcx, target.def_id, all_checkpoints, allow_repeat).run();
94        Self {
95            tcx,
96            target,
97            path_info,
98            engine: VerifyEngine::new(tcx),
99            allow_repeat,
100        }
101    }
102
103    /// Return the compiler type context owned by this driver.
104    pub fn tcx(&self) -> TyCtxt<'tcx> {
105        self.tcx
106    }
107
108    /// Return the function target managed by this driver.
109    pub fn target(&self) -> &'target FunctionTarget<'tcx> {
110        self.target
111    }
112
113    /// Return the per-callee call groups managed by this driver.
114    pub fn path_info(&self) -> &[CallGroup<'tcx>] {
115        &self.path_info
116    }
117
118    /// Run unsafe-checkpoint verification for the managed function target.
119    pub fn verify_function(&self) -> VerificationReport<'tcx> {
120        let mut report = VerificationReport::new(self.target.def_id);
121
122        for view in self.iter_callsite_checks() {
123            let mut view_results: Vec<PropertyCheckResult<'tcx>> = Vec::new();
124
125            for (property_index, property) in view.properties.iter().enumerate() {
126                if property.is_or() {
127                    self.check_or_property(&mut report, &view, property_index, property);
128                    continue;
129                }
130                let bulk = self.engine.check_callsite_from_tree(
131                    view.tree,
132                    view.checkpoint,
133                    property,
134                    &self.target.caller_requires,
135                );
136                for (path_index, (result, path_desc)) in bulk.iter().enumerate() {
137                    let item = PropertyCheckResult {
138                        checkpoint: view.checkpoint.location(),
139                        checkpoint_index: view.checkpoint_index,
140                        path_index,
141                        property_index,
142                        property: property.clone(),
143                        result: result.clone(),
144                        diagnostics: Some(format!("vm-check: {:?}", result)),
145                        path_description: path_desc.clone(),
146                        callee_name: view.checkpoint.callee_name(self.tcx),
147                    };
148                    view_results.push(item);
149                }
150            }
151
152            for item in view_results {
153                report.push(item);
154            }
155        }
156
157        report
158    }
159
160    fn check_or_property(
161        &self,
162        report: &mut VerificationReport<'tcx>,
163        view: &CheckpointCheckView<'_, '_, 'tcx>,
164        property_index: usize,
165        or_property: &Property<'tcx>,
166    ) {
167        // Per-path final OR result, aggregating the AND of each group.
168        let mut per_path: Vec<Option<(super::report::CheckResult, String)>> = Vec::new();
169
170        for group in or_property.groups().iter() {
171            // Per-path AND result of this group.
172            let mut group_per_path: Vec<Option<(super::report::CheckResult, String)>> = Vec::new();
173            for sub_prop in group.iter() {
174                let bulk = self.engine.check_callsite_from_tree(
175                    view.tree,
176                    view.checkpoint,
177                    sub_prop,
178                    &self.target.caller_requires,
179                );
180                if group_per_path.is_empty() {
181                    group_per_path.resize(bulk.len(), None);
182                }
183                for (path_idx, (result, path_desc)) in bulk.iter().enumerate() {
184                    let slot = group_per_path[path_idx]
185                        .get_or_insert_with(|| (result.clone(), path_desc.clone()));
186                    slot.0 = slot.0.clone().and(result.clone());
187                    if matches!(result, super::report::CheckResult::Failed | super::report::CheckResult::Unknown) {
188                        slot.1 = path_desc.clone();
189                    }
190                }
191            }
192
193            // Fold this group's per-path AND result into the OR.
194            if per_path.is_empty() {
195                per_path.resize(group_per_path.len(), None);
196            }
197            for (path_idx, g) in group_per_path.iter().enumerate() {
198                if let Some((g_result, g_desc)) = g {
199                    let slot = per_path[path_idx]
200                        .get_or_insert_with(|| (g_result.clone(), g_desc.clone()));
201                    slot.0 = slot.0.clone().or(g_result.clone());
202                    if matches!(g_result, super::report::CheckResult::Proved) {
203                        slot.1 = g_desc.clone();
204                    }
205                }
206            }
207        }
208
209        for (path_index, best) in per_path.iter().enumerate() {
210            if let Some((result, path_desc)) = best {
211                report.push(PropertyCheckResult {
212                    checkpoint: view.checkpoint.location(),
213                    checkpoint_index: view.checkpoint_index,
214                    path_index,
215                    property_index,
216                    property: or_property.clone(),
217                    result: result.clone(),
218                    diagnostics: Some(path_desc.clone()),
219                    path_description: path_desc.clone(),
220                    callee_name: view.checkpoint.callee_name(self.tcx),
221                });
222            }
223        }
224    }
225
226    /// Return the required properties for a concrete unsafe checkpoint.
227    ///
228    /// Dispatches on [`CheckpointKind`]: synthetic checkpoints (raw pointer
229    /// dereference, static mut access) carry their properties in
230    /// `target.raw_ptr_deref_checks` / `target.static_mut_checks`; real
231    /// unsafe calls look up `target.callee_requires` by callee `DefId`.
232    pub fn properties_for_callsite(
233        &self,
234        checkpoint: &Checkpoint<'tcx>,
235    ) -> &'target [Property<'tcx>] {
236        self.target.properties_for_callsite(checkpoint)
237    }
238
239    /// Iterate over checkpoints together with their shared path tree and properties.
240    pub fn iter_callsite_checks(
241        &self,
242    ) -> impl Iterator<Item = CheckpointCheckView<'_, 'target, 'tcx>> + '_ {
243        let mut checkpoint_index = 0usize;
244        self.path_info.iter().flat_map(move |group| {
245            group.checkpoints.iter().filter_map(move |checkpoint| {
246                let properties = self.properties_for_callsite(checkpoint);
247                if properties.is_empty() {
248                    return None;
249                }
250                let view = CheckpointCheckView {
251                    checkpoint_index,
252                    checkpoint,
253                    tree: &group.tree,
254                    properties,
255                };
256                checkpoint_index += 1;
257                Some(view)
258            })
259        })
260    }
261
262    /// Run struct invariant verification for the managed function target.
263    ///
264    /// For constructors (functions returning `Self`), paths are filtered to
265    /// return blocks to avoid unwinding paths where the struct may not be
266    /// fully initialised. For methods, all whole-CFG paths from
267    /// `PathGraph::enumerate_paths_repeat` are used directly.
268    pub fn verify_struct_invariants(&self) -> VerificationReport<'tcx> {
269        let mut report = VerificationReport::new(self.target.def_id);
270        let invariants = &self.target.struct_invariants;
271        if invariants.is_empty() {
272            return report;
273        }
274
275        let is_constructor = get_type(self.tcx, self.target.def_id) == FnKind::Constructor;
276        let caller_contracts = &self.target.caller_requires;
277
278        let fn_sig = self.tcx.fn_sig(self.target.def_id).skip_binder();
279        let output = fn_sig.output().skip_binder();
280        let returns_self = is_constructor || output.is_param(0);
281
282        let entry_facts: Vec<RelevantItem<'tcx>> = if is_constructor {
283            caller_contracts
284                .iter()
285                .filter(|c| !matches!(c.kind(), Some(PropertyKind::Unknown)))
286                .map(|c| RelevantItem::ContractFact {
287                    property: c.clone(),
288                })
289                .collect()
290        } else {
291            invariants
292                .iter()
293                .map(|inv| RelevantItem::ContractFact {
294                    property: inv.clone(),
295                })
296                .collect()
297        };
298
299        for (checkpoint, tree) in self.build_invariant_trees(is_constructor) {
300            rap_debug!(
301                "[rapx::verify] struct invariant checkpoint bb{}: {} tree node(s)",
302                checkpoint.block.as_usize(),
303                tree.len()
304            );
305
306            let paths = tree.to_vecs();
307
308            for (property_index, invariant) in invariants.iter().enumerate() {
309                let results = self.engine.check_invariant_from_tree(
310                    self.target.def_id,
311                    &tree,
312                    checkpoint,
313                    invariant,
314                    &entry_facts,
315                );
316
317                for (path_index, (result, _path_desc)) in results.iter().enumerate() {
318                    let path_description = paths
319                        .get(path_index)
320                        .map(|p| {
321                            p.iter()
322                                .map(|b| b.to_string())
323                                .collect::<Vec<_>>()
324                                .join(", ")
325                        })
326                        .unwrap_or_default();
327                    report.push(PropertyCheckResult {
328                        checkpoint: checkpoint,
329                        checkpoint_index: checkpoint.block.as_usize(),
330                        path_index,
331                        property_index,
332                        property: invariant.clone(),
333                        result: result.clone(),
334                        diagnostics: Some(format!("vm-invariant: {:?}", result)),
335                        path_description,
336                        callee_name: format!("struct-invariant(bb{})", checkpoint.block.as_usize()),
337                    });
338                }
339            }
340        }
341
342        // For plain methods, and for "wrapped" constructors (`Result<Self>`,
343        // `Option<Self>`, `Box<Self>`), `Unknown` results are benign: methods
344        // don't construct the struct, and the `Err`/`None` paths of a wrapped
345        // constructor don't produce a `Self` to check. Keep `Unknown` only when
346        // some path actually `Failed`, so a genuine soundness gap still surfaces.
347        let wrapped_self = returns_wrapped_self(self.tcx, self.target.def_id);
348        if (!returns_self && !is_constructor) || (is_constructor && wrapped_self) {
349            let has_failed = report.results.iter().any(|r| matches!(r.result, CheckResult::Failed));
350            if !has_failed {
351                report.results.retain(|r| !matches!(r.result, CheckResult::Unknown));
352            }
353        }
354
355        report
356    }
357
358    fn build_invariant_trees(
359        &self,
360        is_constructor: bool,
361    ) -> FxHashMap<CheckpointLocation, PathTree> {
362        let mut pg = PathGraph::new(self.tcx, self.target.def_id);
363        pg.find_scc();
364        let mut enumerator = PathEnumerator::new(&pg);
365        let all_paths = enumerator.enumerate_paths_repeat(self.allow_repeat);
366
367        let kind_label = if is_constructor {
368            "constructor"
369        } else {
370            "method"
371        };
372        rap_debug!(
373            "[rapx::verify] struct invariant ({kind_label}): {} whole-cfg path(s) for {}",
374            all_paths.len(),
375            self.tcx.def_path_str(self.target.def_id),
376        );
377
378        let mut trees_by_checkpoint: FxHashMap<CheckpointLocation, PathTree> = FxHashMap::default();
379
380        if is_constructor {
381            let return_blocks = collect_return_block_indices(self.tcx, self.target.def_id);
382            for &return_block in &return_blocks {
383                let checkpoint = CheckpointLocation {
384                    caller: self.target.def_id,
385                    block: return_block,
386                };
387                let mut tree = PathTree::new();
388                let _ = all_paths.walk_prefixes(
389                    return_block.as_usize(),
390                    &mut |prefix: &[usize]| -> bool {
391                        if tree.len() >= PATH_LIMIT {
392                            return false;
393                        }
394                        tree.insert(prefix);
395                        true
396                    },
397                );
398                if !tree.is_empty() {
399                    trees_by_checkpoint.insert(checkpoint, tree);
400                }
401            }
402        } else {
403            let mut seen_paths = FxHashSet::default();
404            for path in all_paths.iter() {
405                if path.is_empty() {
406                    continue;
407                }
408                if !seen_paths.insert(path.clone()) {
409                    continue;
410                }
411                let last_block = BasicBlock::from(*path.last().unwrap());
412                let checkpoint = CheckpointLocation {
413                    caller: self.target.def_id,
414                    block: last_block,
415                };
416                trees_by_checkpoint
417                    .entry(checkpoint)
418                    .or_insert_with(PathTree::new)
419                    .insert(path.as_slice());
420            }
421        }
422
423        trees_by_checkpoint
424    }
425}
426
427/// Returns whether a function returns the owning struct type (i.e. is a constructor).
428/// Borrowed view of all verification inputs for one unsafe checkpoint.
429pub struct CheckpointCheckView<'view, 'target, 'tcx> {
430    /// Position among checkpoints that have properties to verify.
431    pub checkpoint_index: usize,
432    /// The concrete unsafe checkpoint in the caller MIR body.
433    pub checkpoint: &'view Checkpoint<'tcx>,
434    /// Per-checkpoint prefix tree of all verification paths to this checkpoint.
435    pub tree: &'view PathTree,
436    /// Required safety properties for the unsafe callee.
437    pub properties: &'target [Property<'tcx>],
438}
439
440/// Analysis pass that runs verification and emits function-level summaries.
441pub struct VerifyRun<'tcx> {
442    tcx: TyCtxt<'tcx>,
443    repeat_strategy: RepeatStrategy,
444    mode: VerifyMode,
445    skip_invariant: bool,
446    crate_filter: Option<String>,
447    module_filter: Option<String>,
448    debug_contracts: bool,
449}
450
451impl<'tcx> VerifyRun<'tcx> {
452    /// Create the default verify pass for the current compiler type context.
453    pub fn new(
454        tcx: TyCtxt<'tcx>,
455        repeat_strategy: RepeatStrategy,
456        mode: VerifyMode,
457        skip_invariant: bool,
458        crate_filter: Option<String>,
459        module_filter: Option<String>,
460        debug_contracts: bool,
461    ) -> Self {
462        Self {
463            tcx,
464            repeat_strategy,
465            mode,
466            skip_invariant,
467            crate_filter,
468            module_filter,
469            debug_contracts,
470        }
471    }
472
473    fn repeat_rounds_for_target(&self, target: &FunctionTarget<'tcx>) -> (usize, Vec<usize>) {
474        match self.repeat_strategy {
475            RepeatStrategy::Fixed(n) => (n, (0..=n).collect()),
476            RepeatStrategy::Auto => {
477                let plan = LoopSensitivityAnalyzer::new(self.tcx).analyze(target);
478                let repeat = plan.repeat;
479                (repeat, (0..=repeat).collect())
480            }
481        }
482    }
483
484    /// With `--skip-invariant`, generate verification sequences for each read method
485    /// that chain through constructors and mutators.
486    ///
487    /// Produces sequences like:
488    /// - `constructor → method`
489    /// - `constructor → mutator → method`
490    ///
491    /// Each sequence propagates the constructor's `#[rapx::requires]` through
492    /// the mutator chain to serve as entry assumptions for the read method.
493    fn run_invless_sequences(&self, targets: &[FunctionTarget<'tcx>]) {
494        for target in targets {
495            let read_def_id = target.def_id;
496            let cons = get_cons(self.tcx, read_def_id);
497            if cons.is_empty() {
498                continue;
499            }
500            let muts = get_muts(self.tcx, read_def_id);
501
502            for &con_id in &cons {
503                let con_target = self.build_virtual_target(target, read_def_id, con_id, &[]);
504                self.verify_and_emit_sequence(read_def_id, &con_target, con_id, &[]);
505
506                for &mut_id in &muts {
507                    let con_target =
508                        self.build_virtual_target(target, read_def_id, con_id, &[mut_id]);
509                    self.verify_and_emit_sequence(
510                        read_def_id,
511                        &con_target,
512                        con_id,
513                        &[mut_id],
514                    );
515                }
516            }
517        }
518    }
519
520    fn build_virtual_target(
521        &self,
522        read_target: &FunctionTarget<'tcx>,
523        read_def_id: rustc_hir::def_id::DefId,
524        con_id: rustc_hir::def_id::DefId,
525        mut_ids: &[rustc_hir::def_id::DefId],
526    ) -> FunctionTarget<'tcx> {
527        let mut accumulated_requires: Vec<Property<'tcx>> = Vec::new();
528
529        // Start with the constructor's requires, remapped to refer to struct
530        // fields (self.field) instead of constructor parameters.
531        let con_contracts: Vec<Property<'tcx>> = get_contract_from_annotation(self.tcx, con_id)
532            .into_iter()
533            .map(|c| remap_constructor_contract(c))
534            .collect();
535        accumulated_requires.extend(con_contracts);
536
537        // Remove contracts that are invalidated by mutators
538        if !mut_ids.is_empty() {
539            let mut mutated_fields: Vec<usize> = Vec::new();
540            for &mut_id in mut_ids {
541                for field_idx in get_mutated_fields(self.tcx, mut_id) {
542                    if !mutated_fields.contains(&field_idx) {
543                        mutated_fields.push(field_idx);
544                    }
545                }
546            }
547            if !mutated_fields.is_empty() {
548                accumulated_requires.retain(|prop| {
549                    let prop_fields = property_field_indices(prop);
550                    !prop_fields.iter().any(|f| mutated_fields.contains(f))
551                });
552            }
553        }
554
555        // Also include the read method's own caller requires (which already
556        // contains struct invariants merged by build_function_target).
557        // This is broader than just `get_contract_from_annotation` because
558        // it propagates struct-level properties even when the method has no
559        // explicit `#[rapx::requires]`.
560        accumulated_requires.extend(read_target.caller_requires.clone());
561
562        FunctionTarget {
563            def_id: read_def_id,
564            owner_struct_def_id: read_target.owner_struct_def_id,
565            checkpoints: read_target.checkpoints.clone(),
566            callee_requires: read_target.callee_requires.clone(),
567            caller_requires: accumulated_requires,
568            struct_invariants: Vec::new(),
569            raw_ptr_deref_checks: read_target.raw_ptr_deref_checks.clone(),
570            static_mut_checks: read_target.static_mut_checks.clone(),
571        }
572    }
573
574    fn verify_and_emit_sequence(
575        &self,
576        read_def_id: rustc_hir::def_id::DefId,
577        con_target: &FunctionTarget<'tcx>,
578        con_id: rustc_hir::def_id::DefId,
579        mut_ids: &[rustc_hir::def_id::DefId],
580    ) {
581        let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
582        let mut crashed: Option<String> = None;
583
584        let (_, repeat_rounds) = self.repeat_rounds_for_target(con_target);
585        for repeat in repeat_rounds {
586            let driver = VerifyDriver::new_with_repeat(
587                self.tcx, con_target, repeat,
588            );
589            match crate::helpers::mir_utils::catch_panic(|| driver.verify_function()) {
590                Ok(report) => {
591                    rap_debug!("{}", report.describe());
592                    all_results.extend(report.results);
593                }
594                Err(msg) => {
595                    rap_warn!(
596                        "Skipping constructor {} (repeat {}): {msg}",
597                        self.tcx.def_path_str(con_id),
598                        repeat,
599                    );
600                    all_results.clear();
601                    crashed = Some(format!("repeat {repeat}: {msg}"));
602                    break;
603                }
604            }
605        }
606
607        let read_name = short_fn_name(self.tcx, read_def_id);
608        let con_name = short_fn_name(self.tcx, con_id);
609        let mut chain_parts: Vec<String> = vec![con_name];
610        for &mut_id in mut_ids {
611            chain_parts.push(short_fn_name(self.tcx, mut_id));
612        }
613        chain_parts.push(read_name);
614        let chain_label = chain_parts.join(" -> ");
615
616        rap_info!("============================================================");
617        rap_info!("[rapx::verify] sequence: {chain_label}");
618        rap_info!("============================================================");
619
620        if let Some(msg) = &crashed {
621            rap_warn!("  result: UNKNOWN (verifier crashed: {msg})");
622        } else if all_results.is_empty() {
623            rap_info!("  result: SOUND (no unsafe checkpoints)");
624        } else {
625            emit_results_and_verdict(self.tcx, &all_results);
626        }
627        rap_info!("");
628    }
629}
630
631impl<'tcx> Analysis for VerifyRun<'tcx> {
632    /// Collect verify targets, run the staged driver, and emit a compact summary.
633    ///
634    /// For each target, extracts paths with increasing `postfix-repeat`
635    /// levels from 0 to the configured maximum, running verification at each
636    /// level. Earlier rounds use fewer loop unrollings; later rounds incrementally
637    /// add deeper paths.
638    fn run(&mut self) {
639        // Register `pred!`-emitted `#[rapx::def_contract("...")]` definitions.
640        crate::verify::contract::def::register_contract_defs(self.tcx);
641
642        let collector = VerifyTargetCollector::collect_all(
643            self.tcx,
644            self.mode,
645            self.skip_invariant,
646            self.crate_filter.clone(),
647            self.module_filter.clone(),
648        );
649
650        if self.debug_contracts {
651            self.print_contracts_debug(&collector.function_targets);
652            return;
653        }
654
655        for target in &collector.function_targets {
656            let target_path = fmt_fn_path_with_bounds(self.tcx, target.def_id);
657            let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
658            let mut fn_crashed: Option<String> = None;
659
660            let (planned_repeat, repeat_rounds) = self.repeat_rounds_for_target(target);
661
662            // Phase 1: unsafe checkpoint verification
663            for repeat in repeat_rounds {
664                let driver = VerifyDriver::new_with_repeat(
665                    self.tcx, target, repeat,
666                );
667                match crate::helpers::mir_utils::catch_panic(|| driver.verify_function()) {
668                    Ok(report) => {
669                        rap_debug!("{}", report.describe());
670                        all_results.extend(report.results);
671                    }
672                    Err(msg) => {
673                        rap_warn!(
674                            "Skipping function {} (repeat {}): {msg}",
675                            target_path,
676                            repeat,
677                        );
678                        all_results.clear();
679                        fn_crashed = Some(format!("repeat {repeat}: {msg}"));
680                        break;
681                    }
682                }
683            }
684
685            // Phase 2: struct invariant verification
686            if !target.struct_invariants.is_empty() && !self.skip_invariant {
687                let driver = VerifyDriver::new_with_repeat(
688                    self.tcx, target, planned_repeat,
689                );
690                match crate::helpers::mir_utils::catch_panic(|| driver.verify_struct_invariants()) {
691                    Ok(struct_report) => {
692                        rap_debug!("{}", struct_report.describe());
693                        all_results.extend(struct_report.results);
694                    }
695                    Err(msg) => {
696                        rap_warn!("Skipping struct invariants for {} : {msg}", target_path);
697                        all_results.clear();
698                        fn_crashed = Some(format!("struct-invariant: {msg}"));
699                    }
700                }
701            }
702
703            if let Some(msg) = &fn_crashed {
704                rap_info!("============================================================");
705                rap_info!("[rapx::verify] function: {target_path}");
706                rap_info!("============================================================");
707                rap_warn!("  result: UNKNOWN (verifier crashed: {msg})");
708                rap_info!("");
709                continue;
710            }
711
712            if all_results.is_empty() {
713                let all_callees_skipped = !target.checkpoints.is_empty()
714                    && target.checkpoints.iter().all(|ckpt| {
715                        ckpt.callee.map_or(false, |callee| {
716                            target
717                                .callee_requires
718                                .get(&callee)
719                                .map_or(true, |c| c.is_empty())
720                        })
721                    });
722                if (target.checkpoints.is_empty() || all_callees_skipped)
723                    && target.raw_ptr_deref_checks.is_empty()
724                    && target.static_mut_checks.is_empty()
725                    && target.struct_invariants.is_empty()
726                {
727                    rap_info!("============================================================");
728                    rap_info!("[rapx::verify] function: {target_path}");
729                    rap_info!("============================================================");
730                    if self.skip_invariant {
731                        let cons = get_cons(self.tcx, target.def_id);
732                        for con in &cons {
733                            rap_info!("  + constructor: {}", self.tcx.def_path_str(*con));
734                        }
735                    }
736                    rap_info!("  --- unsafe checkpoints ---");
737                    rap_info!("      <none>");
738                    rap_info!("        <none>");
739                    rap_info!("  result: SOUND (no unsafe checkpoints)");
740                    rap_info!("");
741                }
742                continue;
743            }
744
745            // When --skip-invariant is set, skip standalone emission for methods that
746            // have constructors — sequences will generate dedicated entries.
747            if self.skip_invariant && !get_cons(self.tcx, target.def_id).is_empty() {
748                continue;
749            }
750
751            emit_verify_summary(
752                self.tcx,
753                &target_path,
754                target.def_id,
755                &all_results,
756                self.skip_invariant,
757            );
758        }
759
760        // Emit detected unsafe trait impls (verification deferred)
761        if !collector.trait_targets.is_empty() {
762            let mut trait_ids: Vec<_> = collector.trait_targets.keys().copied().collect();
763            trait_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
764            for trait_def_id in trait_ids {
765                let Some(trait_target) = collector.trait_targets.get(&trait_def_id) else {
766                    continue;
767                };
768                rap_info!("============================================================");
769                rap_info!(
770                    "[rapx::verify] unsafe trait impl: {}",
771                    self.tcx.def_path_str(trait_target.def_id)
772                );
773                rap_info!("============================================================");
774                if let Some(self_ty) = trait_target.self_ty_def_id {
775                    rap_info!("  impl for: {}", self.tcx.def_path_str(self_ty));
776                }
777                if trait_target.ensures.is_empty() {
778                    rap_info!("  ensures: <none>");
779                } else {
780                    rap_info!("  ensures (implementor must satisfy):");
781                    for (method_name, contracts) in &trait_target.ensures {
782                        rap_info!("    fn {}:", method_name);
783                        for property in dedup_compound_props(contracts.iter()) {
784                            rap_info!(
785                                "      - {}",
786                                property.display_for_report(
787                                    self.tcx,
788                                    trait_target.self_ty_def_id,
789                                    None,
790                                )
791                            );
792                        }
793                    }
794                }
795                rap_info!("  verification: deferred");
796                rap_info!("");
797            }
798        }
799
800        // --skip-invariant: generate constructor-mutator-method sequences
801        if self.skip_invariant {
802            self.run_invless_sequences(&collector.function_targets);
803        }
804    }
805
806}
807
808impl<'tcx> VerifyRun<'tcx> {
809    fn print_contracts_debug(&self, targets: &[FunctionTarget<'tcx>]) {
810        rap_info!("{:=<1$}", "", 76);
811        rap_info!("[rapx::debug-contracts] Expanded Contract Assertions");
812        rap_info!("{:=<1$}", "", 76);
813        rap_info!("");
814
815        let mut struct_groups: FxHashMap<
816            rustc_hir::def_id::DefId,
817            Vec<&FunctionTarget<'tcx>>,
818        > = FxHashMap::default();
819        let mut free_targets: Vec<&FunctionTarget<'tcx>> = Vec::new();
820
821        for target in targets {
822            if let Some(sid) = target.owner_struct_def_id {
823                struct_groups.entry(sid).or_default().push(target);
824            } else {
825                free_targets.push(target);
826            }
827        }
828
829        let mut struct_ids: Vec<_> = struct_groups.keys().copied().collect();
830        struct_ids.sort_by_key(|did| self.tcx.def_path_str(*did));
831
832        for struct_def_id in struct_ids {
833            let methods = &struct_groups[&struct_def_id];
834            let struct_name = self.tcx.def_path_str(struct_def_id);
835
836            // -- Struct invariants (once) --
837            let inv_target = methods
838                .iter()
839                .find(|t| !t.struct_invariants.is_empty());
840            let have_invariants = inv_target.is_some();
841
842            if have_invariants || methods.iter().any(|t| {
843                self.has_printable_contracts(t)
844            }) {
845                rap_info!("{:=<1$}", "", 76);
846                rap_info!("[rapx::debug-contracts] struct: {struct_name}");
847                rap_info!("{:=<1$}", "", 76);
848            }
849
850            if let Some(tgt) = inv_target {
851                rap_info!("  [Struct Invariants]:");
852                let invariants = dedup_compound_props(tgt.struct_invariants.iter());
853                let inv_count = invariants.len();
854                for (ii, property) in invariants.iter().enumerate() {
855                    let ibranch = if ii + 1 == inv_count { "`-" } else { "|-" };
856                    let (call, meaning) = fmt_contract_expanded(
857                        self.tcx,
858                        property,
859                        tgt.owner_struct_def_id,
860                        Some(tgt.def_id),
861                    );
862                    self.print_contract_lines("  ", &ibranch, &call, &meaning);
863                }
864                rap_info!("");
865            }
866
867            // -- Each method --
868            let mut printed = false;
869            for (mi, target) in methods.iter().enumerate() {
870                let is_last_method = mi + 1 == methods.len();
871                let branch = if is_last_method { "`-" } else { "|-" };
872                let cont = if is_last_method { "  " } else { "| " };
873                if self.print_target_contracts(target, branch, cont) {
874                    printed = true;
875                }
876            }
877            if printed {
878                rap_info!("{:=<1$}", "", 76);
879                rap_info!("");
880            }
881        }
882
883        // -- Free functions --
884        for target in &free_targets {
885            self.print_target_contracts(target, "- ", "  ");
886        }
887    }
888
889    fn has_printable_contracts(&self, target: &FunctionTarget<'tcx>) -> bool {
890        use crate::verify::contract::PropertyKind;
891        let is_unsafe_fn = self
892            .tcx
893            .fn_sig(target.def_id)
894            .skip_binder()
895            .safety()
896            == rustc_hir::Safety::Unsafe;
897        let has_caller = is_unsafe_fn
898            && target
899                .caller_requires
900                .iter()
901                .any(|p| p.kind() != Some(PropertyKind::Unknown));
902        if has_caller {
903            return true;
904        }
905        target.callee_requires.values().any(|c| {
906            c.iter().any(|p| p.kind() != Some(PropertyKind::Unknown))
907        })
908    }
909
910    fn print_contract_lines(&self, prefix: &str, branch: &str, call: &str, meaning: &str) {
911        rap_info!("{prefix}{branch} {call}");
912        let cont = if branch == "`-" { "  " } else { "| " };
913        for line in meaning.lines() {
914            rap_info!("{prefix}{cont} {line}");
915        }
916    }
917
918    fn print_target_contracts(
919        &self,
920        target: &FunctionTarget<'tcx>,
921        branch: &str,
922        cont: &str,
923    ) -> bool {
924        use crate::verify::contract::PropertyKind;
925
926        let (arg_names_typed, ret_ty) = self.resolve_arg_names_with_types(target.def_id);
927        let is_unsafe_fn = self
928            .tcx
929            .fn_sig(target.def_id)
930            .skip_binder()
931            .safety()
932            == rustc_hir::Safety::Unsafe;
933
934        let target_path = fmt_fn_path_with_generics(self.tcx, target.def_id);
935        let short_name = crate::helpers::name::short_fn_name(self.tcx, target.def_id);
936
937        // Collect what to print first
938        let has_caller = is_unsafe_fn
939            && target
940                .caller_requires
941                .iter()
942                .any(|p| p.kind() != Some(PropertyKind::Unknown));
943        let mut callee_ids: Vec<_> = target.callee_requires.keys().copied().collect();
944        callee_ids.retain(|did| {
945            target
946                .callee_requires
947                .get(did)
948                .is_some_and(|c| c.iter().any(|p| p.kind() != Some(PropertyKind::Unknown)))
949        });
950        callee_ids.sort_by_key(|did| self.tcx.def_path_str(*did));
951        let has_callees = !callee_ids.is_empty();
952
953        if !has_caller && !has_callees {
954            return false;
955        }
956
957        let fn_display = fmt_fn_with_params(&target_path, &arg_names_typed, ret_ty.as_deref());
958        let header = format!("--- method: {short_name}");
959        let dashes = 72usize.saturating_sub(header.len());
960        rap_info!("{branch} {header} {}", "-".repeat(dashes));
961        rap_info!("{cont}  {fn_display}");
962
963        // Caller Contracts (only for unsafe functions)
964        if has_caller {
965            rap_info!("{cont}  [Caller Contracts]:");
966            let caller_props = dedup_compound_props(
967                target
968                    .caller_requires
969                    .iter()
970                    .filter(|p| p.kind() != Some(PropertyKind::Unknown)),
971            );
972            for (pi, property) in caller_props.iter().enumerate() {
973                let is_last = pi + 1 == caller_props.len();
974                let pbranch = if is_last { "`-" } else { "|-" };
975                let (call, meaning) = fmt_contract_expanded(
976                    self.tcx,
977                    property,
978                    target.owner_struct_def_id,
979                    Some(target.def_id),
980                );
981                self.print_contract_lines(
982                    &format!("{cont}  "),
983                    pbranch,
984                    &call,
985                    &meaning,
986                );
987            }
988            if !has_callees {
989                rap_info!("");
990            }
991        }
992
993        // Callee Contracts (for each unsafe callee)
994        if has_callees {
995            rap_info!("{cont}  [Unsafe Callees]:");
996            for (ci, &callee_id) in callee_ids.iter().enumerate() {
997                let is_last_callee = ci + 1 == callee_ids.len();
998                let cbranch = if is_last_callee { "`-" } else { "|-" };
999                let ccont = if is_last_callee { "  " } else { "| " };
1000                let contracts = target.callee_requires.get(&callee_id).unwrap();
1001                let (callee_typed, callee_ret) =
1002                    self.resolve_arg_names_with_types(callee_id);
1003                let callee_path = fmt_fn_path_with_generics(self.tcx, callee_id);
1004                rap_info!(
1005                    "{cont}  {cbranch} {}",
1006                    fmt_fn_with_params(
1007                        &callee_path,
1008                        &callee_typed,
1009                        callee_ret.as_deref()
1010                    )
1011                );
1012                let props = dedup_compound_props(
1013                    contracts
1014                        .iter()
1015                        .filter(|p| p.kind() != Some(PropertyKind::Unknown)),
1016                );
1017                for (pi, property) in props.iter().enumerate() {
1018                    let is_last_prop = pi + 1 == props.len();
1019                    let pbranch = if is_last_prop { "`-" } else { "|-" };
1020                    let (call, meaning) = fmt_contract_expanded(
1021                        self.tcx,
1022                        property,
1023                        None,
1024                        Some(callee_id),
1025                    );
1026                    self.print_contract_lines(
1027                        &format!("{cont}  {ccont}"),
1028                        pbranch,
1029                        &call,
1030                        &meaning,
1031                    );
1032                }
1033            }
1034        }
1035
1036        rap_info!("");
1037        true
1038    }
1039
1040    fn resolve_arg_names_with_types(
1041        &self,
1042        def_id: rustc_hir::def_id::DefId,
1043    ) -> (Vec<String>, Option<String>) {
1044        if !self.tcx.is_mir_available(def_id) {
1045            return (Vec::new(), None);
1046        }
1047        let body = self.tcx.optimized_mir(def_id);
1048        let args: Vec<String> = body
1049            .local_decls
1050            .iter()
1051            .enumerate()
1052            .skip(1)
1053            .take(body.arg_count)
1054            .map(|(i, decl)| {
1055                let name = {
1056                    let span = decl.source_info.span;
1057                    self.tcx
1058                        .sess
1059                        .source_map()
1060                        .span_to_snippet(span)
1061                        .unwrap_or_else(|_| format!("_{}", i))
1062                };
1063                let ty = decl.ty.to_string();
1064                format!("{name}: {ty}")
1065            })
1066            .collect();
1067        let ret_ty = self.tcx.fn_sig(def_id).skip_binder().output().skip_binder();
1068        let ret_ty = if ret_ty.is_unit() {
1069            None
1070        } else {
1071            Some(ret_ty.to_string())
1072        };
1073        (args, ret_ty)
1074    }
1075}
1076
1077use crate::helpers::name::short_fn_name;
1078
1079/// Return true when two properties have the same kind.
1080/// Collect struct field indices referenced by a property's contract places.
1081///
1082/// Used to determine which invariants are invalidated when a mutator writes
1083/// to specific struct fields.
1084fn property_field_indices(property: &crate::verify::contract::Property<'_>) -> Vec<usize> {
1085    use crate::verify::contract::{ContractExpr, PropertyArg};
1086    let mut indices = Vec::new();
1087    for arg in property.args() {
1088        let place = match arg {
1089            PropertyArg::Expr(ContractExpr::Place(p)) => Some(p),
1090            _ => None,
1091        };
1092        if let Some(place) = place {
1093            for proj in &place.projections {
1094                match proj {
1095                    crate::verify::contract::ContractProjection::Field { index, .. } => {
1096                        let idx = *index;
1097                        if !indices.contains(&idx) {
1098                            indices.push(idx);
1099                        }
1100                    }
1101                    crate::verify::contract::ContractProjection::Downcast { .. } => {}
1102                    crate::verify::contract::ContractProjection::IterElements => {}
1103                }
1104            }
1105        }
1106    }
1107    indices
1108}
1109
1110fn remap_constructor_contract<'tcx>(
1111    property: crate::verify::contract::Property<'tcx>,
1112) -> crate::verify::contract::Property<'tcx> {
1113    use crate::verify::contract::{
1114        ContractExpr, ContractPlace, ContractProjection, PlaceBase, PropertyArg,
1115    };
1116
1117    fn remap_place_arg<'tcx>(arg: &PropertyArg<'tcx>) -> PropertyArg<'tcx> {
1118        let place = match arg {
1119            PropertyArg::Expr(ContractExpr::Place(p)) => p,
1120            _ => return arg.clone(),
1121        };
1122        let PlaceBase::Arg(field_idx) = place.base else {
1123            return arg.clone();
1124        };
1125        let projection = ContractProjection::Field {
1126            index: field_idx,
1127            ty: None,
1128        };
1129        let mut new_place = ContractPlace {
1130            base: PlaceBase::Arg(0),
1131            projections: vec![projection],
1132        };
1133        new_place
1134            .projections
1135            .extend(place.projections.iter().cloned());
1136        PropertyArg::Expr(ContractExpr::Place(new_place))
1137    }
1138
1139    let new_args: Vec<PropertyArg<'tcx>> = property
1140        .args()
1141        .iter()
1142        .map(|arg| remap_place_arg(arg))
1143        .collect();
1144
1145    match property {
1146        crate::verify::contract::Property::Leaf(mut leaf) => {
1147            leaf.args = new_args;
1148            crate::verify::contract::Property::Leaf(leaf)
1149        }
1150        crate::verify::contract::Property::Or(or) => crate::verify::contract::Property::Or(or),
1151    }
1152}