Skip to main content

rapx/verify/
target.rs

1use crate::analysis::Analysis;
2use crate::analysis::safety_flow::root::{
3    function_has_struct_invariant, function_has_trait_ensurance, hir_contains_unsafe,
4};
5use crate::cli::VerifyMode;
6use crate::helpers::mir_scan::{collect_raw_ptr_deref_info, collect_static_mut_access_info};
7use crate::helpers::name::short_fn_name;
8use rustc_hir::{
9    Attribute, BodyId, FnDecl, ItemKind,
10    def_id::{DefId, LocalDefId},
11    intravisit::{FnKind, Visitor},
12};
13#[cfg(not(rapx_ge_100))]
14use rustc_hir::LangItem;
15#[cfg(rapx_ge_100)]
16use rustc_hir::attrs::lang_items::LangItem;
17use rustc_middle::{hir::nested_filter, ty::TyCtxt};
18use rustc_span::Span;
19use std::collections::{HashMap, HashSet};
20use crate::compat::FxHashMap;
21
22use super::{
23    contract::{
24        ContractExpr, ContractPlace, PlaceBase, Property, PropertyArg, PropertyKind,
25        attr::parse_rapx_attr,
26    },
27    path_extractor::PathExtractor,
28};
29use crate::helpers::mir_utils::{
30    collect_return_block_indices, get_owner_struct_def_id, has_rapx_verify_attr,
31    is_std_crate_def_id, is_trait_unsafe, resolve_impl_self_ty_def_id,
32};
33use crate::helpers::mir_scan::{Checkpoint, collect_unsafe_callsites};
34
35/// A list of parsed `requires` contracts.
36pub type FnContracts<'tcx> = Vec<Property<'tcx>>;
37
38/// A list of parsed struct invariants.
39pub type StructInvariants<'tcx> = Vec<Property<'tcx>>;
40
41/// Collected verification data for a single function under analysis.
42///
43/// `FunctionTarget` is the complete **problem statement** for one function: it
44/// records every unsafe operation found in the function's MIR body, the safety
45/// contracts that each operation demands, and any contracts or invariants that
46/// serves as entry assumptions or structural guarantees.
47///
48/// # How it is built
49///
50/// [`VerifyTargetCollector::build_function_target`] assembles a `FunctionTarget`
51/// in one pass over the MIR body:
52///
53/// 1. Unsafe checkpoints are collected via [`collect_unsafe_callsites`].
54/// 2. Each unique callee `DefId` gets its `#[rapx::requires]` contracts parsed
55///    (with fallback to bundled JSON contracts for standard-library callees).
56/// 3. Raw pointer dereferences are detected and converted into synthetic
57///    (pseudo-checkpoint, `[ValidPtr, Align, (Typed)]`) pairs.
58/// 4. The caller's own `#[rapx::requires]` contracts become entry assumptions.
59/// 5. If the function is a method on a struct, struct-level `#[rapx::invariant]`
60///    and `#[rapx::requires]` annotations are collected.
61///
62/// # Role in the pipeline
63///
64/// The [`VerifyDriver`](super::driver::VerifyDriver) consumes a `FunctionTarget`
65/// to route each unsafe operation to the verifier engine along reachability paths
66/// extracted from the MIR CFG.  The target is the primary data carrier between
67/// the *target collection* stage and the *path extraction / verification* stage.
68#[derive(Clone, Debug)]
69pub struct FunctionTarget<'tcx> {
70    /// The function being verified.
71    pub def_id: DefId,
72
73    /// Owning struct when this function is an associated method (e.g.
74    /// `impl MyStruct { fn foo(...) }`).  `None` for free functions.
75    ///
76    /// Used to associate struct invariants and to group method-level
77    /// verification results under the owning struct in diagnostic output.
78    pub owner_struct_def_id: Option<DefId>,
79
80    /// All call-terminator-based unsafe checkpoints found in this function's MIR.
81    ///
82    /// Each [`Checkpoint`] records the callee `DefId`, the source-span of the
83    /// call, the basic-block location, and the MIR operands passed as arguments.
84    pub checkpoints: Vec<Checkpoint<'tcx>>,
85
86    /// Safety contracts demanded by each unique unsafe callee reachable from
87    /// this function, keyed by callee `DefId`.
88    ///
89    /// Contracts are sourced from `#[rapx::requires(...)]` annotations on the
90    /// callee (inline mode) or from a bundled JSON contract database for
91    /// standard-library functions.  Each value is a `Vec<Property>` — the
92    /// concrete safety requirements the callee expects its caller to satisfy.
93    pub callee_requires: HashMap<DefId, FnContracts<'tcx>>,
94
95    /// Safety contracts that the **caller itself** requires as entry
96    /// assumptions, parsed from `#[rapx::requires(...)]` on this function.
97    ///
98    /// During verification the engine prepends these properties as *facts* that
99    /// are assumed to hold at function entry, constraining the backward
100    /// data-dependency analysis and forward simulation.
101    pub caller_requires: FnContracts<'tcx>,
102
103    /// Struct invariants that methods of the owning struct must maintain.
104    ///
105    /// Collected from `#[rapx::invariant(...)]` / `#[rapx::requires(...)]`
106    /// annotations on the struct definition.  Checked at constructor return
107    /// blocks and at all path endpoints for non-constructor methods.
108    pub struct_invariants: Vec<Property<'tcx>>,
109
110    /// Raw pointer dereference checks with their required safety properties.
111    ///
112    /// Each entry is a `(Checkpoint, Vec<Property>)` pair where the `Checkpoint`
113    /// carries a synthetic dummy `DefId` (so the path extractor can treat
114    /// dereferences uniformly with checkpoints) and the properties encode the
115    /// pointer-validity requirements: always [`Allocated`](PropertyKind::Allocated),
116    /// [`InBound`](PropertyKind::InBound), and [`Align`](PropertyKind::Align);
117    /// additionally [`Typed`](PropertyKind::Typed) when the dereference is a read.
118    pub raw_ptr_deref_checks: Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)>,
119
120    /// Static mut access checks with their required safety properties.
121    ///
122    /// Each entry is a `(Checkpoint, Vec<Property>)` pair following the same
123    /// pattern as [`raw_ptr_deref_checks`](Self::raw_ptr_deref_checks).  The
124    /// properties are [`Allocated`](PropertyKind::Allocated),
125    /// [`InBound`](PropertyKind::InBound), [`Align`](PropertyKind::Align),
126    /// and [`Init`](PropertyKind::Init)
127    /// (conservatively checked for both reads and writes).
128    pub static_mut_checks: Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)>,
129}
130
131impl<'tcx> FunctionTarget<'tcx> {
132    pub fn all_checkpoints(&self) -> Vec<&Checkpoint<'tcx>> {
133        self.checkpoints
134            .iter()
135            .chain(
136                self.raw_ptr_deref_checks
137                    .iter()
138                    .map(|(checkpoint, _)| checkpoint),
139            )
140            .chain(
141                self.static_mut_checks
142                    .iter()
143                    .map(|(checkpoint, _)| checkpoint),
144            )
145            .collect()
146    }
147
148    pub fn properties_for_callsite(&self, checkpoint: &Checkpoint<'tcx>) -> &[Property<'tcx>] {
149        let loc = checkpoint.location();
150        match checkpoint.kind {
151            crate::helpers::mir_scan::CheckpointKind::RawPtrDeref => self
152                .raw_ptr_deref_checks
153                .iter()
154                .find(|(candidate, _)| candidate.location() == loc)
155                .map(|(_, properties)| properties.as_slice())
156                .unwrap_or(&[]),
157            crate::helpers::mir_scan::CheckpointKind::StaticMutAccess => self
158                .static_mut_checks
159                .iter()
160                .find(|(candidate, _)| candidate.location() == loc)
161                .map(|(_, properties)| properties.as_slice())
162                .unwrap_or(&[]),
163            crate::helpers::mir_scan::CheckpointKind::UnsafeCall => checkpoint
164                .callee
165                .and_then(|callee| self.callee_requires.get(&callee))
166                .map(Vec::as_slice)
167                .unwrap_or(&[]),
168        }
169    }
170}
171
172/// Collected verification data for a struct that owns methods marked with `#[rapx::verify]`.
173pub struct StructTarget<'tcx> {
174    /// Struct that owns one or more methods selected as targets to verify.
175    pub def_id: DefId,
176    /// Parsed `invariant` contracts attached to the struct.
177    pub invariants: StructInvariants<'tcx>,
178    /// Methods of this struct selected as targets to verify.
179    pub function_targets: Vec<FunctionTarget<'tcx>>,
180}
181
182/// Collected verification data for an `impl unsafe Trait for Type` block.
183///
184/// The trait's `#[rapx::ensures(...)]` contracts define safety obligations the
185/// implementor must satisfy.  Full verification of trait impls is deferred.
186pub struct TraitEnsurance<'tcx> {
187    /// The unsafe trait definition.
188    pub def_id: DefId,
189    /// The concrete type that implements the trait (e.g. `SomeStruct`).
190    pub self_ty_def_id: Option<DefId>,
191    /// `ensures` contracts grouped by trait method name.
192    pub ensures: Vec<(String, FnContracts<'tcx>)>,
193}
194
195/// Follow an unsafe callee's call chain to find inherited safety contracts.
196///
197/// When an unsafe callee (e.g. B) lacks its own contracts, look into its MIR
198/// body for the unsafe callees it calls (e.g. C, D).  If one of those has
199/// contracts (e.g. D), inherit them.  The chain `A -> B -> C -> D` means A's
200/// checkpoint on B is verified using D's contracts.
201///
202/// `visited` prevents infinite recursion on mutually-recursive functions.
203fn resolve_chain_contracts<'tcx>(
204    tcx: TyCtxt<'tcx>,
205    callee_def_id: DefId,
206    visited: &mut HashSet<DefId>,
207) -> FnContracts<'tcx> {
208    if !visited.insert(callee_def_id) {
209        return Vec::new();
210    }
211
212    if !tcx.is_mir_available(callee_def_id) {
213        return Vec::new();
214    }
215
216    let body = tcx.optimized_mir(callee_def_id);
217    let mut contracts = Vec::new();
218
219    for bb in body.basic_blocks.iter() {
220        let Some(terminator) = &bb.terminator else {
221            continue;
222        };
223        if let rustc_middle::mir::TerminatorKind::Call { func, .. } = &terminator.kind {
224            if let rustc_middle::mir::Operand::Constant(c) = func {
225                let rustc_middle::ty::TyKind::FnDef(sub_def_id, _) = c.const_.ty().kind() else {
226                    continue;
227                };
228                let sub_def_id = *sub_def_id;
229
230                let fn_sig = tcx.fn_sig(sub_def_id).skip_binder();
231                if fn_sig.safety() != rustc_hir::Safety::Unsafe {
232                    continue;
233                }
234
235                // Try annotation first.
236                let mut reqs = get_contract_from_annotation(tcx, sub_def_id);
237
238                // Try trait method requires.
239                if reqs.is_empty() {
240                    reqs = get_trait_method_requires(tcx, sub_def_id);
241                }
242
243                // Try std contracts database.
244                if reqs.is_empty() && is_std_crate_def_id(tcx, sub_def_id) {
245                    reqs = super::contract::query_json_contracts(tcx, sub_def_id);
246                }
247
248                // If still no contracts, recurse into this callee.
249                if reqs.is_empty() {
250                    reqs = resolve_chain_contracts(tcx, sub_def_id, visited);
251                }
252
253                contracts.extend(reqs);
254            }
255        }
256    }
257
258    contracts
259}
260
261/// Visitor that collects targets annotated with `#[rapx::verify]`.
262pub struct VerifyTargetCollector<'tcx> {
263    tcx: TyCtxt<'tcx>,
264    mode: VerifyMode,
265    skip_invariant: bool,
266    crate_filter: Option<String>,
267    crate_filter_matched: bool,
268    module_filter: Option<String>,
269    module_filter_matched: bool,
270    /// All function targets to verify collected from the current crate.
271    pub function_targets: Vec<FunctionTarget<'tcx>>,
272    /// All struct targets to verify collected from the current crate.
273    pub struct_targets: HashMap<DefId, StructTarget<'tcx>>,
274    /// All trait targets to verify collected from the current crate.
275    pub trait_targets: HashMap<DefId, TraitEnsurance<'tcx>>,
276    /// Cached contracts for each callee function so repeated callees are parsed once.
277    fn_contract_cache: HashMap<DefId, FnContracts<'tcx>>,
278}
279
280impl<'tcx> VerifyTargetCollector<'tcx> {
281    /// Collect all verification targets across the current crate and optionally
282    /// external crates (when `crate_filter` is set).
283    pub fn collect_all(
284        tcx: TyCtxt<'tcx>,
285        mode: VerifyMode,
286        skip_invariant: bool,
287        crate_filter: Option<String>,
288        module_filter: Option<String>,
289    ) -> Self {
290        let mut collector = Self::new(tcx, mode, skip_invariant, crate_filter.clone(), module_filter);
291        tcx.hir_visit_all_item_likes_in_crate(&mut collector);
292        if crate_filter.is_some() {
293            collector.collect_extern_crate_targets();
294        }
295        collector.check_module_filter_result();
296        collector
297    }
298
299    /// Creates a new collector for the current type context.
300    pub fn new(
301        tcx: TyCtxt<'tcx>,
302        mode: VerifyMode,
303        skip_invariant: bool,
304        crate_filter: Option<String>,
305        module_filter: Option<String>,
306    ) -> Self {
307        VerifyTargetCollector {
308            tcx,
309            mode,
310            skip_invariant,
311            crate_filter,
312            crate_filter_matched: false,
313            module_filter,
314            module_filter_matched: false,
315            function_targets: Vec::new(),
316            struct_targets: HashMap::new(),
317            trait_targets: HashMap::new(),
318            fn_contract_cache: HashMap::new(),
319        }
320    }
321
322    /// Returns (and caches) the contracts for an unsafe callee.
323    ///
324    /// Contracts are resolved with the following priority:
325    /// 1. Inline RAPx annotations attached to the callee.
326    /// 2. If the callee is a trait method impl without its own annotations,
327    ///    fall back to the trait method's `#[rapx::requires(...)]`.
328    /// 3. If no annotations are found and the callee belongs to the standard
329    ///    library, fall back to the bundled JSON contract database.
330    ///
331    /// Results are memoized in `fn_contract_cache` to avoid recomputation.
332    fn get_fn_contracts(&mut self, callee_def_id: DefId) -> FnContracts<'tcx> {
333        let is_std = is_std_crate_def_id(self.tcx, callee_def_id);
334
335        let trait_requires = get_trait_method_requires(self.tcx, callee_def_id);
336
337        self.fn_contract_cache
338            .entry(callee_def_id)
339            .or_insert_with(|| {
340                let mut requires = get_contract_from_annotation(self.tcx, callee_def_id);
341
342                if requires.is_empty() && !trait_requires.is_empty() {
343                    requires = trait_requires.clone();
344                }
345
346                if requires.is_empty() && is_std {
347                    requires = super::contract::query_json_contracts(
348                        self.tcx,
349                        callee_def_id,
350                    );
351
352                if requires.is_empty() {
353                    // Recursively resolve contracts from the callee's call chain.
354                    // e.g. A -> B -> C -> D where B,C are unsafe unannotated,
355                    // D has contracts; follow the chain to D and use its contracts.
356                    let mut visited = HashSet::new();
357                    requires = resolve_chain_contracts(
358                        self.tcx,
359                        callee_def_id,
360                        &mut visited,
361                    );
362                    if requires.is_empty() {
363                        let path = crate::helpers::name::get_cleaned_def_path_name(
364                            self.tcx,
365                            callee_def_id,
366                        );
367                        rap_warn!(
368                            "no safety contracts found for callee \"{path}\""
369                        );
370                    } else {
371                        let path = crate::helpers::name::get_cleaned_def_path_name(
372                            self.tcx,
373                            callee_def_id,
374                        );
375                        rap_debug!(
376                            "resolved {} safety contract(s) for std callee \"{path}\" via call chain",
377                            requires.len()
378                        );
379                    }
380                }
381                }
382
383                if requires.is_empty() {
384                    requires.push(Property::new(
385                        self.tcx,
386                        callee_def_id,
387                        "Unknown",
388                        &[],
389                    ));
390                }
391
392                requires
393            })
394            .clone()
395    }
396
397    /// Builds a function target to verify from a function definition.
398    fn build_function_target(&mut self, def_id: DefId) -> FunctionTarget<'tcx> {
399        let checkpoints = collect_unsafe_callsites(self.tcx, def_id);
400        let unsafe_callees: HashSet<_> = checkpoints
401            .iter()
402            .filter_map(|checkpoint| checkpoint.callee)
403            .collect();
404        let callee_requires = unsafe_callees
405            .iter()
406            .map(|callee_def_id| {
407                let mut contracts = self.get_fn_contracts(*callee_def_id);
408                contracts
409                    .retain(|p| {
410                        !matches!(
411                            p.kind(),
412                            Some(crate::verify::contract::PropertyKind::Unknown)
413                        )
414                    });
415                (*callee_def_id, contracts)
416            })
417            .collect();
418
419        let mut caller_requires = self.get_fn_contracts(def_id);
420        // Supplement inline #[rapx::requires] with JSON contracts from the
421        // standard-library database so that `caller_requires` (used as the
422        // entry-point assumptions when verifying the function body) reflects
423        // the full documented safety contract.  Callee-side resolution is
424        // unchanged.
425        if is_std_crate_def_id(self.tcx, def_id) {
426            let json_contracts =
427                super::contract::query_json_contracts(self.tcx, def_id);
428            caller_requires.extend(json_contracts);
429        }
430
431        let raw_ptr_deref_checks = build_raw_ptr_deref_checks(self.tcx, def_id);
432        let static_mut_checks = build_static_mut_checks(self.tcx, def_id);
433
434        let owner_struct_def_id = get_owner_struct_def_id(self.tcx, def_id);
435        let mut struct_invariants = owner_struct_def_id
436            .map(|struct_def_id| {
437                get_struct_invariants_from_annotation(self.tcx, struct_def_id, def_id)
438            })
439            .unwrap_or_default();
440
441        // Struct invariants are implicit preconditions for every method:
442        // safe methods need them as automatic entry facts (no requires
443        // needed), and unsafe constructors verify they hold at return.
444        caller_requires.extend(struct_invariants.clone());
445
446        // drop is a destructor — the struct is being torn down, so we skip
447        // struct invariant checks at exit points.
448        if is_drop_impl(self.tcx, def_id) {
449            struct_invariants.clear();
450        }
451
452        // Standard-library type invariants: for each function parameter (and
453        // the return type for constructors), look up the type's invariants
454        // from std-type-invariants.json and add them as preconditions.
455        let type_invariants = build_type_invariants_from_params(self.tcx, def_id);
456        caller_requires.extend(type_invariants);
457
458        FunctionTarget {
459            def_id,
460            owner_struct_def_id,
461            checkpoints,
462            callee_requires,
463            caller_requires,
464            struct_invariants,
465            raw_ptr_deref_checks,
466            static_mut_checks,
467        }
468    }
469
470    /// Adds a function target and updates its owning struct target when applicable.
471    fn push_function_target(&mut self, function_target: FunctionTarget<'tcx>) {
472        self.function_targets.push(function_target.clone());
473
474        if let Some(struct_def_id) = function_target.owner_struct_def_id {
475            self.struct_targets
476                .entry(struct_def_id)
477                .or_insert_with(|| StructTarget {
478                    def_id: struct_def_id,
479                    invariants: get_struct_invariants_from_annotation(
480                        self.tcx,
481                        struct_def_id,
482                        function_target.def_id,
483                    ),
484                    function_targets: Vec::new(),
485                })
486                .function_targets
487                .push(function_target);
488        }
489    }
490
491    /// Process MIR keys from non-local crates that match the `--crate` filter.
492    ///
493    /// `hir_visit_all_item_likes_in_crate` only visits the *local* crate, but
494    /// in a workspace the target crate (e.g. `core`) may be compiled as a
495    /// dependency of another crate (e.g. `std`).  This method iterates all
496    /// crates' MIR bodies and collects targets from those matching the filter.
497    fn collect_extern_crate_targets(&mut self) {
498        let local_crate = rustc_hir::def_id::LOCAL_CRATE;
499
500        for def_id in self.tcx.mir_keys(()) {
501            let def_id = def_id.to_def_id();
502            if def_id.krate == local_crate {
503                continue; // already visited via the HIR visitor
504            }
505            if !self.crate_name_matches(def_id) {
506                continue;
507            }
508            let def_kind = self.tcx.def_kind(def_id);
509            if !matches!(
510                def_kind,
511                rustc_hir::def::DefKind::Fn | rustc_hir::def::DefKind::AssocFn
512            ) {
513                continue;
514            }
515
516            // Skip `targeted` mode filtering — non-local crates don't have
517            // HIR attributes available (only metadata is present).
518            if matches!(self.mode, VerifyMode::Targeted) {
519                continue;
520            }
521
522            self.crate_filter_matched = true;
523
524            if !self.module_path_matches(def_id) {
525                continue;
526            }
527            self.module_filter_matched = true;
528
529            let function_target = self.build_function_target(def_id);
530            self.push_function_target(function_target);
531        }
532    }
533
534    fn crate_name_matches(&self, def_id: DefId) -> bool {
535        match self.crate_filter {
536            None => true,
537            Some(ref filter) => {
538                let crate_name = self.tcx.crate_name(def_id.krate);
539                if crate_name.as_str() == *filter {
540                    return true;
541                }
542                if let Ok(pkg_name) = std::env::var("CARGO_PKG_NAME") {
543                    if pkg_name == *filter {
544                        return true;
545                    }
546                }
547                false
548            }
549        }
550    }
551
552    fn module_path_matches(&self, def_id: DefId) -> bool {
553        let Some(ref filter) = self.module_filter else {
554            return true;
555        };
556        let def_path = self.tcx.def_path_str(def_id);
557
558        if def_path == *filter || def_path.starts_with(&format!("{}::", filter)) {
559            return true;
560        }
561        let crate_name = self.tcx.crate_name(def_id.krate);
562        let crate_prefix = format!("{}::", crate_name.as_str());
563
564        // Try matching filter after stripping the crate prefix.
565        // e.g. filter "slice" matches def_path "core::slice::raw::from_raw_parts"
566        // after stripping "core::".
567        if let Some(inner) = filter.strip_prefix(&crate_prefix) {
568            if def_path == inner || def_path.starts_with(&format!("{}::", inner)) {
569                return true;
570            }
571        }
572
573        // Try matching def_path after stripping the crate prefix.
574        // e.g. filter "core::slice" matches def_path "slice::raw::from_raw_parts"
575        // after stripping "core::" from the filter.
576        if let Some(inner) = def_path.strip_prefix(&crate_prefix) {
577            if inner == *filter || inner.starts_with(&format!("{}::", filter)) {
578                return true;
579            }
580        }
581
582        false
583    }
584
585    pub fn check_module_filter_result(&self) {
586        if let Some(ref filter) = self.crate_filter {
587            if !self.crate_filter_matched {
588                rap_warn!("[rapx::verify] --crate \"{filter}\" matched no targets");
589            }
590        }
591        if let Some(ref filter) = self.module_filter {
592            if !self.module_filter_matched {
593                rap_warn!("[rapx::verify] --module \"{filter}\" matched no functions in the crate");
594            }
595        }
596    }
597}
598
599fn get_trait_method_requires<'tcx>(tcx: TyCtxt<'tcx>, callee_def_id: DefId) -> FnContracts<'tcx> {
600    let Some(assoc_item) = tcx.opt_associated_item(callee_def_id) else {
601        return Vec::new();
602    };
603    let Some(trait_item_def_id) = assoc_item.trait_item_def_id() else {
604        return Vec::new();
605    };
606    get_contract_from_annotation(tcx, trait_item_def_id)
607}
608
609impl<'tcx> Visitor<'tcx> for VerifyTargetCollector<'tcx> {
610    type NestedFilter = nested_filter::OnlyBodies;
611
612    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
613        self.tcx
614    }
615
616    /// Detect `impl unsafe Trait for Type` blocks and record them as
617    /// [`TraitEnsurance`] placeholders.
618    ///
619    /// In `targeted` mode, only `impl` blocks annotated with `#[rapx::verify]`
620    /// are recorded.  In `scan` mode, all `unsafe trait` impls
621    /// are recorded.
622    fn visit_item(&mut self, item: &'tcx rustc_hir::Item<'tcx>) {
623        if let ItemKind::Impl(rustc_hir::Impl { of_trait, .. }) = &item.kind
624            && of_trait.is_some()
625        {
626            if matches!(self.mode, VerifyMode::Targeted)
627                && !has_rapx_verify_attr(self.tcx, item.owner_id.def_id)
628            {
629                rustc_hir::intravisit::walk_item(self, item);
630                return;
631            }
632
633            let impl_def_id = item.owner_id.to_def_id();
634
635            if !self.crate_name_matches(impl_def_id) {
636                rustc_hir::intravisit::walk_item(self, item);
637                return;
638            }
639            self.crate_filter_matched = true;
640
641            if !self.module_path_matches(impl_def_id) {
642                rustc_hir::intravisit::walk_item(self, item);
643                return;
644            }
645            self.module_filter_matched = true;
646
647            let trait_ref = {
648                self.tcx.impl_opt_trait_ref(impl_def_id)
649            };
650
651            if let Some(trait_ref) = trait_ref {
652                let trait_def_id = trait_ref.skip_binder().def_id;
653                if is_trait_unsafe(self.tcx, trait_def_id) {
654                    let ensures = get_trait_contracts_from_annotation(self.tcx, trait_def_id);
655
656                    let self_ty_def_id = resolve_impl_self_ty_def_id(&item);
657
658                    self.trait_targets
659                        .entry(trait_def_id)
660                        .or_insert_with(|| TraitEnsurance {
661                            def_id: trait_def_id,
662                            self_ty_def_id,
663                            ensures,
664                        });
665                }
666            }
667        }
668
669        rustc_hir::intravisit::walk_item(self, item);
670    }
671
672    /// Visits each function body and records verification targets.
673    ///
674    /// In `targeted` mode, only functions annotated with `#[rapx::verify]` are collected.
675    /// In `scan` mode, a HIR-level pre-filter (`contains_unsafe`
676    /// and `function_has_struct_invariant`) avoids expensive MIR scanning for functions
677    /// that have no unsafe content and no struct invariants.
678    fn visit_fn(
679        &mut self,
680        _fk: FnKind<'tcx>,
681        _fd: &'tcx FnDecl<'tcx>,
682        body_id: BodyId,
683        _span: Span,
684        id: LocalDefId,
685    ) -> Self::Result {
686        if matches!(self.mode, VerifyMode::Targeted) && !has_rapx_verify_attr(self.tcx, id) {
687            // Drop impls on structs with invariants are implicitly verified.
688            if !is_drop_impl(self.tcx, id.to_def_id()) {
689                return;
690            }
691        }
692
693        // HIR pre-filter: skip functions that have nothing to verify.
694        // `contains_unsafe` catches functions with unsafe blocks/declarations;
695        // `function_has_struct_invariant` catches methods on structs with invariants;
696        // `function_has_trait_ensurance` catches methods on unsafe trait impls with contracts.
697        let def_id = id.to_def_id();
698
699        // Skip never-returning (divergent) functions — they have no return
700        // paths and can trigger stack overflows in downstream analysis.
701        if let rustc_hir::def::DefKind::Fn = self.tcx.def_kind(def_id) {
702            let fn_sig = self.tcx.fn_sig(def_id).skip_binder();
703            if matches!(
704                fn_sig.output().skip_binder().kind(),
705                rustc_type_ir::TyKind::Never
706            ) {
707                return;
708            }
709        }
710
711        if !matches!(self.mode, VerifyMode::Targeted) {
712            if !hir_contains_unsafe(self.tcx, body_id)
713                && !function_has_struct_invariant(self.tcx, def_id)
714                && !function_has_trait_ensurance(self.tcx, def_id)
715            {
716                return;
717            }
718        }
719
720        let function_target = self.build_function_target(def_id);
721
722        match self.mode {
723            VerifyMode::Targeted => {}
724            VerifyMode::Scan => {
725                if function_target.checkpoints.is_empty()
726                    && function_target.raw_ptr_deref_checks.is_empty()
727                    && function_target.static_mut_checks.is_empty()
728                {
729                    if !function_target.struct_invariants.is_empty() {
730                        if self.skip_invariant {
731                            return;
732                        }
733                    } else {
734                        let root =
735                            crate::analysis::safety_flow::root::scan_mir(self.tcx, def_id);
736                        if root.is_none() {
737                            return;
738                        }
739                    }
740                }
741            }
742        }
743
744        if !self.crate_name_matches(def_id) {
745            return;
746        }
747        self.crate_filter_matched = true;
748
749        if !self.module_path_matches(def_id) {
750            return;
751        }
752        self.module_filter_matched = true;
753
754        self.push_function_target(function_target);
755    }
756}
757
758/// Analysis pass that finds all verification targets.
759///
760/// In `targeted` mode, only functions annotated with `#[rapx::verify]` are listed.
761/// In `scan` mode, all functions with unsafe callees or struct invariants are listed.
762pub struct PrepareTargets<'tcx> {
763    tcx: TyCtxt<'tcx>,
764    mode: VerifyMode,
765    skip_invariant: bool,
766    crate_filter: Option<String>,
767    module_filter: Option<String>,
768}
769
770impl<'tcx> Analysis for PrepareTargets<'tcx> {
771    fn run(&mut self) {
772        let collector = VerifyTargetCollector::collect_all(
773            self.tcx,
774            self.mode,
775            self.skip_invariant,
776            self.crate_filter.clone(),
777            self.module_filter.clone(),
778        );
779
780        // Free functions (no owning struct)
781        let free_targets: Vec<_> = collector
782            .function_targets
783            .iter()
784            .filter(|target| target.owner_struct_def_id.is_none())
785            .collect();
786        for target in &free_targets {
787            let target_path = self.tcx.def_path_str(target.def_id);
788            rap_info!("============================================================");
789            rap_info!(
790                "[rapx::verify] prepare targets for free function: {}",
791                target_path
792            );
793            rap_info!("============================================================");
794            self.log_free_function_unsafe_callees(target);
795            rap_info!("");
796        }
797
798        // Structs with methods
799        let mut struct_ids: Vec<_> = collector.struct_targets.keys().copied().collect();
800        struct_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
801
802        for struct_def_id in struct_ids {
803            let Some(struct_target) = collector.struct_targets.get(&struct_def_id) else {
804                continue;
805            };
806            let struct_path = self.tcx.def_path_str(struct_target.def_id);
807
808            rap_info!("============================================================");
809            rap_info!("[rapx::verify] prepare targets for struct: {}", struct_path);
810            rap_info!("============================================================");
811
812            self.log_struct_invariants(struct_target);
813
814            for target in &struct_target.function_targets {
815                self.log_method_target(target);
816            }
817        }
818
819        // Traits with impl methods
820        let mut trait_ids: Vec<_> = collector.trait_targets.keys().copied().collect();
821        trait_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
822
823        for trait_def_id in trait_ids {
824            let Some(trait_target) = collector.trait_targets.get(&trait_def_id) else {
825                continue;
826            };
827            let trait_path = self.tcx.def_path_str(trait_target.def_id);
828
829            rap_info!("============================================================");
830            rap_info!(
831                "[rapx::verify] prepare targets for unsafe trait: {}",
832                trait_path
833            );
834            rap_info!("============================================================");
835
836            self.log_trait_ensurance(trait_target);
837
838            rap_info!("");
839        }
840
841        let total_free = free_targets.len();
842        let total_method = collector
843            .function_targets
844            .iter()
845            .filter(|target| target.owner_struct_def_id.is_some())
846            .count();
847        let total_struct = collector.struct_targets.len();
848        let total_trait = collector.trait_targets.len();
849
850        rap_info!("============================================================");
851        rap_info!(
852            "[rapx::verify] total: {} free function(s), {} method(s), {} struct(s), {} trait(s)",
853            total_free,
854            total_method,
855            total_struct,
856            total_trait
857        );
858        rap_info!("============================================================");
859    }
860
861}
862
863impl<'tcx> PrepareTargets<'tcx> {
864    pub fn new(
865        tcx: TyCtxt<'tcx>,
866        mode: VerifyMode,
867        skip_invariant: bool,
868        crate_filter: Option<String>,
869        module_filter: Option<String>,
870    ) -> Self {
871        PrepareTargets {
872            tcx,
873            mode,
874            skip_invariant,
875            crate_filter,
876            module_filter,
877        }
878    }
879
880    fn log_struct_invariants(&self, struct_target: &StructTarget<'tcx>) {
881        if struct_target.invariants.is_empty() {
882            rap_info!("  struct invariants: <none>");
883        } else {
884            rap_info!("  struct invariants:");
885            for property in crate::verify::display::dedup_compound_props(struct_target.invariants.iter()) {
886                rap_info!(
887                    "    - {}",
888                    property.display_for_report(self.tcx, Some(struct_target.def_id), None,)
889                );
890            }
891        }
892    }
893
894    fn log_trait_ensurance(&self, trait_target: &TraitEnsurance<'tcx>) {
895        if let Some(self_ty) = trait_target.self_ty_def_id {
896            rap_info!("  impl for: {}", self.tcx.def_path_str(self_ty));
897        }
898        if trait_target.ensures.is_empty() {
899            rap_info!("  ensures: <none>");
900        } else {
901            rap_info!("  ensures (implementor must satisfy):");
902            for (method_name, contracts) in &trait_target.ensures {
903                rap_info!("    fn {}:", method_name);
904                for property in crate::verify::display::dedup_compound_props(contracts.iter()) {
905                    rap_info!(
906                        "      - {}",
907                        property.display_for_report(self.tcx, trait_target.self_ty_def_id, None,)
908                    );
909                }
910            }
911        }
912    }
913
914    fn log_method_target(&self, target: &FunctionTarget<'tcx>) {
915        let name = short_fn_name(self.tcx, target.def_id);
916        let dashes = 62usize.saturating_sub(10 + name.len());
917        rap_info!("  --- method: {name} {}", "-".repeat(dashes));
918
919        let return_blocks = collect_return_block_indices(self.tcx, target.def_id);
920        rap_info!(
921            "      return checkpoints: {} block(s) {:?}",
922            return_blocks.len(),
923            return_blocks
924                .iter()
925                .map(|bb| bb.as_usize())
926                .collect::<Vec<_>>()
927        );
928
929        let path_map = self.build_checkpoint_path_map(target);
930        self.log_unsafe_callees_and_contracts(target, &path_map);
931    }
932
933    fn log_free_function_unsafe_callees(&self, target: &FunctionTarget<'tcx>) {
934        let path_map = self.build_checkpoint_path_map(target);
935        self.log_unsafe_callees_and_contracts(target, &path_map);
936    }
937
938    fn log_unsafe_callees_and_contracts(
939        &self,
940        target: &FunctionTarget<'tcx>,
941        path_map: &FxHashMap<DefId, Vec<(usize, Vec<String>)>>,
942    ) {
943        if target.callee_requires.is_empty() {
944            rap_info!("      unsafe checkpoints: <none>");
945            return;
946        }
947
948            let mut unsafe_callee_ids: Vec<_> = target.callee_requires.keys().copied().collect();
949        unsafe_callee_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
950
951        for unsafe_callee_def_id in unsafe_callee_ids {
952            let fn_sig = self.tcx.fn_sig(unsafe_callee_def_id).skip_binder();
953            let unsafe_callee_path = self.tcx.def_path_str(unsafe_callee_def_id);
954            let inputs: Vec<String> = fn_sig
955                .inputs()
956                .skip_binder()
957                .iter()
958                .map(|ty| format!("{}", ty))
959                .collect();
960            let output = format!("{}", fn_sig.output().skip_binder());
961            rap_info!(
962                "      unsafe callee: {}({}) -> {}",
963                unsafe_callee_path,
964                inputs.join(", "),
965                output,
966            );
967
968            if let Some(requires) = target.callee_requires.get(&unsafe_callee_def_id) {
969                if requires.is_empty() {
970                    rap_info!("        safety contracts: <none>");
971                } else {
972                    rap_info!("        safety contracts:");
973                    for property in crate::verify::display::dedup_compound_props(requires.iter()) {
974                        rap_info!(
975                            "          - {}",
976                            property.display_for_report(
977                                self.tcx,
978                                target.owner_struct_def_id,
979                                Some(unsafe_callee_def_id),
980                            )
981                        );
982                    }
983                }
984            }
985
986            if let Some(path_entries) = path_map.get(&unsafe_callee_def_id) {
987                for (_block_idx, path_strings) in path_entries {
988                    if path_strings.is_empty() {
989                        rap_info!("        path: <none>");
990                    } else {
991                        for desc in path_strings {
992                            rap_info!("        path: shortest path: {desc}");
993                        }
994                    }
995                }
996            }
997        }
998    }
999
1000    fn build_checkpoint_path_map(
1001        &self,
1002        target: &FunctionTarget<'tcx>,
1003    ) -> FxHashMap<DefId, Vec<(usize, Vec<String>)>> {
1004        let mut path_map: FxHashMap<DefId, Vec<(usize, Vec<String>)>> = FxHashMap::default();
1005
1006        if target.checkpoints.is_empty() {
1007            return path_map;
1008        }
1009
1010        let groups =
1011            PathExtractor::new(self.tcx, target.def_id, target.checkpoints.clone(), 0).run();
1012
1013        for group in &groups {
1014            for checkpoint in &group.checkpoints {
1015                if let Some(callee_def_id) = checkpoint.callee {
1016                    let block_idx = checkpoint.block.as_usize();
1017                    let mut path_strings: Vec<String> = Vec::new();
1018                    let _ = group.tree.walk_prefixes(
1019                        checkpoint.block.as_usize(),
1020                        &mut |prefix: &[usize]| -> bool {
1021                            let desc = prefix
1022                                .iter()
1023                                .map(usize::to_string)
1024                                .collect::<Vec<_>>()
1025                                .join(" -> ");
1026                            path_strings.push(desc);
1027                            true
1028                        },
1029                    );
1030
1031                    path_map
1032                        .entry(callee_def_id)
1033                        .or_insert_with(Vec::new)
1034                        .push((block_idx, path_strings));
1035                }
1036            }
1037        }
1038
1039        path_map
1040    }
1041}
1042
1043fn is_rapx_named_attr(attr: &Attribute, name: &str) -> bool {
1044    let path = attr.path();
1045    if path.len() >= 2
1046        && path[path.len() - 2].as_str() == "rapx"
1047        && path[path.len() - 1].as_str() == name
1048    {
1049        return true;
1050    }
1051    // In newer rustc, tool attrs may have the tool prefix stripped from the path.
1052    // Match bare name when the attribute has exactly one path segment.
1053    path.len() == 1 && path[0].as_str() == name
1054}
1055
1056fn collect_properties_from_named_attrs<'tcx>(
1057    tcx: TyCtxt<'tcx>,
1058    attrs: impl IntoIterator<Item = &'tcx Attribute>,
1059    property_def_id: DefId,
1060    parse_error_label: &str,
1061    attr_name: &str,
1062) -> Vec<Property<'tcx>> {
1063    let mut results = Vec::new();
1064
1065    for attr in attrs {
1066        if !is_rapx_named_attr(attr, attr_name) {
1067            continue;
1068        }
1069
1070        let attr_str = crate::compat::attribute_to_string(tcx, attr);
1071        let parsed = match parse_rapx_attr(attr_str.as_str(), attr_name) {
1072            Ok(parsed) => parsed,
1073            Err(err) => {
1074                rap_error!(
1075                    "Failed to parse RAPx {} attr '{}': {}",
1076                    parse_error_label,
1077                    attr_str,
1078                    err
1079                );
1080                continue;
1081            }
1082        };
1083
1084        let Some(property) = parsed else { continue };
1085        results.extend(
1086            Property::parse_list(tcx, property_def_id, property.tag.as_str(), &property.args)
1087                .into_iter()
1088                .map(move |mut p| {
1089                    p.apply_kind(property.kind.as_deref());
1090                    p
1091                }),
1092        );
1093    }
1094
1095    results
1096}
1097
1098/// Parses `requires` contracts from source-level RAPx annotations attached to a definition.
1099pub(crate) fn get_contract_from_annotation<'tcx>(
1100    tcx: TyCtxt<'tcx>,
1101    def_id: DefId,
1102) -> FnContracts<'tcx> {
1103    // Prefer HIR-level attrs for local defs (tool attributes visible),
1104    // fall back to get_all_attrs for external defs.
1105    if let Some(local_def_id) = def_id.as_local() {
1106        let hir_id = tcx.local_def_id_to_hir_id(local_def_id);
1107        let hir_attrs = tcx.hir_attrs(hir_id);
1108        // hir_attrs is &'tcx [Attribute<'tcx>]; iter yields &'tcx Attribute<'tcx>
1109        return collect_properties_from_named_attrs(tcx, hir_attrs, def_id, "requires", "requires");
1110    }
1111
1112    let attrs = crate::compat::get_all_attrs(tcx, def_id);
1113    collect_properties_from_named_attrs(tcx, attrs, def_id, "requires", "requires")
1114}
1115
1116/// Parses struct invariants from source-level RAPx annotations attached to a struct definition.
1117fn get_struct_invariants_from_annotation<'tcx>(
1118    tcx: TyCtxt<'tcx>,
1119    struct_def_id: DefId,
1120    context_def_id: DefId,
1121) -> StructInvariants<'tcx> {
1122    let Some(local_def_id) = struct_def_id.as_local() else {
1123        return Vec::new();
1124    };
1125
1126    let item = tcx.hir_expect_item(local_def_id);
1127    if !matches!(item.kind, ItemKind::Struct(..)) {
1128        return Vec::new();
1129    }
1130
1131    let mut invariants = collect_properties_from_named_attrs(
1132        tcx,
1133        {
1134            crate::compat::get_all_attrs(tcx, struct_def_id)
1135        },
1136        context_def_id,
1137        "invariant",
1138        "requires",
1139    );
1140    invariants.extend(collect_properties_from_named_attrs(
1141        tcx,
1142        {
1143            crate::compat::get_all_attrs(tcx, struct_def_id)
1144        },
1145        context_def_id,
1146        "invariant",
1147        "invariant",
1148    ));
1149    invariants
1150}
1151
1152/// Parses trait safety contracts from `#[rapx::ensures(...)]` on unsafe trait
1153/// methods, grouped by method name.
1154fn get_trait_contracts_from_annotation<'tcx>(
1155    tcx: TyCtxt<'tcx>,
1156    trait_def_id: DefId,
1157) -> Vec<(String, FnContracts<'tcx>)> {
1158    let Some(local_id) = trait_def_id.as_local() else {
1159        return Vec::new();
1160    };
1161
1162    let item = tcx.hir_expect_item(local_id);
1163
1164    let trait_items = {
1165        #[cfg(not(rapx_ge_99))]
1166        if let ItemKind::Trait(.., items) = &item.kind {
1167            items
1168        } else {
1169            return Vec::new();
1170        }
1171        #[cfg(rapx_ge_99)]
1172        if let ItemKind::Trait { items, .. } = &item.kind {
1173            items
1174        } else {
1175            return Vec::new();
1176        }
1177    };
1178
1179    let mut ensures: Vec<(String, FnContracts<'tcx>)> = Vec::new();
1180
1181    for trait_item_id in trait_items.iter() {
1182        let trait_item_def_id = trait_item_id.owner_id.to_def_id();
1183        let method_name = tcx.def_path_str(trait_item_def_id);
1184        let attrs = crate::compat::get_all_attrs(tcx, trait_item_def_id);
1185
1186        let method_ensures =
1187            collect_properties_from_named_attrs(tcx, attrs, trait_item_def_id, "trait ensures", "ensures");
1188
1189        if !method_ensures.is_empty() {
1190            ensures.push((method_name, method_ensures));
1191        }
1192    }
1193
1194    ensures
1195}
1196
1197/// Build (pseudo-checkpoint, properties) pairs for every raw pointer dereference
1198/// in the target function.
1199fn build_raw_ptr_deref_checks<'tcx>(
1200    tcx: TyCtxt<'tcx>,
1201    def_id: DefId,
1202) -> Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)> {
1203    let infos = collect_raw_ptr_deref_info(tcx, def_id);
1204    if infos.is_empty() {
1205        return Vec::new();
1206    }
1207
1208    infos
1209        .into_iter()
1210        .map(|info| {
1211            let target = PropertyArg::Expr(ContractExpr::Place(ContractPlace {
1212                base: PlaceBase::Arg(0),
1213                projections: vec![],
1214            }));
1215            let ty = PropertyArg::Ty(info.pointee_ty);
1216            let count = PropertyArg::Expr(ContractExpr::Const(1));
1217
1218            let mut properties = if info.is_ref {
1219                vec![
1220                    Property::new_leaf(PropertyKind::NonNull, vec![target.clone()]),
1221                    Property::new_leaf(PropertyKind::Align, vec![target.clone(), ty.clone()]),
1222                    { let mut p = Property::new_leaf(PropertyKind::Alias, vec![target.clone()]); p.set_contract_kind(crate::verify::contract::ContractKind::Hazard); p },
1223                ]
1224            } else {
1225                vec![
1226                    Property::new_leaf(PropertyKind::Allocated, vec![target.clone(), ty.clone(), count.clone()]),
1227                    Property::new_leaf(PropertyKind::InBound, vec![target.clone(), ty.clone(), count.clone()]),
1228                    Property::new_leaf(PropertyKind::Align, vec![target.clone(), ty.clone()]),
1229                ]
1230            };
1231
1232            if info.is_read && !info.is_ref {
1233                properties.push(Property::new_leaf(PropertyKind::Typed, vec![target, ty]));
1234            }
1235
1236            (
1237                Checkpoint {
1238                    caller: def_id,
1239                    callee: None,
1240                    block: info.block,
1241                    span: rustc_span::DUMMY_SP,
1242                    args: vec![info.ptr_operand],
1243                    kind: crate::helpers::mir_scan::CheckpointKind::RawPtrDeref,
1244                    is_ref: info.is_ref,
1245                    is_mut_ref: info.is_mut_ref,
1246                    destination: Some(info.destination),
1247                },
1248                properties,
1249            )
1250        })
1251        .collect()
1252}
1253
1254/// Build (pseudo-checkpoint, properties) pairs for every static mut access
1255/// in the target function.
1256fn build_static_mut_checks<'tcx>(
1257    tcx: TyCtxt<'tcx>,
1258    def_id: DefId,
1259) -> Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)> {
1260    let infos = collect_static_mut_access_info(tcx, def_id);
1261    if infos.is_empty() {
1262        return Vec::new();
1263    }
1264
1265    infos
1266        .into_iter()
1267        .map(|info| {
1268            let target = PropertyArg::Expr(ContractExpr::Place(ContractPlace {
1269                base: PlaceBase::Arg(0),
1270                projections: vec![],
1271            }));
1272            let ty = PropertyArg::Ty(info.ty);
1273            let count = PropertyArg::Expr(ContractExpr::Const(1));
1274
1275            let properties = vec![
1276                Property::new_leaf(PropertyKind::Allocated, vec![target.clone(), ty.clone(), count.clone()]),
1277                Property::new_leaf(PropertyKind::InBound, vec![target.clone(), ty.clone(), count.clone()]),
1278                Property::new_leaf(PropertyKind::Align, vec![target.clone(), ty.clone()]),
1279                Property::new_leaf(PropertyKind::Init, vec![target, ty, count]),
1280            ];
1281
1282            (
1283                Checkpoint {
1284                    caller: def_id,
1285                    callee: None,
1286                    block: info.block,
1287                    span: rustc_span::DUMMY_SP,
1288                    args: vec![info.ptr_operand],
1289                    kind: crate::helpers::mir_scan::CheckpointKind::StaticMutAccess,
1290                    is_ref: false,
1291                    is_mut_ref: false,
1292                    destination: None,
1293                },
1294                properties,
1295            )
1296        })
1297        .collect()
1298}
1299
1300/// For each function parameter (and the return type), look up the type's
1301/// invariants from `std-type-invariants.json` and create preconditions.
1302fn build_type_invariants_from_params<'tcx>(
1303    tcx: TyCtxt<'tcx>,
1304    def_id: DefId,
1305) -> Vec<Property<'tcx>> {
1306    let db = crate::verify::contract::assets::get_std_type_invariants();
1307    if db.is_empty() {
1308        return Vec::new();
1309    }
1310
1311    let fn_sig = tcx.fn_sig(def_id).skip_binder();
1312    let inputs = fn_sig.inputs().skip_binder();
1313    let output = fn_sig.output().skip_binder();
1314
1315    let mut results = Vec::new();
1316
1317    // Use the existing signature parser for parameter names.
1318    let (param_names, _param_tys) = crate::helpers::name::parse_signature(tcx, def_id);
1319
1320    // Add invariants for each parameter
1321    for (index, &param_ty) in inputs.iter().enumerate() {
1322        if param_ty.is_primitive() {
1323            continue;
1324        }
1325        let param_name = param_names.get(index).cloned().unwrap_or_default();
1326        let type_path = type_path_key(tcx, param_ty);
1327        collect_type_invariants(tcx, def_id, &db, &type_path, &param_name, &mut results);
1328    }
1329
1330    // Also add invariants for the return type
1331    if !output.is_unit() && !output.is_primitive() {
1332        let type_path = type_path_key(tcx, output);
1333        collect_type_invariants(tcx, def_id, &db, &type_path, "return", &mut results);
1334    }
1335
1336    results
1337}
1338
1339/// Look up the type path in the DB and add instantiated invariants to `results`.
1340fn collect_type_invariants<'tcx>(
1341    tcx: TyCtxt<'tcx>,
1342    def_id: DefId,
1343    db: &std::collections::HashMap<String, crate::verify::contract::assets::TypeInvariantEntry>,
1344    type_path: &str,
1345    param_name: &str,
1346    results: &mut Vec<Property<'tcx>>,
1347) {
1348    if let Some(entry) = db.get(type_path) {
1349        for prop_entry in &entry.invariants {
1350            if let Some(property) = instantiate_type_invariant(tcx, def_id, prop_entry, param_name)
1351            {
1352                results.push(property);
1353            }
1354        }
1355    }
1356    // Also try with common alloc/std prefixes
1357    for prefix in ["alloc::", "std::"] {
1358        let prefixed = format!("{prefix}{type_path}");
1359        if prefixed != type_path {
1360            if let Some(entry) = db.get(&prefixed) {
1361                for prop_entry in &entry.invariants {
1362                    if let Some(property) =
1363                        instantiate_type_invariant(tcx, def_id, prop_entry, param_name)
1364                    {
1365                        results.push(property);
1366                    }
1367                }
1368            }
1369        }
1370    }
1371}
1372
1373/// Create a property from a type invariant entry, substituting the parameter name.
1374fn instantiate_type_invariant<'tcx>(
1375    tcx: TyCtxt<'tcx>,
1376    def_id: DefId,
1377    entry: &crate::verify::contract::assets::PropertyEntry,
1378    param_name: &str,
1379) -> Option<Property<'tcx>> {
1380    let mut exprs: Vec<syn::Expr> = Vec::new();
1381    for arg_str in &entry.args {
1382        // Substitute the `$self` placeholder with the actual parameter name
1383        // (or "return"), so e.g. "$self.0 != 0" becomes "param.0 != 0".
1384        let substituted = arg_str.replace("$self", param_name);
1385        // Substitute struct field references like "0" → "param_name.0"
1386        let resolved = if is_numeric_field_access(&substituted) {
1387            format!("{}.{}", param_name, substituted)
1388        } else {
1389            substituted
1390        };
1391        match syn::parse_str::<syn::Expr>(&resolved) {
1392            Ok(expr) => exprs.push(expr),
1393            Err(_) => {
1394                rap_debug!(
1395                    "  [type-invariant] failed to parse arg '{}' for tag {}",
1396                    resolved,
1397                    entry.tag
1398                );
1399                return None;
1400            }
1401        }
1402    }
1403    if exprs.is_empty() {
1404        return None;
1405    }
1406    let mut property = Property::new(tcx, def_id, &entry.tag, &exprs);
1407    property.apply_kind(entry.kind.as_deref());
1408    if !matches!(
1409        property.kind(),
1410        Some(crate::verify::contract::PropertyKind::Unknown)
1411    ) {
1412        Some(property)
1413    } else {
1414        None
1415    }
1416}
1417
1418/// Check if a string looks like a numeric field access (e.g. "0").
1419fn is_numeric_field_access(s: &str) -> bool {
1420    let trimmed = s.trim();
1421    !trimmed.is_empty()
1422        && trimmed
1423            .split('.')
1424            .all(|part| !part.is_empty() && part.chars().all(|c| c.is_ascii_digit()))
1425}
1426
1427/// Generate a normalised type path key for lookups in the type-invariants DB.
1428fn type_path_key<'tcx>(tcx: TyCtxt<'tcx>, ty: rustc_middle::ty::Ty<'tcx>) -> String {
1429    match ty.kind() {
1430        rustc_middle::ty::TyKind::Adt(adt_def, _) => {
1431            let path = tcx.def_path_str(adt_def.did());
1432            path
1433        }
1434        _ => format!("{ty:?}"),
1435    }
1436}
1437
1438fn is_drop_impl(tcx: TyCtxt<'_>, fn_did: DefId) -> bool {
1439    let Some(impl_id) = tcx.trait_impl_of_assoc(fn_did) else {
1440        return false;
1441    };
1442    let trait_did = tcx.impl_trait_id(impl_id);
1443    tcx.is_lang_item(trait_did, LangItem::Drop)
1444}