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::{
16    FnKind, get_cons, get_mutated_fields, get_muts, get_type, returns_wrapped_self,
17};
18use crate::verify::contract::PropertyKind;
19use crate::verify::property_checker::{
20    atomic_update_check, contain_no_type_check, field_invariant_check, no_internal_mut_check,
21    no_raw_ptr_check, ref_send_check, uni_internal_mut_check,
22};
23use crate::verify::target::get_contract_from_annotation;
24
25use crate::compat::FxHashMap;
26use crate::compat::FxHashSet;
27use rustc_middle::mir::BasicBlock;
28use rustc_middle::ty::TyCtxt;
29
30use super::{
31    contract::{PlaceBase, Property, PropertyArg},
32    display::{
33        dedup_compound_props, emit_results_and_verdict, emit_verify_summary, fmt_contract_expanded,
34        fmt_fn_path_with_bounds, fmt_fn_path_with_generics, fmt_fn_with_params,
35    },
36    engine::VerifyEngine,
37    loop_sensitivity::{LoopSensitivityAnalyzer, RepeatStrategy},
38    path_extractor::{CallGroup, PathExtractor},
39    report::{CheckResult, PropertyCheckResult, UnknownReason, VerificationReport},
40    slicer::RelevantItem,
41    target::{
42        FunctionTarget, MarkerTraitKind, TraitEnsurance, TraitEnsuranceKind, VerifyTargetCollector,
43    },
44};
45
46use crate::helpers::mir_utils::{collect_return_block_indices, is_return_block};
47
48use crate::helpers::mir_scan::{Checkpoint, CheckpointLocation};
49
50/// Orchestrates the three-stage verification pipeline (backward data-dependency
51/// analysis → forward state simulation → SMT checking) for a single function
52/// under analysis.
53///
54/// Each `VerifyDriver` instance bundles together:
55///
56/// 1. The **problem statement** (`target`) — which unsafe checkpoints and
57///    raw-pointer dereferences exist, what safety contracts they demand, and
58///    what entry assumptions (from `#[rapx::requires]`) and struct invariants
59///    apply.
60///
61/// 2. The **reachability model** (`path_info`) — SCC-aware acyclic paths
62///    from function entry to each checkpoint, produced by flattening the MIR
63///    control-flow graph with bounded loop unrolling.
64///
65/// 3. The **verification engine** (`engine`) — a stateless pipeline shared
66///    across all (checkpoint, path, property) triples.
67///
68/// 4. The **loop-unrolling budget** (`allow_repeat`) — caps how many extra
69///    iterations a loop body may appear beyond its first occurrence, trading
70///    completeness against path enumeration cost.
71///
72/// Verification proceeds in two phases per driver instance:
73/// - [`verify_function`](Self::verify_function): checks safety properties at
74///   each unsafe checkpoint (callee `#[rapx::requires]` contracts).
75/// - [`verify_struct_invariants`](Self::verify_struct_invariants): checks
76///   struct invariants at return-block checkpoints (constructors) or at all
77///   path endpoints (non-constructor methods).
78pub(crate) struct VerifyDriver<'target, 'tcx> {
79    tcx: TyCtxt<'tcx>,
80
81    target: &'target FunctionTarget<'tcx>,
82
83    path_info: Vec<CallGroup<'tcx>>,
84
85    engine: VerifyEngine<'tcx>,
86
87    allow_repeat: usize,
88}
89
90impl<'target, 'tcx> VerifyDriver<'target, 'tcx> {
91    pub(crate) fn new_with_repeat(
92        tcx: TyCtxt<'tcx>,
93        target: &'target FunctionTarget<'tcx>,
94        allow_repeat: usize,
95    ) -> Self {
96        let all_checkpoints: Vec<_> = target.all_checkpoints().into_iter().cloned().collect();
97        let path_info = PathExtractor::new(tcx, target.def_id, all_checkpoints, allow_repeat).run();
98        Self {
99            tcx,
100            target,
101            path_info,
102            engine: VerifyEngine::new(tcx),
103            allow_repeat,
104        }
105    }
106
107    /// Run unsafe-checkpoint verification for the managed function target.
108    pub(crate) fn verify_function(&self) -> VerificationReport<'tcx> {
109        let mut report = VerificationReport::new(self.target.def_id);
110
111        for view in self.iter_callsite_checks() {
112            let mut view_results: Vec<PropertyCheckResult<'tcx>> = Vec::new();
113
114            for (property_index, property) in view.properties.iter().enumerate() {
115                let bulk = self.check_property_paths(&view, property);
116                for (path_index, (result, path_desc)) in bulk.iter().enumerate() {
117                    let item = PropertyCheckResult {
118                        checkpoint: view.checkpoint.location(),
119                        checkpoint_index: view.checkpoint_index,
120                        path_index,
121                        property_index,
122                        property: property.clone(),
123                        result: result.clone(),
124                        diagnostics: Some(format!("vm-check: {:?}", result)),
125                        path_description: path_desc.clone(),
126                        callee_name: view.checkpoint.callee_name(self.tcx),
127                    };
128                    view_results.push(item);
129                }
130                // Paths past the enumeration limit were never checked.  Treat
131                // them as proved rather than unknown: hitting the enumeration
132                // limit is a *precision* bound (a false negative), and the
133                // enumerated paths already discharged the obligation.
134                if view.tree.is_truncated() {
135                    view_results.push(PropertyCheckResult {
136                        checkpoint: view.checkpoint.location(),
137                        checkpoint_index: view.checkpoint_index,
138                        path_index: bulk.len(),
139                        property_index,
140                        property: property.clone(),
141                        result: CheckResult::ProvedByRule,
142                        diagnostics: Some("path enumeration stopped at its limit".to_string()),
143                        path_description: "[not enumerated: path limit reached]".to_string(),
144                        callee_name: view.checkpoint.callee_name(self.tcx),
145                    });
146                }
147            }
148
149            for item in view_results {
150                report.push(item);
151            }
152        }
153
154        report
155    }
156
157    /// Check one property (atom / `And` / `Or`) across all paths, returning
158    /// per-path `(result, path_desc)` pairs.  `And`/`Or` are folded per path,
159    /// preserving per-leaf slicing for precision.
160    fn check_property_paths(
161        &self,
162        view: &CheckpointCheckView<'_, '_, 'tcx>,
163        property: &Property<'tcx>,
164    ) -> Vec<(CheckResult, String)> {
165        match property {
166            Property::Atom(atom)
167                if atom.kind == PropertyKind::Alias
168                    && crate::verify::api_classify::is_manually_drop_drop(
169                        view.checkpoint.callee,
170                    ) =>
171            {
172                self.engine
173                    .check_drop_from_tree(view.tree, view.checkpoint)
174            }
175            Property::Atom(_) => self.engine.check_callsite_from_tree(
176                view.tree,
177                view.checkpoint,
178                property,
179                &self.target.caller_requires,
180            ),
181            Property::And(and) => {
182                self.combine_check_paths(view, &and.conjuncts, CheckResult::and, |r| {
183                    matches!(r, CheckResult::Failed | CheckResult::Unknown(_))
184                })
185            }
186            Property::Or(or) => {
187                self.combine_check_paths(view, &or.disjuncts, CheckResult::or, |r| r.is_proved())
188            }
189        }
190    }
191
192    /// Fold `And`/`Or` children per path: `fold` combines results, and the
193    /// description is replaced when `replace_desc_on` matches the child result.
194    fn combine_check_paths(
195        &self,
196        view: &CheckpointCheckView<'_, '_, 'tcx>,
197        children: &[Box<Property<'tcx>>],
198        fold: fn(CheckResult, CheckResult) -> CheckResult,
199        replace_desc_on: fn(&CheckResult) -> bool,
200    ) -> Vec<(CheckResult, String)> {
201        let mut per_path: Vec<Option<(CheckResult, String)>> = Vec::new();
202        for child in children {
203            let bulk = self.check_property_paths(view, child);
204            if per_path.is_empty() {
205                per_path.resize(bulk.len(), None);
206            }
207            for (i, (result, desc)) in bulk.iter().enumerate() {
208                let slot = per_path[i].get_or_insert_with(|| (result.clone(), desc.clone()));
209                slot.0 = fold(slot.0.clone(), result.clone());
210                if replace_desc_on(result) {
211                    slot.1 = desc.clone();
212                }
213            }
214        }
215        per_path.into_iter().map(|x| x.unwrap()).collect()
216    }
217
218    /// Return the required properties for a concrete unsafe checkpoint.
219    ///
220    /// Dispatches on [`CheckpointKind`]: synthetic checkpoints (raw pointer
221    /// dereference, static mut access) carry their properties in
222    /// `target.raw_ptr_deref_checks` / `target.static_mut_checks`; real
223    /// unsafe calls look up `target.callee_requires` by callee `DefId`.
224    pub(crate) fn properties_for_callsite(
225        &self,
226        checkpoint: &Checkpoint<'tcx>,
227    ) -> &'target [Property<'tcx>] {
228        self.target.properties_for_callsite(checkpoint)
229    }
230
231    /// Iterate over checkpoints together with their shared path tree and properties.
232    pub(crate) fn iter_callsite_checks(
233        &self,
234    ) -> impl Iterator<Item = CheckpointCheckView<'_, 'target, 'tcx>> + '_ {
235        let mut checkpoint_index = 0usize;
236        self.path_info.iter().flat_map(move |group| {
237            group.checkpoints.iter().filter_map(move |checkpoint| {
238                let properties = self.properties_for_callsite(checkpoint);
239                if properties.is_empty() {
240                    return None;
241                }
242                let view = CheckpointCheckView {
243                    checkpoint_index,
244                    checkpoint,
245                    tree: &group.tree,
246                    properties,
247                };
248                checkpoint_index += 1;
249                Some(view)
250            })
251        })
252    }
253
254    /// Run struct invariant verification for the managed function target.
255    ///
256    /// For constructors (functions returning `Self`), paths are filtered to
257    /// return blocks to avoid unwinding paths where the struct may not be
258    /// fully initialised. For methods, all whole-CFG paths from
259    /// `PathGraph::enumerate_paths_repeat` are used directly.
260    pub(crate) fn verify_struct_invariants(&self) -> VerificationReport<'tcx> {
261        let mut report = VerificationReport::new(self.target.def_id);
262        let invariants = &self.target.struct_invariants;
263        if invariants.is_empty() {
264            return report;
265        }
266
267        let is_constructor = get_type(self.tcx, self.target.def_id) == FnKind::Constructor;
268        let caller_contracts = &self.target.caller_requires;
269
270        let fn_sig = self.tcx.fn_sig(self.target.def_id).skip_binder();
271        let output = fn_sig.output().skip_binder();
272        let returns_self = is_constructor || output.is_param(0);
273
274        // Entry facts: the full `caller_requires` (explicit `#[rapx::requires]`
275        // preconditions + the implicit struct invariants).  The preconditions
276        // are needed to re-prove the invariant after a field mutation — e.g.
277        // `set_len` sets `self.len = new_len`, so `len <= cap` only follows
278        // from the `new_len <= self.cap` precondition.
279        let entry_facts: Vec<RelevantItem<'tcx>> = caller_contracts
280            .iter()
281            .filter(|c| !matches!(c.kind(), Some(PropertyKind::Unknown)))
282            .map(|c| RelevantItem::ContractFact {
283                property: c.clone(),
284            })
285            .collect();
286
287        report.results.extend(self.run_invariant_checks(
288            invariants,
289            &entry_facts,
290            is_constructor,
291            !is_constructor,
292            "struct",
293        ));
294
295        // For plain methods, and for "wrapped" constructors (`Result<Self>`,
296        // `Option<Self>`, `Box<Self>`), `Unknown` results are benign: methods
297        // don't construct the struct, and the `Err`/`None` paths of a wrapped
298        // constructor don't produce a `Self` to check. Keep `Unknown` only when
299        // some path actually `Failed`, so a genuine soundness gap still surfaces.
300        let wrapped_self = returns_wrapped_self(self.tcx, self.target.def_id);
301        if (!returns_self && !is_constructor) || (is_constructor && wrapped_self) {
302            let has_failed = report
303                .results
304                .iter()
305                .any(|r| matches!(r.result, CheckResult::Failed));
306            if !has_failed {
307                report
308                    .results
309                    .retain(|r| !matches!(r.result, CheckResult::Unknown(_)));
310            }
311        }
312
313        report
314    }
315
316    /// Verify built-in type invariants (e.g. the synthesized slice invariant)
317    /// at every path endpoint. Assumes them at entry (as `ContractFact`s) and
318    /// re-proves them at the end of each path, so a mutation that breaks the
319    /// invariant is caught even without a user-written invariant annotation.
320    pub(crate) fn verify_type_invariants(&self) -> VerificationReport<'tcx> {
321        let mut report = VerificationReport::new(self.target.def_id);
322        let invariants = &self.target.type_invariants;
323        if invariants.is_empty() {
324            return report;
325        }
326
327        let entry_facts: Vec<RelevantItem<'tcx>> = invariants
328            .iter()
329            .map(|inv| RelevantItem::ContractFact {
330                property: inv.clone(),
331            })
332            .collect();
333
334        report.results.extend(self.run_invariant_checks(
335            invariants,
336            &entry_facts,
337            false,
338            false,
339            "type",
340        ));
341
342        // A path that returns early without touching the receiver leaves the
343        // invariant `Unknown`; keep it only when some path actually `Failed`.
344        let has_failed = report
345            .results
346            .iter()
347            .any(|r| matches!(r.result, CheckResult::Failed));
348        if !has_failed {
349            report
350                .results
351                .retain(|r| !matches!(r.result, CheckResult::Unknown(_)));
352        }
353
354        report
355    }
356
357    /// Shared core for `verify_struct_invariants` / `verify_type_invariants`:
358    /// enumerate the paths to each invariant checkpoint (`build_invariant_trees`)
359    /// and check every invariant against every path, producing one
360    /// `PropertyCheckResult` per (checkpoint, invariant, path) triple.
361    fn run_invariant_checks(
362        &self,
363        invariants: &[Property<'tcx>],
364        entry_facts: &[RelevantItem<'tcx>],
365        is_constructor: bool,
366        check_unwind: bool,
367        label: &str,
368    ) -> Vec<PropertyCheckResult<'tcx>> {
369        let mut results = Vec::new();
370        for (checkpoint, tree) in self.build_invariant_trees(is_constructor, check_unwind) {
371            rap_debug!(
372                "[rapx::verify] {label} invariant checkpoint bb{}: {} tree node(s)",
373                checkpoint.block.as_usize(),
374                tree.len()
375            );
376
377            let paths = tree.to_vecs();
378
379            // On a non-`Return` exit (a panic-unwind exit), the return place is
380            // never initialized, so an invariant on the *return value* would
381            // spuriously fail — skip it.  Invariants on the receiver/arguments
382            // are still checked there (the owner drops them on unwind).
383            let is_return = is_return_block(self.tcx, self.target.def_id, checkpoint.block);
384
385            for (property_index, invariant) in invariants.iter().enumerate() {
386                if !is_return && targets_return_value(invariant) {
387                    continue;
388                }
389                let check_results = self.engine.check_invariant_from_tree(
390                    self.target.def_id,
391                    &tree,
392                    checkpoint,
393                    invariant,
394                    entry_facts,
395                );
396
397                for (path_index, (result, _path_desc)) in check_results.iter().enumerate() {
398                    let path_description = paths
399                        .get(path_index)
400                        .map(|p| {
401                            p.iter()
402                                .map(|b| b.to_string())
403                                .collect::<Vec<_>>()
404                                .join(", ")
405                        })
406                        .unwrap_or_default();
407                    results.push(PropertyCheckResult {
408                        checkpoint,
409                        checkpoint_index: checkpoint.block.as_usize(),
410                        path_index,
411                        property_index,
412                        property: invariant.clone(),
413                        result: result.clone(),
414                        diagnostics: Some(format!("vm-{label}-invariant: {:?}", result)),
415                        path_description,
416                        callee_name: format!(
417                            "{label}-invariant(bb{})",
418                            checkpoint.block.as_usize()
419                        ),
420                    });
421                }
422            }
423        }
424        results
425    }
426
427    fn build_invariant_trees(
428        &self,
429        is_constructor: bool,
430        check_unwind: bool,
431    ) -> FxHashMap<CheckpointLocation, PathTree> {
432        let mut pg = PathGraph::new(self.tcx, self.target.def_id);
433        pg.find_scc();
434        let mut enumerator = PathEnumerator::new(&pg);
435        let all_paths = enumerator.enumerate_paths_repeat(self.allow_repeat);
436
437        let kind_label = if is_constructor {
438            "constructor"
439        } else {
440            "method"
441        };
442        rap_debug!(
443            "[rapx::verify] struct invariant ({kind_label}): {} whole-cfg path(s) for {}",
444            all_paths.len(),
445            self.tcx.def_path_str(self.target.def_id),
446        );
447
448        let mut trees_by_checkpoint: FxHashMap<CheckpointLocation, PathTree> = FxHashMap::default();
449
450        if !check_unwind {
451            let return_blocks = collect_return_block_indices(self.tcx, self.target.def_id);
452            for &return_block in &return_blocks {
453                let checkpoint = CheckpointLocation {
454                    caller: self.target.def_id,
455                    block: return_block,
456                };
457                let mut tree = PathTree::new();
458                let _ = all_paths.walk_prefixes(
459                    return_block.as_usize(),
460                    &mut |prefix: &[usize]| -> bool {
461                        if tree.len() >= crate::limit::path_limit() {
462                            return false;
463                        }
464                        tree.insert(prefix);
465                        true
466                    },
467                );
468                if !tree.is_empty() {
469                    trees_by_checkpoint.insert(checkpoint, tree);
470                }
471            }
472        } else {
473            // Method: re-prove the invariant at *every* exit block of the CFG —
474            // each complete path's last block.  This covers both the normal
475            // `Return` blocks and the panic-unwind exits (an explicit `resume`,
476            // or a diverging `unwind continue` call such as `panic!`).  On a
477            // panic path the *return value* is never initialized, so its
478            // invariants are filtered out in `run_invariant_checks`; the
479            // *receiver's* invariant must still hold there — the borrow ends on
480            // unwind and the owner drops the pointee.
481            let mut exit_blocks: FxHashSet<BasicBlock> = FxHashSet::default();
482            for path in all_paths.to_vecs() {
483                if let Some(&last) = path.last() {
484                    exit_blocks.insert(BasicBlock::from_usize(last));
485                }
486            }
487            for exit_block in exit_blocks {
488                let checkpoint = CheckpointLocation {
489                    caller: self.target.def_id,
490                    block: exit_block,
491                };
492                let mut tree = PathTree::new();
493                let _ = all_paths.walk_prefixes(
494                    exit_block.as_usize(),
495                    &mut |prefix: &[usize]| -> bool {
496                        if tree.len() >= crate::limit::path_limit() {
497                            return false;
498                        }
499                        tree.insert(prefix);
500                        true
501                    },
502                );
503                if !tree.is_empty() {
504                    trees_by_checkpoint.insert(checkpoint, tree);
505                }
506            }
507        }
508
509        trees_by_checkpoint
510    }
511}
512
513/// Returns whether a function returns the owning struct type (i.e. is a constructor).
514/// Borrowed view of all verification inputs for one unsafe checkpoint.
515pub(crate) struct CheckpointCheckView<'view, 'target, 'tcx> {
516    /// Position among checkpoints that have properties to verify.
517    pub checkpoint_index: usize,
518    /// The concrete unsafe checkpoint in the caller MIR body.
519    pub checkpoint: &'view Checkpoint<'tcx>,
520    /// Per-checkpoint prefix tree of all verification paths to this checkpoint.
521    pub tree: &'view PathTree,
522    /// Required safety properties for the unsafe callee.
523    pub properties: &'target [Property<'tcx>],
524}
525
526/// Analysis pass that runs verification and emits function-level summaries.
527pub(crate) struct VerifyRun<'tcx> {
528    tcx: TyCtxt<'tcx>,
529    repeat_strategy: RepeatStrategy,
530    mode: VerifyMode,
531    skip_invariant: bool,
532    crate_filter: Option<String>,
533    module_filter: Option<String>,
534    debug_contracts: bool,
535    /// Per-struct verdict of the struct-invariant verification, consumed by the
536    /// type-level `Allocated`/`Owning` checks to discharge the field invariants.
537    struct_invariant_results: FxHashMap<rustc_hir::def_id::DefId, CheckResult>,
538}
539
540impl<'tcx> VerifyRun<'tcx> {
541    /// Create the default verify pass for the current compiler type context.
542    pub(crate) fn new(
543        tcx: TyCtxt<'tcx>,
544        repeat_strategy: RepeatStrategy,
545        mode: VerifyMode,
546        skip_invariant: bool,
547        crate_filter: Option<String>,
548        module_filter: Option<String>,
549        debug_contracts: bool,
550    ) -> Self {
551        Self {
552            tcx,
553            repeat_strategy,
554            mode,
555            skip_invariant,
556            crate_filter,
557            module_filter,
558            debug_contracts,
559            struct_invariant_results: FxHashMap::default(),
560        }
561    }
562
563    fn repeat_rounds_for_target(&self, target: &FunctionTarget<'tcx>) -> (usize, Vec<usize>) {
564        match self.repeat_strategy {
565            RepeatStrategy::Fixed(n) => (n, (0..=n).collect()),
566            RepeatStrategy::Auto => {
567                let plan = LoopSensitivityAnalyzer::new(self.tcx).analyze(target);
568                let repeat = plan.repeat;
569                (repeat, (0..=repeat).collect())
570            }
571        }
572    }
573
574    /// With `--skip-invariant`, generate verification sequences for each read method
575    /// that chain through constructors and mutators.
576    ///
577    /// Produces sequences like:
578    /// - `constructor → method`
579    /// - `constructor → mutator → method`
580    ///
581    /// Each sequence propagates the constructor's `#[rapx::requires]` through
582    /// the mutator chain to serve as entry assumptions for the read method.
583    fn run_invless_sequences(&self, targets: &[FunctionTarget<'tcx>]) {
584        for target in targets {
585            let read_def_id = target.def_id;
586            let cons = get_cons(self.tcx, read_def_id);
587            if cons.is_empty() {
588                continue;
589            }
590            let muts = get_muts(self.tcx, read_def_id);
591
592            for &con_id in &cons {
593                let con_target = self.build_virtual_target(target, read_def_id, con_id, &[]);
594                self.verify_and_emit_sequence(read_def_id, &con_target, con_id, &[]);
595
596                for &mut_id in &muts {
597                    let con_target =
598                        self.build_virtual_target(target, read_def_id, con_id, &[mut_id]);
599                    self.verify_and_emit_sequence(read_def_id, &con_target, con_id, &[mut_id]);
600                }
601            }
602        }
603    }
604
605    fn build_virtual_target(
606        &self,
607        read_target: &FunctionTarget<'tcx>,
608        read_def_id: rustc_hir::def_id::DefId,
609        con_id: rustc_hir::def_id::DefId,
610        mut_ids: &[rustc_hir::def_id::DefId],
611    ) -> FunctionTarget<'tcx> {
612        let mut accumulated_requires: Vec<Property<'tcx>> = Vec::new();
613
614        // Start with the constructor's requires, remapped to refer to struct
615        // fields (self.field) instead of constructor parameters.
616        let con_contracts: Vec<Property<'tcx>> = get_contract_from_annotation(self.tcx, con_id)
617            .into_iter()
618            .map(|c| remap_constructor_contract(c))
619            .collect();
620        accumulated_requires.extend(con_contracts);
621
622        // Remove contracts that are invalidated by mutators
623        if !mut_ids.is_empty() {
624            let mut mutated_fields: Vec<usize> = Vec::new();
625            for &mut_id in mut_ids {
626                for field_idx in get_mutated_fields(self.tcx, mut_id) {
627                    if !mutated_fields.contains(&field_idx) {
628                        mutated_fields.push(field_idx);
629                    }
630                }
631            }
632            if !mutated_fields.is_empty() {
633                accumulated_requires.retain(|prop| {
634                    let prop_fields = property_field_indices(prop);
635                    !prop_fields.iter().any(|f| mutated_fields.contains(f))
636                });
637            }
638        }
639
640        // Also include the read method's own caller requires (which already
641        // contains struct invariants merged by build_function_target).
642        // This is broader than just `get_contract_from_annotation` because
643        // it propagates struct-level properties even when the method has no
644        // explicit `#[rapx::requires]`.
645        accumulated_requires.extend(read_target.caller_requires.clone());
646
647        FunctionTarget {
648            def_id: read_def_id,
649            owner_struct_def_id: read_target.owner_struct_def_id,
650            checkpoints: read_target.checkpoints.clone(),
651            callee_requires: read_target.callee_requires.clone(),
652            caller_requires: accumulated_requires,
653            struct_invariants: Vec::new(),
654            type_invariants: Vec::new(),
655            raw_ptr_deref_checks: read_target.raw_ptr_deref_checks.clone(),
656            static_mut_checks: read_target.static_mut_checks.clone(),
657        }
658    }
659
660    /// Run `verify_function` across `repeat_rounds`, collecting results and
661    /// returning the crash message (if any round panicked).
662    fn run_repeat_rounds(
663        &self,
664        target: &FunctionTarget<'tcx>,
665        repeat_rounds: &[usize],
666        skip_label: &str,
667        all_results: &mut Vec<PropertyCheckResult<'tcx>>,
668    ) -> Option<String> {
669        for &repeat in repeat_rounds {
670            let driver = VerifyDriver::new_with_repeat(self.tcx, target, repeat);
671            match crate::helpers::mir_utils::catch_panic(|| driver.verify_function()) {
672                Ok(report) => {
673                    rap_debug!("{}", report.describe());
674                    all_results.extend(report.results);
675                }
676                Err(msg) => {
677                    rap_warn!("Skipping {} (repeat {}): {msg}", skip_label, repeat);
678                    all_results.clear();
679                    return Some(format!("repeat {repeat}: {msg}"));
680                }
681            }
682        }
683        None
684    }
685
686    fn verify_and_emit_sequence(
687        &self,
688        read_def_id: rustc_hir::def_id::DefId,
689        con_target: &FunctionTarget<'tcx>,
690        con_id: rustc_hir::def_id::DefId,
691        mut_ids: &[rustc_hir::def_id::DefId],
692    ) {
693        let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
694
695        let (_, repeat_rounds) = self.repeat_rounds_for_target(con_target);
696        let crashed = self.run_repeat_rounds(
697            con_target,
698            &repeat_rounds,
699            &format!("constructor {}", self.tcx.def_path_str(con_id)),
700            &mut all_results,
701        );
702
703        let read_name = short_fn_name(self.tcx, read_def_id);
704        let con_name = short_fn_name(self.tcx, con_id);
705        let mut chain_parts: Vec<String> = vec![con_name];
706        for &mut_id in mut_ids {
707            chain_parts.push(short_fn_name(self.tcx, mut_id));
708        }
709        chain_parts.push(read_name);
710        let chain_label = chain_parts.join(" -> ");
711
712        rap_info!("============================================================");
713        rap_info!("[rapx::verify] sequence: {chain_label}");
714        rap_info!("============================================================");
715
716        if let Some(msg) = &crashed {
717            rap_warn!("  result: UNKNOWN (verifier crashed: {msg})");
718        } else if all_results.is_empty() {
719            rap_info!("  result: SOUND (no unsafe checkpoints)");
720        } else {
721            emit_results_and_verdict(self.tcx, &all_results);
722        }
723        rap_info!("");
724    }
725}
726
727impl<'tcx> Analysis for VerifyRun<'tcx> {
728    /// Collect verify targets, run the staged driver, and emit a compact summary.
729    ///
730    /// For each target, extracts paths with increasing `postfix-repeat`
731    /// levels from 0 to the configured maximum, running verification at each
732    /// level. Earlier rounds use fewer loop unrollings; later rounds incrementally
733    /// add deeper paths.
734    fn run(&mut self) {
735        // Register `pred!`-emitted `#[rapx::def_property("...")]` definitions.
736        crate::verify::contract::compound::register_compound_properties(self.tcx);
737
738        let collector = VerifyTargetCollector::collect_all(
739            self.tcx,
740            self.mode,
741            self.skip_invariant,
742            self.crate_filter.clone(),
743            self.module_filter.clone(),
744        );
745
746        if self.debug_contracts {
747            self.print_contracts_debug(&collector.function_targets, &collector.trait_targets);
748            return;
749        }
750
751        for target in &collector.function_targets {
752            let target_path = fmt_fn_path_with_bounds(self.tcx, target.def_id);
753            let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
754            let mut fn_crashed: Option<String>;
755
756            let (planned_repeat, repeat_rounds) = self.repeat_rounds_for_target(target);
757
758            // Phase 1: unsafe checkpoint verification
759            fn_crashed = self.run_repeat_rounds(
760                target,
761                &repeat_rounds,
762                &format!("function {}", target_path),
763                &mut all_results,
764            );
765
766            // Phase 2: struct invariant verification
767            if !target.struct_invariants.is_empty() && !self.skip_invariant {
768                let driver = VerifyDriver::new_with_repeat(self.tcx, target, planned_repeat);
769                match crate::helpers::mir_utils::catch_panic(|| driver.verify_struct_invariants()) {
770                    Ok(struct_report) => {
771                        rap_debug!("{}", struct_report.describe());
772                        all_results.extend(struct_report.results.clone());
773                        // Record the per-struct verdict so the type-level
774                        // `Allocated`/`Owning` checks can discharge the field
775                        // invariants only when they actually hold.
776                        if let Some(struct_id) = target.owner_struct_def_id {
777                            let verdict = struct_report
778                                .results
779                                .iter()
780                                .fold(CheckResult::ProvedByRule, |acc, r| {
781                                    acc.and(r.result.clone())
782                                });
783                            self.struct_invariant_results
784                                .entry(struct_id)
785                                .and_modify(|v| *v = v.clone().and(verdict.clone()))
786                                .or_insert(verdict);
787                        }
788                    }
789                    Err(msg) => {
790                        rap_warn!("Skipping struct invariants for {} : {msg}", target_path);
791                        all_results.clear();
792                        fn_crashed = Some(format!("struct-invariant: {msg}"));
793                    }
794                }
795            }
796
797            // Phase 3: built-in type invariant verification
798            if !target.type_invariants.is_empty() && !self.skip_invariant {
799                let driver = VerifyDriver::new_with_repeat(self.tcx, target, planned_repeat);
800                match crate::helpers::mir_utils::catch_panic(|| driver.verify_type_invariants()) {
801                    Ok(type_report) => {
802                        rap_debug!("{}", type_report.describe());
803                        all_results.extend(type_report.results.clone());
804                    }
805                    Err(msg) => {
806                        rap_warn!("Skipping type invariants for {} : {msg}", target_path);
807                        all_results.clear();
808                        fn_crashed = Some(format!("type-invariant: {msg}"));
809                    }
810                }
811            }
812
813            if let Some(msg) = &fn_crashed {
814                rap_info!("============================================================");
815                rap_info!("[rapx::verify] function: {target_path}");
816                rap_info!("============================================================");
817                rap_warn!("  result: UNKNOWN (verifier crashed: {msg})");
818                rap_info!("");
819                continue;
820            }
821
822            if all_results.is_empty() {
823                let all_callees_skipped = !target.checkpoints.is_empty()
824                    && target.checkpoints.iter().all(|ckpt| {
825                        ckpt.callee.is_some_and(|callee| {
826                            target
827                                .callee_requires
828                                .get(&callee)
829                                .is_none_or(|c| c.is_empty())
830                        })
831                    });
832                if (target.checkpoints.is_empty() || all_callees_skipped)
833                    && target.raw_ptr_deref_checks.is_empty()
834                    && target.static_mut_checks.is_empty()
835                    && target.struct_invariants.is_empty()
836                    && target.type_invariants.is_empty()
837                {
838                    rap_info!("============================================================");
839                    rap_info!("[rapx::verify] function: {target_path}");
840                    rap_info!("============================================================");
841                    if self.skip_invariant {
842                        let cons = get_cons(self.tcx, target.def_id);
843                        for con in &cons {
844                            rap_info!("  + constructor: {}", self.tcx.def_path_str(*con));
845                        }
846                    }
847                    rap_info!("  --- unsafe checkpoints ---");
848                    rap_info!("      <none>");
849                    rap_info!("        <none>");
850                    rap_info!("  result: SOUND (no unsafe checkpoints)");
851                    rap_info!("");
852                }
853                continue;
854            }
855
856            // When --skip-invariant is set, skip standalone emission for methods that
857            // have constructors — sequences will generate dedicated entries.
858            if self.skip_invariant && !get_cons(self.tcx, target.def_id).is_empty() {
859                continue;
860            }
861
862            emit_verify_summary(
863                self.tcx,
864                &target_path,
865                target.def_id,
866                &all_results,
867                self.skip_invariant,
868            );
869        }
870
871        // Emit detected unsafe trait impls (verification deferred)
872        let mut unsafe_traits: Vec<_> = collector
873            .trait_targets
874            .iter()
875            .filter(|t| matches!(&t.kind, TraitEnsuranceKind::Unsafe(_)))
876            .collect();
877        unsafe_traits.sort_by_key(|t| self.tcx.def_path_str(t.def_id));
878        for trait_target in unsafe_traits {
879            let TraitEnsuranceKind::Unsafe(ensures) = &trait_target.kind else {
880                continue;
881            };
882            rap_info!("============================================================");
883            rap_info!(
884                "[rapx::verify] unsafe trait impl: {}",
885                self.tcx.def_path_str(trait_target.def_id)
886            );
887            rap_info!("============================================================");
888            if let Some(self_ty) = trait_target.self_ty_def_id {
889                rap_info!("  impl for: {}", self.tcx.def_path_str(self_ty));
890            }
891            if ensures.is_empty() {
892                rap_info!("  ensures: <none>");
893            } else {
894                rap_info!("  ensures (implementor must satisfy):");
895                for (method_name, contracts) in ensures {
896                    rap_info!("    fn {}:", method_name);
897                    for property in dedup_compound_props(contracts.iter()) {
898                        rap_info!(
899                            "      - {}",
900                            property.display_for_report(
901                                self.tcx,
902                                trait_target.self_ty_def_id,
903                                None,
904                            )
905                        );
906                    }
907                }
908            }
909            rap_info!("  verification: deferred");
910            rap_info!("");
911        }
912
913        // Verify marker-trait (`Send`/`Sync`) impls structurally.
914        let marker_targets: Vec<_> = collector
915            .trait_targets
916            .iter()
917            .filter(|t| matches!(&t.kind, TraitEnsuranceKind::Marker(..)))
918            .collect();
919        if !marker_targets.is_empty() {
920            self.emit_marker_trait_ensurance(&marker_targets);
921        }
922
923        // --skip-invariant: generate constructor-mutator-method sequences
924        if self.skip_invariant {
925            self.run_invless_sequences(&collector.function_targets);
926        }
927    }
928}
929
930impl<'tcx> VerifyRun<'tcx> {
931    /// Structurally verify collected `Send`/`Sync` marker-trait impls.
932    fn emit_marker_trait_ensurance(&self, units: &[&TraitEnsurance<'tcx>]) {
933        for unit in units {
934            let TraitEnsuranceKind::Marker(kind, obligations) = &unit.kind else {
935                continue;
936            };
937            let trait_name = match kind {
938                MarkerTraitKind::Send => "Send",
939                MarkerTraitKind::Sync => "Sync",
940            };
941            let is_sync = matches!(kind, MarkerTraitKind::Sync);
942            let self_ty_label = unit
943                .self_ty_def_id
944                .map(|d| self.tcx.def_path_str(d))
945                .unwrap_or_else(|| "<unknown>".to_string());
946
947            rap_info!("============================================================");
948            rap_info!("[rapx::verify] unsafe impl {trait_name} for {self_ty_label}");
949            rap_info!("============================================================");
950
951            if obligations.is_empty() {
952                rap_info!("  ensures: <none>");
953            }
954
955            let mut any_failed = false;
956            let mut any_unknown = false;
957            for property in obligations {
958                let Some(self_ty) = unit
959                    .self_ty_def_id
960                    .map(|d| self.tcx.type_of(d).skip_binder())
961                else {
962                    any_unknown = true;
963                    continue;
964                };
965                let result =
966                    self.check_type_obligation(property, unit.impl_def_id, self_ty, is_sync);
967                let label = property.display_for_report(self.tcx, unit.self_ty_def_id, None);
968                let verdict = match result {
969                    CheckResult::ProvedByRule | CheckResult::ProvedBySmt => "PROVED",
970                    CheckResult::Failed => "FAILED",
971                    CheckResult::Unknown(_) => "UNKNOWN",
972                };
973                rap_info!("  - {label} => {verdict}");
974                any_failed |= matches!(result, CheckResult::Failed);
975                any_unknown |= matches!(result, CheckResult::Unknown(_));
976            }
977
978            let verdict = if any_failed {
979                "UNSAFE (obligation failed)"
980            } else if any_unknown {
981                "UNKNOWN"
982            } else {
983                "SAFE (all obligations proved)"
984            };
985            rap_info!("  verdict: {verdict}");
986            rap_info!("");
987        }
988    }
989
990    /// Dispatch a single type-level obligation (`ContainNoType` / `NoRawPtr` /
991    /// `NoInternalMut` / `UniInternalMut` / `AtomicUpdate` / `Allocated` /
992    /// `Owning` / `RefSend`).
993    fn check_type_obligation(
994        &self,
995        property: &Property<'tcx>,
996        impl_def_id: rustc_hir::def_id::DefId,
997        self_ty: rustc_middle::ty::Ty<'tcx>,
998        is_sync: bool,
999    ) -> CheckResult {
1000        match property {
1001            Property::Atom(atom) => match atom.kind {
1002                PropertyKind::ContainNoType => {
1003                    let Some(ty) = atom.args.first().and_then(|a| match a {
1004                        PropertyArg::Ty(t) => Some(*t),
1005                        _ => None,
1006                    }) else {
1007                        return CheckResult::Unknown(UnknownReason::Unimplemented);
1008                    };
1009                    let negatives: Vec<String> = atom.args[1..]
1010                        .iter()
1011                        .filter_map(|a| match a {
1012                            PropertyArg::Ident(n) => Some(n.clone()),
1013                            _ => None,
1014                        })
1015                        .collect();
1016                    contain_no_type_check(self.tcx, ty, &negatives, impl_def_id, is_sync)
1017                }
1018                PropertyKind::NoRawPtr => {
1019                    let Some(ty) = atom.args.first().and_then(|a| match a {
1020                        PropertyArg::Ty(t) => Some(*t),
1021                        _ => None,
1022                    }) else {
1023                        return CheckResult::Unknown(UnknownReason::Unimplemented);
1024                    };
1025                    no_raw_ptr_check(self.tcx, ty, impl_def_id, is_sync)
1026                }
1027                PropertyKind::NoInternalMut => {
1028                    let Some(ty) = atom.args.first().and_then(|a| match a {
1029                        PropertyArg::Ty(t) => Some(*t),
1030                        _ => None,
1031                    }) else {
1032                        return CheckResult::Unknown(UnknownReason::Unimplemented);
1033                    };
1034                    no_internal_mut_check(self.tcx, ty)
1035                }
1036                PropertyKind::UniInternalMut => {
1037                    let Some(ty) = atom.args.first().and_then(|a| match a {
1038                        PropertyArg::Ty(t) => Some(*t),
1039                        _ => None,
1040                    }) else {
1041                        return CheckResult::Unknown(UnknownReason::Unimplemented);
1042                    };
1043                    uni_internal_mut_check(self.tcx, ty)
1044                }
1045                PropertyKind::Allocated | PropertyKind::Owning => {
1046                    // Type-level `Allocated(ptr, T, n)` / `Owning(ptr)` reached
1047                    // through a `TamedRawPtr` compound: check that the type declares
1048                    // a matching `#[rapx::invariant(...)]` (optionally restricted to
1049                    // the field named in the property) and that it verified.
1050                    let adt_def_id = match self_ty.kind() {
1051                        rustc_middle::ty::TyKind::Adt(adt_def, _) => adt_def.did(),
1052                        _ => return CheckResult::Unknown(UnknownReason::Unimplemented),
1053                    };
1054                    let field = atom.args.first().and_then(|a| {
1055                        crate::verify::contract::place::field_name_from_arg(self.tcx, adt_def_id, a)
1056                    });
1057                    field_invariant_check(
1058                        self.tcx,
1059                        self_ty,
1060                        atom.kind,
1061                        field.as_deref(),
1062                        &self.struct_invariant_results,
1063                    )
1064                }
1065                PropertyKind::AtomicUpdate => {
1066                    let Some(ty) = atom.args.first().and_then(|a| match a {
1067                        PropertyArg::Ty(t) => Some(*t),
1068                        _ => None,
1069                    }) else {
1070                        return CheckResult::Unknown(UnknownReason::Unimplemented);
1071                    };
1072                    atomic_update_check(self.tcx, ty, impl_def_id, is_sync)
1073                }
1074                PropertyKind::RefSend => {
1075                    let Some(ty) = atom.args.first().and_then(|a| match a {
1076                        PropertyArg::Ty(t) => Some(*t),
1077                        _ => None,
1078                    }) else {
1079                        return CheckResult::Unknown(UnknownReason::Unimplemented);
1080                    };
1081                    ref_send_check(self.tcx, ty, impl_def_id, is_sync)
1082                }
1083                _ => CheckResult::Unknown(UnknownReason::Unimplemented),
1084            },
1085            Property::And(and) => {
1086                let mut overall = CheckResult::ProvedByRule;
1087                for conjunct in &and.conjuncts {
1088                    overall = overall.and(self.check_type_obligation(
1089                        conjunct,
1090                        impl_def_id,
1091                        self_ty,
1092                        is_sync,
1093                    ));
1094                }
1095                overall
1096            }
1097            Property::Or(or) => {
1098                let mut overall = CheckResult::Failed;
1099                for disjunct in &or.disjuncts {
1100                    overall = overall.or(self.check_type_obligation(
1101                        disjunct,
1102                        impl_def_id,
1103                        self_ty,
1104                        is_sync,
1105                    ));
1106                }
1107                overall
1108            }
1109        }
1110    }
1111
1112    fn print_contracts_debug(
1113        &self,
1114        targets: &[FunctionTarget<'tcx>],
1115        trait_targets: &[TraitEnsurance<'tcx>],
1116    ) {
1117        rap_info!("{:=<1$}", "", 76);
1118        rap_info!("[rapx::debug-contracts] Expanded Contract Assertions");
1119        rap_info!("{:=<1$}", "", 76);
1120        rap_info!("");
1121
1122        let mut struct_groups: FxHashMap<rustc_hir::def_id::DefId, Vec<&FunctionTarget<'tcx>>> =
1123            FxHashMap::default();
1124        let mut free_targets: Vec<&FunctionTarget<'tcx>> = Vec::new();
1125
1126        for target in targets {
1127            if let Some(sid) = target.owner_struct_def_id {
1128                struct_groups.entry(sid).or_default().push(target);
1129            } else {
1130                free_targets.push(target);
1131            }
1132        }
1133
1134        let mut struct_ids: Vec<_> = struct_groups.keys().copied().collect();
1135        struct_ids.sort_by_key(|did| self.tcx.def_path_str(*did));
1136
1137        for struct_def_id in struct_ids {
1138            let methods = &struct_groups[&struct_def_id];
1139            let struct_name = self.tcx.def_path_str(struct_def_id);
1140
1141            // -- Struct invariants (once) --
1142            let inv_target = methods.iter().find(|t| !t.struct_invariants.is_empty());
1143            let have_invariants = inv_target.is_some();
1144
1145            if have_invariants || methods.iter().any(|t| self.has_printable_contracts(t)) {
1146                rap_info!("{:=<1$}", "", 76);
1147                rap_info!("[rapx::debug-contracts] struct: {struct_name}");
1148                rap_info!("{:=<1$}", "", 76);
1149            }
1150
1151            if let Some(tgt) = inv_target {
1152                rap_info!("  [Struct Invariants]:");
1153                let invariants = dedup_compound_props(tgt.struct_invariants.iter());
1154                let inv_count = invariants.len();
1155                for (ii, property) in invariants.iter().enumerate() {
1156                    let ibranch = if ii + 1 == inv_count { "`-" } else { "|-" };
1157                    let (call, meaning) = fmt_contract_expanded(
1158                        self.tcx,
1159                        property,
1160                        tgt.owner_struct_def_id,
1161                        Some(tgt.def_id),
1162                    );
1163                    self.print_contract_lines("  ", ibranch, &call, &meaning);
1164                }
1165                rap_info!("");
1166            }
1167
1168            // -- Each method --
1169            let mut printed = false;
1170            for (mi, target) in methods.iter().enumerate() {
1171                let is_last_method = mi + 1 == methods.len();
1172                let branch = if is_last_method { "`-" } else { "|-" };
1173                let cont = if is_last_method { "  " } else { "| " };
1174                if self.print_target_contracts(target, branch, cont) {
1175                    printed = true;
1176                }
1177            }
1178            if printed {
1179                rap_info!("{:=<1$}", "", 76);
1180                rap_info!("");
1181            }
1182        }
1183
1184        // -- Free functions --
1185        for target in &free_targets {
1186            self.print_target_contracts(target, "- ", "  ");
1187        }
1188
1189        // -- Traits (ensures / marker obligations) --
1190        let mut traits = trait_targets.iter().collect::<Vec<_>>();
1191        traits.sort_by_key(|t| self.tcx.def_path_str(t.def_id));
1192        for trait_target in traits {
1193            match &trait_target.kind {
1194                TraitEnsuranceKind::Unsafe(ensures) => {
1195                    let trait_path = self.tcx.def_path_str(trait_target.def_id);
1196                    rap_info!("{:=<1$}", "", 76);
1197                    rap_info!("[rapx::debug-contracts] unsafe trait: {trait_path}");
1198                    rap_info!("{:=<1$}", "", 76);
1199                    if let Some(self_ty) = trait_target.self_ty_def_id {
1200                        rap_info!("  impl for: {}", self.tcx.def_path_str(self_ty));
1201                    }
1202                    if ensures.is_empty() {
1203                        rap_info!("  ensures: <none>");
1204                    } else {
1205                        for (method_name, contracts) in ensures {
1206                            rap_info!("  fn {method_name}:");
1207                            for property in dedup_compound_props(contracts.iter()) {
1208                                let (call, meaning) = fmt_contract_expanded(
1209                                    self.tcx,
1210                                    property,
1211                                    trait_target.self_ty_def_id,
1212                                    Some(trait_target.def_id),
1213                                );
1214                                self.print_contract_lines("  ", "|-", &call, &meaning);
1215                            }
1216                        }
1217                    }
1218                    rap_info!("");
1219                }
1220                TraitEnsuranceKind::Marker(kind, obligations) => {
1221                    let name = match kind {
1222                        MarkerTraitKind::Send => "Send",
1223                        MarkerTraitKind::Sync => "Sync",
1224                    };
1225                    rap_info!("{:=<1$}", "", 76);
1226                    rap_info!("[rapx::debug-contracts] marker trait: {name}");
1227                    rap_info!("{:=<1$}", "", 76);
1228                    if let Some(self_ty) = trait_target.self_ty_def_id {
1229                        rap_info!("  impl for: {}", self.tcx.def_path_str(self_ty));
1230                    }
1231                    if obligations.is_empty() {
1232                        rap_info!("  obligations: <none>");
1233                    } else {
1234                        for property in obligations {
1235                            let (call, meaning) = fmt_contract_expanded(
1236                                self.tcx,
1237                                property,
1238                                trait_target.self_ty_def_id,
1239                                None,
1240                            );
1241                            self.print_contract_lines("  ", "|-", &call, &meaning);
1242                        }
1243                    }
1244                    rap_info!("");
1245                }
1246            }
1247        }
1248    }
1249
1250    fn is_unsafe_fn(&self, def_id: rustc_hir::def_id::DefId) -> bool {
1251        self.tcx.fn_sig(def_id).skip_binder().safety() == rustc_hir::Safety::Unsafe
1252    }
1253
1254    fn has_caller_contracts(&self, target: &FunctionTarget<'tcx>, is_unsafe_fn: bool) -> bool {
1255        is_unsafe_fn
1256            && target
1257                .caller_requires
1258                .iter()
1259                .any(|p| p.kind() != Some(PropertyKind::Unknown))
1260    }
1261
1262    fn has_printable_contracts(&self, target: &FunctionTarget<'tcx>) -> bool {
1263        let is_unsafe_fn = self.is_unsafe_fn(target.def_id);
1264        self.has_caller_contracts(target, is_unsafe_fn)
1265            || target
1266                .callee_requires
1267                .values()
1268                .any(|c| c.iter().any(|p| p.kind() != Some(PropertyKind::Unknown)))
1269    }
1270
1271    fn print_contract_lines(&self, prefix: &str, branch: &str, call: &str, meaning: &str) {
1272        rap_info!("{prefix}{branch} {call}");
1273        let cont = if branch == "`-" { "  " } else { "| " };
1274        for line in meaning.lines() {
1275            rap_info!("{prefix}{cont} {line}");
1276        }
1277    }
1278
1279    fn print_target_contracts(
1280        &self,
1281        target: &FunctionTarget<'tcx>,
1282        branch: &str,
1283        cont: &str,
1284    ) -> bool {
1285        use crate::verify::contract::PropertyKind;
1286
1287        let (arg_names_typed, ret_ty) = self.resolve_arg_names_with_types(target.def_id);
1288        let is_unsafe_fn = self.is_unsafe_fn(target.def_id);
1289
1290        let target_path = fmt_fn_path_with_generics(self.tcx, target.def_id);
1291        let short_name = short_fn_name(self.tcx, target.def_id);
1292
1293        // Collect what to print first
1294        let has_caller = self.has_caller_contracts(target, is_unsafe_fn);
1295        let mut callee_ids: Vec<_> = target.callee_requires.keys().copied().collect();
1296        callee_ids.retain(|did| {
1297            target
1298                .callee_requires
1299                .get(did)
1300                .is_some_and(|c| c.iter().any(|p| p.kind() != Some(PropertyKind::Unknown)))
1301        });
1302        callee_ids.sort_by_key(|did| self.tcx.def_path_str(*did));
1303        let has_callees = !callee_ids.is_empty();
1304
1305        if !has_caller && !has_callees {
1306            return false;
1307        }
1308
1309        let fn_display = fmt_fn_with_params(&target_path, &arg_names_typed, ret_ty.as_deref());
1310        let header = format!("--- method: {short_name}");
1311        let dashes = 72usize.saturating_sub(header.len());
1312        rap_info!("{branch} {header} {}", "-".repeat(dashes));
1313        rap_info!("{cont}  {fn_display}");
1314
1315        // Caller Contracts (only for unsafe functions)
1316        if has_caller {
1317            rap_info!("{cont}  [Caller Contracts]:");
1318            let caller_props = dedup_compound_props(
1319                target
1320                    .caller_requires
1321                    .iter()
1322                    .filter(|p| p.kind() != Some(PropertyKind::Unknown)),
1323            );
1324            for (pi, property) in caller_props.iter().enumerate() {
1325                let is_last = pi + 1 == caller_props.len();
1326                let pbranch = if is_last { "`-" } else { "|-" };
1327                let (call, meaning) = fmt_contract_expanded(
1328                    self.tcx,
1329                    property,
1330                    target.owner_struct_def_id,
1331                    Some(target.def_id),
1332                );
1333                self.print_contract_lines(&format!("{cont}  "), pbranch, &call, &meaning);
1334            }
1335            if !has_callees {
1336                rap_info!("");
1337            }
1338        }
1339
1340        // Callee Contracts (for each unsafe callee)
1341        if has_callees {
1342            rap_info!("{cont}  [Unsafe Callees]:");
1343            for (ci, &callee_id) in callee_ids.iter().enumerate() {
1344                let is_last_callee = ci + 1 == callee_ids.len();
1345                let cbranch = if is_last_callee { "`-" } else { "|-" };
1346                let ccont = if is_last_callee { "  " } else { "| " };
1347                let contracts = target.callee_requires.get(&callee_id).unwrap();
1348                let (callee_typed, callee_ret) = self.resolve_arg_names_with_types(callee_id);
1349                let callee_path = fmt_fn_path_with_generics(self.tcx, callee_id);
1350                rap_info!(
1351                    "{cont}  {cbranch} {}",
1352                    fmt_fn_with_params(&callee_path, &callee_typed, callee_ret.as_deref())
1353                );
1354                let props = dedup_compound_props(
1355                    contracts
1356                        .iter()
1357                        .filter(|p| p.kind() != Some(PropertyKind::Unknown)),
1358                );
1359                for (pi, property) in props.iter().enumerate() {
1360                    let is_last_prop = pi + 1 == props.len();
1361                    let pbranch = if is_last_prop { "`-" } else { "|-" };
1362                    let (call, meaning) =
1363                        fmt_contract_expanded(self.tcx, property, None, Some(callee_id));
1364                    self.print_contract_lines(
1365                        &format!("{cont}  {ccont}"),
1366                        pbranch,
1367                        &call,
1368                        &meaning,
1369                    );
1370                }
1371            }
1372        }
1373
1374        rap_info!("");
1375        true
1376    }
1377
1378    fn resolve_arg_names_with_types(
1379        &self,
1380        def_id: rustc_hir::def_id::DefId,
1381    ) -> (Vec<String>, Option<String>) {
1382        if !self.tcx.is_mir_available(def_id) {
1383            return (Vec::new(), None);
1384        }
1385        let body = self.tcx.optimized_mir(def_id);
1386        let args: Vec<String> = body
1387            .local_decls
1388            .iter()
1389            .enumerate()
1390            .skip(1)
1391            .take(body.arg_count)
1392            .map(|(i, decl)| {
1393                let name = {
1394                    let span = decl.source_info.span;
1395                    self.tcx
1396                        .sess
1397                        .source_map()
1398                        .span_to_snippet(span)
1399                        .unwrap_or_else(|_| format!("_{}", i))
1400                };
1401                let ty = decl.ty.to_string();
1402                format!("{name}: {ty}")
1403            })
1404            .collect();
1405        let ret_ty = self.tcx.fn_sig(def_id).skip_binder().output().skip_binder();
1406        let ret_ty = if ret_ty.is_unit() {
1407            None
1408        } else {
1409            Some(ret_ty.to_string())
1410        };
1411        (args, ret_ty)
1412    }
1413}
1414
1415use crate::helpers::name::short_fn_name;
1416
1417/// Collect struct field indices referenced by a property's contract places.
1418///
1419/// Used to determine which invariants are invalidated when a mutator writes
1420/// to specific struct fields.
1421fn property_field_indices(property: &crate::verify::contract::Property<'_>) -> Vec<usize> {
1422    use crate::verify::contract::{ContractExpr, PropertyArg};
1423    let mut indices = Vec::new();
1424    for arg in property.args() {
1425        let place = match arg {
1426            PropertyArg::Expr(ContractExpr::Place(p)) => Some(p),
1427            _ => None,
1428        };
1429        if let Some(place) = place {
1430            for proj in &place.projections {
1431                match proj {
1432                    crate::verify::contract::ContractProjection::Field { index, .. } => {
1433                        let idx = *index;
1434                        if !indices.contains(&idx) {
1435                            indices.push(idx);
1436                        }
1437                    }
1438                    crate::verify::contract::ContractProjection::Downcast { .. } => {}
1439                    crate::verify::contract::ContractProjection::ForEach => {}
1440                }
1441            }
1442        }
1443    }
1444    indices
1445}
1446
1447/// Whether an invariant targets the function's return value (as opposed to a
1448/// parameter / the receiver).  Only `Atom` invariants carry a first-argument
1449/// place; `And`/`Or` invariants have no `target_place`, so they are never
1450/// treated as return-value invariants.
1451fn targets_return_value(property: &Property<'_>) -> bool {
1452    property
1453        .target_place()
1454        .is_some_and(|cp| matches!(cp.base, PlaceBase::Return))
1455}
1456
1457fn remap_constructor_contract<'tcx>(
1458    property: crate::verify::contract::Property<'tcx>,
1459) -> crate::verify::contract::Property<'tcx> {
1460    use crate::verify::contract::{
1461        ContractExpr, ContractPlace, ContractProjection, PlaceBase, PropertyArg,
1462    };
1463
1464    fn remap_place_arg<'tcx>(arg: &PropertyArg<'tcx>) -> PropertyArg<'tcx> {
1465        let place = match arg {
1466            PropertyArg::Expr(ContractExpr::Place(p)) => p,
1467            _ => return arg.clone(),
1468        };
1469        let PlaceBase::Arg(field_idx) = place.base else {
1470            return arg.clone();
1471        };
1472        let projection = ContractProjection::Field {
1473            index: field_idx,
1474            ty: None,
1475        };
1476        let mut new_place = ContractPlace {
1477            base: PlaceBase::Arg(0),
1478            projections: vec![projection],
1479        };
1480        new_place
1481            .projections
1482            .extend(place.projections.iter().cloned());
1483        PropertyArg::Expr(ContractExpr::Place(new_place))
1484    }
1485
1486    let new_args: Vec<PropertyArg<'tcx>> = property
1487        .args()
1488        .iter()
1489        .map(|arg| remap_place_arg(arg))
1490        .collect();
1491
1492    match property {
1493        crate::verify::contract::Property::Atom(mut atom) => {
1494            atom.args = new_args;
1495            crate::verify::contract::Property::Atom(atom)
1496        }
1497        crate::verify::contract::Property::And(and) => crate::verify::contract::Property::And(and),
1498        crate::verify::contract::Property::Or(or) => crate::verify::contract::Property::Or(or),
1499    }
1500}