Skip to main content

rapx/verify/
target.rs

1//! Discovery of verification targets and their contract obligations.
2//!
3//! `VerifyTargetCollector` walks the crate's HIR (in `targeted` or `scan`
4//! mode), and for each candidate assembles a [`FunctionTarget`]: unsafe
5//! call-site checkpoints with per-callee preconditions, raw-pointer/static-mut
6//! synthetic checkpoints, struct invariants, and std type invariants.
7
8use crate::analysis::Analysis;
9use crate::analysis::safety_flow::root::{
10    function_has_struct_invariant, function_has_trait_ensurance, hir_contains_unsafe,
11};
12use crate::cli::VerifyMode;
13use crate::compat::FxHashMap;
14use crate::helpers::mir_scan::{collect_raw_ptr_deref_info, collect_static_mut_access_info};
15use crate::helpers::name::short_fn_name;
16#[cfg(rapx_has_attr_ir)]
17use rustc_attr_ir::LangItem;
18#[cfg(all(not(rapx_has_attr_ir), not(rapx_ge_100)))]
19use rustc_hir::LangItem;
20#[cfg(all(not(rapx_has_attr_ir), rapx_ge_100))]
21use rustc_hir::attrs::lang_items::LangItem;
22use rustc_hir::{
23    BodyId, FnDecl, ItemKind,
24    def_id::{DefId, LocalDefId},
25    intravisit::{FnKind, Visitor},
26};
27#[cfg(rapx_has_attr_ir)]
28use rustc_attr_ir::Attribute;
29#[cfg(not(rapx_has_attr_ir))]
30use rustc_hir::Attribute;
31use rustc_middle::{hir::nested_filter, ty::TyCtxt};
32use rustc_span::Span;
33use std::collections::{HashMap, HashSet, VecDeque};
34
35use super::{
36    contract::{
37        ContractExpr, ContractPlace, PlaceBase, Property, PropertyArg, PropertyKind,
38        attr::parse_rapx_attr,
39    },
40    path_extractor::PathExtractor,
41    type_invariants::build_type_invariants_from_params,
42};
43use crate::helpers::fn_info::get_adt_def_id_by_adt_method;
44use crate::helpers::mir_scan::{Checkpoint, collect_unsafe_callsites};
45use crate::helpers::mir_utils::{
46    collect_return_block_indices, has_rapx_verify_attr, is_std_crate_def_id, is_trait_unsafe,
47    resolve_impl_self_ty_def_id,
48};
49
50/// A list of parsed `requires` contracts.
51pub(crate) type FnContracts<'tcx> = Vec<Property<'tcx>>;
52
53/// A list of parsed struct invariants.
54pub(crate) type StructInvariants<'tcx> = Vec<Property<'tcx>>;
55
56/// Collected verification data for a single function under analysis.
57///
58/// `FunctionTarget` is the complete **problem statement** for one function: it
59/// records every unsafe operation found in the function's MIR body, the safety
60/// contracts that each operation demands, and any contracts or invariants that
61/// serves as entry assumptions or structural guarantees.
62///
63/// # How it is built
64///
65/// [`VerifyTargetCollector::build_function_target`] assembles a `FunctionTarget`
66/// in one pass over the MIR body:
67///
68/// 1. Unsafe checkpoints are collected via [`collect_unsafe_callsites`].
69/// 2. Each unique callee `DefId` gets its `#[rapx::requires]` contracts parsed
70///    (with fallback to bundled JSON contracts for standard-library callees).
71/// 3. Raw pointer dereferences are detected and converted into synthetic
72///    (pseudo-checkpoint, `[ValidPtr, Align, (Typed)]`) pairs.
73/// 4. The caller's own `#[rapx::requires]` contracts become entry assumptions.
74/// 5. If the function is a method on a struct, struct-level `#[rapx::invariant]`
75///    and `#[rapx::requires]` annotations are collected.
76///
77/// # Role in the pipeline
78///
79/// The [`VerifyDriver`](super::driver::VerifyDriver) consumes a `FunctionTarget`
80/// to route each unsafe operation to the verifier engine along reachability paths
81/// extracted from the MIR CFG.  The target is the primary data carrier between
82/// the *target collection* stage and the *path extraction / verification* stage.
83#[derive(Clone, Debug)]
84pub(crate) struct FunctionTarget<'tcx> {
85    /// The function being verified.
86    pub def_id: DefId,
87
88    /// Owning struct when this function is an associated method (e.g.
89    /// `impl MyStruct { fn foo(...) }`).  `None` for free functions.
90    ///
91    /// Used to associate struct invariants and to group method-level
92    /// verification results under the owning struct in diagnostic output.
93    pub owner_struct_def_id: Option<DefId>,
94
95    /// All call-terminator-based unsafe checkpoints found in this function's MIR.
96    ///
97    /// Each [`Checkpoint`] records the callee `DefId`, the source-span of the
98    /// call, the basic-block location, and the MIR operands passed as arguments.
99    pub checkpoints: Vec<Checkpoint<'tcx>>,
100
101    /// Safety contracts demanded by each unique unsafe callee reachable from
102    /// this function, keyed by callee `DefId`.
103    ///
104    /// Contracts are sourced from `#[rapx::requires(...)]` annotations on the
105    /// callee (inline mode) or from a bundled JSON contract database for
106    /// standard-library functions.  Each value is a `Vec<Property>` — the
107    /// concrete safety requirements the callee expects its caller to satisfy.
108    pub callee_requires: HashMap<DefId, FnContracts<'tcx>>,
109
110    /// Safety contracts that the **caller itself** requires as entry
111    /// assumptions, parsed from `#[rapx::requires(...)]` on this function.
112    ///
113    /// During verification the engine prepends these properties as *facts* that
114    /// are assumed to hold at function entry, constraining the backward
115    /// data-dependency analysis and forward simulation.
116    pub caller_requires: FnContracts<'tcx>,
117
118    /// Struct invariants that methods of the owning struct must maintain.
119    ///
120    /// Collected from `#[rapx::invariant(...)]` / `#[rapx::requires(...)]`
121    /// annotations on the struct definition.  Checked at constructor return
122    /// blocks and at all path endpoints for non-constructor methods.
123    pub struct_invariants: Vec<Property<'tcx>>,
124
125    /// Built-in type invariants (e.g. the synthesized `NonNull`/`Init`/`Alive`
126    /// slice invariant for `&[T]`/`&mut [T]` receivers and returns).
127    ///
128    /// Like [`struct_invariants`](Self::struct_invariants), these are assumed
129    /// at entry (via `caller_requires`) and re-proved at return so a mutation
130    /// that breaks them (e.g. an out-of-bounds write) is caught even without a
131    /// user-written `#[rapx::invariant]`.
132    pub type_invariants: Vec<Property<'tcx>>,
133
134    /// Raw pointer dereference checks with their required safety properties.
135    ///
136    /// Each entry is a `(Checkpoint, Vec<Property>)` pair where the `Checkpoint`
137    /// carries a synthetic dummy `DefId` (so the path extractor can treat
138    /// dereferences uniformly with checkpoints) and the properties encode the
139    /// pointer-validity requirements: always [`Allocated`](PropertyKind::Allocated),
140    /// [`InBound`](PropertyKind::InBound), and [`Align`](PropertyKind::Align);
141    /// additionally [`Typed`](PropertyKind::Typed) when the dereference is a read.
142    pub raw_ptr_deref_checks: Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)>,
143
144    /// Static mut access checks with their required safety properties.
145    ///
146    /// Each entry is a `(Checkpoint, Vec<Property>)` pair following the same
147    /// pattern as [`raw_ptr_deref_checks`](Self::raw_ptr_deref_checks).  The
148    /// properties are [`Allocated`](PropertyKind::Allocated),
149    /// [`InBound`](PropertyKind::InBound), [`Align`](PropertyKind::Align),
150    /// and [`Init`](PropertyKind::Init)
151    /// (conservatively checked for both reads and writes).
152    pub static_mut_checks: Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)>,
153}
154
155impl<'tcx> FunctionTarget<'tcx> {
156    pub(crate) fn all_checkpoints(&self) -> Vec<&Checkpoint<'tcx>> {
157        self.checkpoints
158            .iter()
159            .chain(
160                self.raw_ptr_deref_checks
161                    .iter()
162                    .map(|(checkpoint, _)| checkpoint),
163            )
164            .chain(
165                self.static_mut_checks
166                    .iter()
167                    .map(|(checkpoint, _)| checkpoint),
168            )
169            .collect()
170    }
171
172    pub(crate) fn properties_for_callsite(
173        &self,
174        checkpoint: &Checkpoint<'tcx>,
175    ) -> &[Property<'tcx>] {
176        let loc = checkpoint.location();
177        match checkpoint.kind {
178            crate::helpers::mir_scan::CheckpointKind::RawPtrDeref => self
179                .raw_ptr_deref_checks
180                .iter()
181                .find(|(candidate, _)| candidate.location() == loc)
182                .map(|(_, properties)| properties.as_slice())
183                .unwrap_or(&[]),
184            crate::helpers::mir_scan::CheckpointKind::StaticMutAccess => self
185                .static_mut_checks
186                .iter()
187                .find(|(candidate, _)| candidate.location() == loc)
188                .map(|(_, properties)| properties.as_slice())
189                .unwrap_or(&[]),
190            crate::helpers::mir_scan::CheckpointKind::UnsafeCall => checkpoint
191                .callee
192                .and_then(|callee| self.callee_requires.get(&callee))
193                .map(Vec::as_slice)
194                .unwrap_or(&[]),
195        }
196    }
197}
198
199/// Collected verification data for a struct that owns methods marked with `#[rapx::verify]`.
200pub(crate) struct StructTarget<'tcx> {
201    /// Struct that owns one or more methods selected as targets to verify.
202    pub def_id: DefId,
203    /// Parsed `invariant` contracts attached to the struct.
204    pub invariants: StructInvariants<'tcx>,
205    /// Methods of this struct selected as targets to verify.
206    pub function_targets: Vec<FunctionTarget<'tcx>>,
207}
208
209/// Collected verification data for an `impl unsafe Trait for Type` block.
210///
211/// Covers two cases, distinguished by whether the trait has methods:
212/// - marker traits (`Send`/`Sync`) carry *type-level* obligations, and
213/// - unsafe traits with methods carry *method-level* `ensures` contracts
214///   (verification deferred).
215pub(crate) struct TraitEnsurance<'tcx> {
216    /// The trait being implemented.
217    pub def_id: DefId,
218    /// The `impl ... for Type` block's DefId (carries the impl's generic bounds,
219    /// e.g. `T: Send`).
220    pub impl_def_id: DefId,
221    /// The concrete type that implements the trait (e.g. `SomeStruct`).
222    pub self_ty_def_id: Option<DefId>,
223    /// The obligations to verify, keyed by whether the trait has methods.
224    pub kind: TraitEnsuranceKind<'tcx>,
225}
226
227/// What a [`TraitEnsurance`] must verify.
228pub(crate) enum TraitEnsuranceKind<'tcx> {
229    /// A marker trait (`Send`/`Sync`, no methods): type-level obligations
230    /// generated from the bundled `std-trait-ensures.json` template.
231    Marker(MarkerTraitKind, Vec<Property<'tcx>>),
232    /// An unsafe trait with methods: `ensures` contracts grouped by method name.
233    Unsafe(Vec<(String, FnContracts<'tcx>)>),
234}
235
236/// Which marker trait an `unsafe impl` is claiming safety for.
237#[derive(Clone, Copy, PartialEq, Eq, Debug)]
238pub(crate) enum MarkerTraitKind {
239    Send,
240    Sync,
241}
242
243/// Trace a call-site argument operand back to the enclosing callee's parameter
244/// index (0-based), following direct `Copy`/`Move` assignment chains. Returns
245/// `None` for constants, field projections, call results, or values that do not
246/// trace to a parameter (those references cannot be restated on the enclosing
247/// callee).
248fn call_arg_to_outer_param(
249    op: &rustc_middle::mir::Operand<'_>,
250    body: &rustc_middle::mir::Body<'_>,
251) -> Option<usize> {
252    let local = match op {
253        rustc_middle::mir::Operand::Copy(p) | rustc_middle::mir::Operand::Move(p) => {
254            if p.projection.is_empty() {
255                p.local
256            } else {
257                return None;
258            }
259        }
260        _ => return None,
261    };
262    let mut queue = VecDeque::from([local]);
263    let mut seen = HashSet::from([local]);
264    while let Some(current) = queue.pop_front() {
265        let cidx = current.as_usize();
266        if cidx >= 1 && cidx <= body.arg_count {
267            return Some(cidx - 1);
268        }
269        for bb in body.basic_blocks.iter() {
270            for stmt in &bb.statements {
271                let rustc_middle::mir::StatementKind::Assign(assign) = &stmt.kind else {
272                    continue;
273                };
274                let (dest, rvalue) = &**assign;
275                if dest.local != current || !dest.projection.is_empty() {
276                    continue;
277                }
278                let source = match rvalue {
279                    rustc_middle::mir::Rvalue::Use(
280                        rustc_middle::mir::Operand::Copy(p)
281                        | rustc_middle::mir::Operand::Move(p),
282                        ..,
283                    ) => p.local,
284                    _ => continue,
285                };
286                if !seen.contains(&source) {
287                    seen.insert(source);
288                    queue.push_back(source);
289                }
290            }
291        }
292    }
293    None
294}
295
296/// Rewrite a contract's argument references (`PlaceBase::Arg(i)`) onto the
297/// enclosing callee's parameters using the call-site argument list. This rebinds
298/// a leaf callee's contract (e.g. `ptr::write`'s `ValidPtr(self, T, 1)`) to the
299/// wrapper callee's arguments (e.g. `maybe_init_slot`'s `ptr`).
300///
301/// Returns `false` when a referenced argument cannot be traced to a parameter
302/// (a constant, a field projection, a temporary, or a call result). Such a
303/// contract expresses an internal invariant of the leaf callee and cannot be
304/// restated as a precondition on this callee's parameters, so it is dropped.
305fn rebind_property_to_args<'tcx>(
306    prop: &mut Property<'tcx>,
307    args: &[rustc_middle::mir::Operand<'tcx>],
308    callee_args: &rustc_middle::ty::GenericArgs<'tcx>,
309    body: &rustc_middle::mir::Body<'tcx>,
310) -> bool {
311    match prop {
312        Property::Atom(atom) => {
313            let mut ok = true;
314            for arg in &mut atom.args {
315                if !rebind_property_arg(arg, args, callee_args, body) {
316                    ok = false;
317                }
318            }
319            if let Some(place) = &mut atom.for_each {
320                if !rebind_place(place, args, body) {
321                    ok = false;
322                }
323            }
324            ok
325        }
326        Property::And(and) => {
327            let mut ok = true;
328            for conjunct in &mut and.conjuncts {
329                if !rebind_property_to_args(conjunct, args, callee_args, body) {
330                    ok = false;
331                }
332            }
333            ok
334        }
335        Property::Or(or) => {
336            // Unlike `And`, an `Or` whose disjuncts were individually filtered
337            // would leave orphaned place-less disjuncts (e.g. `ValidPtr`'s
338            // `Size(T, 0) || Deref(p)` keeps `Size` after `Deref` is dropped,
339            // and `Size(u8, 0)` then reads as `Failed`).  Treat `Or` as
340            // all-or-nothing too: if any disjunct cannot be restated, drop the
341            // whole disjunction.
342            let mut ok = true;
343            for disjunct in &mut or.disjuncts {
344                if !rebind_property_to_args(disjunct, args, callee_args, body) {
345                    ok = false;
346                }
347            }
348            ok
349        }
350    }
351}
352
353fn rebind_property_arg<'tcx>(
354    arg: &mut PropertyArg<'tcx>,
355    args: &[rustc_middle::mir::Operand<'tcx>],
356    callee_args: &rustc_middle::ty::GenericArgs<'tcx>,
357    body: &rustc_middle::mir::Body<'tcx>,
358) -> bool {
359    match arg {
360        PropertyArg::Expr(expr) => rebind_expr(expr, args, callee_args, body),
361        PropertyArg::Predicates(preds) => {
362            let mut ok = true;
363            for p in preds {
364                if !rebind_expr(&mut p.lhs, args, callee_args, body) {
365                    ok = false;
366                }
367                if !rebind_expr(&mut p.rhs, args, callee_args, body) {
368                    ok = false;
369                }
370            }
371            ok
372        }
373        // Resolve the leaf callee's type parameter to the concrete type
374        // argument passed at the call site (e.g. `NonNull::<Node<T>>::as_mut`'s
375        // `Ptr2Ref(self, T)` -> `Ptr2Ref(self, Node<T>)`).
376        PropertyArg::Ty(ty) => {
377            rebind_ty(ty, callee_args);
378            true
379        }
380        _ => true,
381    }
382}
383
384fn rebind_expr<'tcx>(
385    expr: &mut ContractExpr<'tcx>,
386    args: &[rustc_middle::mir::Operand<'tcx>],
387    callee_args: &rustc_middle::ty::GenericArgs<'tcx>,
388    body: &rustc_middle::mir::Body<'tcx>,
389) -> bool {
390    match expr {
391        ContractExpr::Place(place) => rebind_place(place, args, body),
392        ContractExpr::Len(inner) => rebind_expr(inner, args, callee_args, body),
393        ContractExpr::SizeOf(ty) => {
394            rebind_ty(ty, callee_args);
395            true
396        }
397        ContractExpr::AlignOf(ty) => {
398            rebind_ty(ty, callee_args);
399            true
400        }
401        ContractExpr::IndexAccess { slice, index } => {
402            let a = rebind_expr(slice, args, callee_args, body);
403            let b = rebind_expr(index, args, callee_args, body);
404            a && b
405        }
406        ContractExpr::Binary { lhs, rhs, .. } => {
407            let a = rebind_expr(lhs, args, callee_args, body);
408            let b = rebind_expr(rhs, args, callee_args, body);
409            a && b
410        }
411        ContractExpr::Unary { expr, .. } => rebind_expr(expr, args, callee_args, body),
412        ContractExpr::If {
413            cond,
414            then_expr,
415            else_expr,
416        } => {
417            let a = rebind_expr(&mut cond.lhs, args, callee_args, body);
418            let b = rebind_expr(&mut cond.rhs, args, callee_args, body);
419            let c = rebind_expr(then_expr, args, callee_args, body);
420            let d = rebind_expr(else_expr, args, callee_args, body);
421            a && b && c && d
422        }
423        _ => true,
424    }
425}
426
427/// Replace a leaf callee's type parameter (`TyKind::Param`) with the concrete
428/// type argument passed at the call site.
429fn rebind_ty<'tcx>(
430    ty: &mut rustc_middle::ty::Ty<'tcx>,
431    callee_args: &rustc_middle::ty::GenericArgs<'tcx>,
432) {
433    if let rustc_middle::ty::TyKind::Param(param) = ty.kind() {
434        if let Some(actual) = callee_args.get(param.index as usize).and_then(|a| a.as_type()) {
435            *ty = actual;
436        }
437    }
438}
439
440fn rebind_place<'tcx>(
441    place: &mut ContractPlace<'tcx>,
442    args: &[rustc_middle::mir::Operand<'tcx>],
443    body: &rustc_middle::mir::Body<'tcx>,
444) -> bool {
445    // A contract references the leaf callee's arguments either as a 0-based
446    // `Arg(i)` (std JSON contracts) or as a MIR `Local(k)` (inline
447    // `#[rapx::requires]`, where local 1..=arg_count are the parameters). Map
448    // both onto the argument index, then trace that argument back to this
449    // callee's parameter. `Local(0)`/`Return` name the return value and cannot
450    // be restated as a precondition.
451    let arg_idx = match place.base {
452        PlaceBase::Arg(i) => Some(i),
453        PlaceBase::Local(k) if k >= 1 => Some(k - 1),
454        _ => return false,
455    };
456    match arg_idx
457        .and_then(|i| args.get(i))
458        .and_then(|op| call_arg_to_outer_param(op, body))
459    {
460        Some(outer) => {
461            place.base = PlaceBase::Arg(outer);
462            true
463        }
464        None => false,
465    }
466}
467
468/// Follow an unsafe callee's call chain to find inherited safety contracts.
469///
470/// When an unsafe callee (e.g. B) lacks its own contracts, look into its MIR
471/// body for the unsafe callees it calls (e.g. C, D).  If one of those has
472/// contracts (e.g. D), inherit them.  The chain `A -> B -> C -> D` means A's
473/// checkpoint on B is verified using D's contracts.
474///
475/// `visited` prevents infinite recursion on mutually-recursive functions.
476fn resolve_chain_contracts<'tcx>(
477    tcx: TyCtxt<'tcx>,
478    callee_def_id: DefId,
479    visited: &mut HashSet<DefId>,
480) -> FnContracts<'tcx> {
481    if !visited.insert(callee_def_id) {
482        return Vec::new();
483    }
484
485    if !tcx.is_mir_available(callee_def_id) {
486        return Vec::new();
487    }
488
489    let body = tcx.optimized_mir(callee_def_id);
490    let mut contracts = Vec::new();
491
492    for bb in body.basic_blocks.iter() {
493        let Some(terminator) = &bb.terminator else {
494            continue;
495        };
496        if let rustc_middle::mir::TerminatorKind::Call { func, args, .. } = &terminator.kind {
497            if let rustc_middle::mir::Operand::Constant(c) = func {
498                let rustc_middle::ty::TyKind::FnDef(sub_def_id, callee_args) =
499                    c.const_.ty().kind()
500                else {
501                    continue;
502                };
503                let sub_def_id = *sub_def_id;
504
505                let fn_sig = tcx.fn_sig(sub_def_id).skip_binder();
506                if fn_sig.safety() != rustc_hir::Safety::Unsafe {
507                    continue;
508                }
509
510                // Try annotation first.
511                let mut reqs = get_contract_from_annotation(tcx, sub_def_id);
512
513                // Try trait method requires.
514                if reqs.is_empty() {
515                    reqs = get_trait_method_requires(tcx, sub_def_id);
516                }
517
518                // Try std contracts database.
519                if reqs.is_empty() && is_std_crate_def_id(tcx, sub_def_id) {
520                    reqs = super::contract::json::query_json_contracts(tcx, sub_def_id);
521                }
522
523                // If still no contracts, recurse into this callee.
524                if reqs.is_empty() {
525                    reqs = resolve_chain_contracts(tcx, sub_def_id, visited);
526                }
527
528                // Rebind the leaf callee's argument references onto this
529                // callee's parameters (e.g. `ptr::write`'s `self` -> this
530                // callee's pointer argument), dropping contracts whose arguments
531                // are internal temporaries that can't be restated on this
532                // callee's parameters. Also resolve the leaf's type parameters
533                // to the concrete type arguments passed at the call site.
534                let arg_operands: Vec<_> = args.iter().map(|a| a.node.clone()).collect();
535                #[cfg(rapx_ge_99)]
536                let callee_args = callee_args.skip_binder();
537                reqs.retain_mut(|req| {
538                    rebind_property_to_args(req, &arg_operands, callee_args, body)
539                });
540
541                contracts.extend(reqs);
542            }
543        }
544    }
545
546    contracts
547}
548
549/// Visitor that collects targets annotated with `#[rapx::verify]`.
550pub(crate) struct VerifyTargetCollector<'tcx> {
551    tcx: TyCtxt<'tcx>,
552    mode: VerifyMode,
553    skip_invariant: bool,
554    crate_filter: Option<String>,
555    crate_filter_matched: bool,
556    module_filter: Option<String>,
557    module_filter_matched: bool,
558    /// All function targets to verify collected from the current crate.
559    pub function_targets: Vec<FunctionTarget<'tcx>>,
560    /// All struct targets to verify collected from the current crate.
561    pub struct_targets: HashMap<DefId, StructTarget<'tcx>>,
562    /// All trait impls to verify (marker traits and unsafe traits), one entry
563    /// per `impl` block.
564    pub trait_targets: Vec<TraitEnsurance<'tcx>>,
565    /// Cached contracts for each callee function so repeated callees are parsed once.
566    fn_contract_cache: HashMap<(DefId, bool), FnContracts<'tcx>>,
567}
568
569impl<'tcx> VerifyTargetCollector<'tcx> {
570    /// Collect all verification targets across the current crate and optionally
571    /// external crates (when `crate_filter` is set).
572    pub(crate) fn collect_all(
573        tcx: TyCtxt<'tcx>,
574        mode: VerifyMode,
575        skip_invariant: bool,
576        crate_filter: Option<String>,
577        module_filter: Option<String>,
578    ) -> Self {
579        let mut collector = Self::new(
580            tcx,
581            mode,
582            skip_invariant,
583            crate_filter.clone(),
584            module_filter,
585        );
586        tcx.hir_visit_all_item_likes_in_crate(&mut collector);
587        if crate_filter.is_some() {
588            collector.collect_extern_crate_targets();
589        }
590        collector.check_module_filter_result();
591        collector
592    }
593
594    /// Creates a new collector for the current type context.
595    pub(crate) fn new(
596        tcx: TyCtxt<'tcx>,
597        mode: VerifyMode,
598        skip_invariant: bool,
599        crate_filter: Option<String>,
600        module_filter: Option<String>,
601    ) -> Self {
602        VerifyTargetCollector {
603            tcx,
604            mode,
605            skip_invariant,
606            crate_filter,
607            crate_filter_matched: false,
608            module_filter,
609            module_filter_matched: false,
610            function_targets: Vec::new(),
611            struct_targets: HashMap::new(),
612            trait_targets: Vec::new(),
613            fn_contract_cache: HashMap::new(),
614        }
615    }
616
617    /// Returns (and caches) the contracts for an unsafe callee.
618    ///
619    /// Contracts are resolved with the following priority:
620    /// 1. Inline RAPx annotations attached to the callee.
621    /// 2. If the callee is a trait method impl without its own annotations,
622    ///    fall back to the trait method's `#[rapx::requires(...)]`.
623    /// 3. If no annotations are found and the callee belongs to the standard
624    ///    library, fall back to the bundled JSON contract database.
625    ///
626    /// Results are memoized in `fn_contract_cache` to avoid recomputation.
627    ///
628    /// `follow_chain` controls whether an otherwise-unannotated callee's
629    /// contract is resolved by walking its call chain (e.g. an unsafe wrapper
630    /// that calls `ptr::write`). It must be `true` when resolving the contracts
631    /// a *caller* has to prove at a call site (`callee_requires`), and `false`
632    /// when resolving a function's own assumed preconditions (`caller_requires`)
633    /// — those must not inherit contracts from the function's internal calls.
634    fn get_fn_contracts(&mut self, callee_def_id: DefId, follow_chain: bool) -> FnContracts<'tcx> {
635        let is_std = is_std_crate_def_id(self.tcx, callee_def_id);
636
637        let trait_requires = get_trait_method_requires(self.tcx, callee_def_id);
638
639        self.fn_contract_cache
640            .entry((callee_def_id, follow_chain))
641            .or_insert_with(|| {
642                let mut requires = get_contract_from_annotation(self.tcx, callee_def_id);
643
644                if requires.is_empty() && !trait_requires.is_empty() {
645                    requires = trait_requires.clone();
646                }
647
648                if requires.is_empty() && is_std {
649                    requires = super::contract::json::query_json_contracts(
650                        self.tcx,
651                        callee_def_id,
652                    );
653                }
654
655                if requires.is_empty() && follow_chain {
656                    // Recursively resolve contracts from the callee's call chain.
657                    // e.g. A -> B -> C -> D where B,C are unsafe unannotated,
658                    // D has contracts; follow the chain to D and use its contracts.
659                    let mut visited = HashSet::new();
660                    requires = resolve_chain_contracts(
661                        self.tcx,
662                        callee_def_id,
663                        &mut visited,
664                    );
665                    if requires.is_empty() {
666                        // Only warn for genuinely `unsafe` callees: a safe
667                        // function has no caller-side safety contract, so an
668                        // empty entry here is expected (e.g. a safe
669                        // `#[rapx::verify]` target whose body uses raw-pointer
670                        // derefs rather than unsafe callee calls).  Compiler
671                        // intrinsics (`extern "rust-intrinsic"`) have no MIR
672                        // and their safety is enforced by the type system, so
673                        // they never carry a JSON contract either.
674                        let is_intrinsic = self.tcx.intrinsic(callee_def_id).is_some();
675                        let has_json_entry = is_std
676                            && super::contract::json::std_contracts_has_entry(
677                                self.tcx,
678                                callee_def_id,
679                            );
680                        let is_verify_target = callee_def_id
681                            .as_local()
682                            .is_some_and(|id| has_rapx_verify_attr(self.tcx, id));
683                        if self.tcx.fn_sig(callee_def_id).skip_binder().safety()
684                            == rustc_hir::Safety::Unsafe
685                            && !is_intrinsic
686                            && !has_json_entry
687                            && !is_verify_target
688                        {
689                            let path = crate::helpers::name::get_cleaned_def_path_name(
690                                self.tcx,
691                                callee_def_id,
692                            );
693                            rap_warn!(
694                                "no safety contracts found for callee \"{path}\""
695                            );
696                        }
697                    } else {
698                        let path = crate::helpers::name::get_cleaned_def_path_name(
699                            self.tcx,
700                            callee_def_id,
701                        );
702                        rap_debug!(
703                            "resolved {} safety contract(s) for callee \"{path}\" via call chain",
704                            requires.len()
705                        );
706                    }
707                }
708
709                if requires.is_empty() {
710                    // An empty JSON entry (e.g. `fmt::new`) means "no safety
711                    // contract needed".  A `#[rapx::verify]` callee has its body
712                    // verified, so its "contract" is the body proof (or a struct
713                    // invariant) rather than a `requires`.  Anything else with no
714                    // contract is a genuine unknown.
715                    let has_json_entry = is_std
716                        && super::contract::json::std_contracts_has_entry(
717                            self.tcx,
718                            callee_def_id,
719                        );
720                    let is_verify_target = callee_def_id
721                        .as_local()
722                        .is_some_and(|id| has_rapx_verify_attr(self.tcx, id));
723                    if !has_json_entry && !is_verify_target {
724                        requires.push(Property::new(
725                            self.tcx,
726                            callee_def_id,
727                            "Unknown",
728                            &[],
729                        ));
730                    }
731                }
732
733                requires
734            })
735            .clone()
736    }
737
738    /// Builds a function target to verify from a function definition.
739    fn build_function_target(&mut self, def_id: DefId) -> FunctionTarget<'tcx> {
740        let checkpoints = collect_unsafe_callsites(self.tcx, def_id);
741        let unsafe_callees: HashSet<_> = checkpoints
742            .iter()
743            .filter_map(|checkpoint| checkpoint.callee)
744            .collect();
745        let callee_requires = unsafe_callees
746            .iter()
747            .map(|callee_def_id| {
748                let contracts = self.get_fn_contracts(*callee_def_id, true);
749                (*callee_def_id, contracts)
750            })
751            .collect();
752
753        let mut caller_requires = self.get_fn_contracts(def_id, false);
754        // A `requires` lifetime (`Alive(p, 'a)`) names a lifetime on the
755        // function, so bind it here (callee `requires` keep the generic `Ident`
756        // and are instantiated at the call site instead).
757        for contract in &mut caller_requires {
758            bind_alive_regions(self.tcx, def_id, contract);
759        }
760        // `get_fn_contracts` already resolves the entry contracts with the
761        // right precedence — inline `#[rapx::requires]`, then trait contracts,
762        // then the std JSON database (only when no annotation is present).
763        // Querying JSON again here would duplicate the annotation.
764
765        let raw_ptr_deref_checks = build_raw_ptr_deref_checks(self.tcx, def_id);
766        let static_mut_checks = build_static_mut_checks(self.tcx, def_id);
767
768        let owner_struct_def_id = get_adt_def_id_by_adt_method(self.tcx, def_id);
769        let mut struct_invariants = owner_struct_def_id
770            .map(|struct_def_id| {
771                get_struct_invariants_from_annotation(self.tcx, struct_def_id, def_id)
772            })
773            .unwrap_or_default();
774
775        // Struct invariants are implicit preconditions for every method:
776        // safe methods need them as automatic entry facts (no requires
777        // needed), and unsafe constructors verify they hold at return.
778        caller_requires.extend(struct_invariants.clone());
779
780        // drop is a destructor — the struct is being torn down, so we skip
781        // struct invariant checks at exit points.
782        if is_drop_impl(self.tcx, def_id) {
783            struct_invariants.clear();
784        }
785
786        // Standard-library type invariants: for each function parameter (and
787        // the return type), look up the type's invariants from
788        // std-type-invariants.json (including the built-in `[T]` slice key)
789        // and add them as preconditions.
790        let type_invariants = build_type_invariants_from_params(self.tcx, def_id);
791        caller_requires.extend(type_invariants.clone());
792
793        FunctionTarget {
794            def_id,
795            owner_struct_def_id,
796            checkpoints,
797            callee_requires,
798            caller_requires,
799            struct_invariants,
800            type_invariants,
801            raw_ptr_deref_checks,
802            static_mut_checks,
803        }
804    }
805
806    /// Adds a function target and updates its owning struct target when applicable.
807    fn push_function_target(&mut self, function_target: FunctionTarget<'tcx>) {
808        self.function_targets.push(function_target.clone());
809
810        if let Some(struct_def_id) = function_target.owner_struct_def_id {
811            self.struct_targets
812                .entry(struct_def_id)
813                .or_insert_with(|| StructTarget {
814                    def_id: struct_def_id,
815                    invariants: get_struct_invariants_from_annotation(
816                        self.tcx,
817                        struct_def_id,
818                        function_target.def_id,
819                    ),
820                    function_targets: Vec::new(),
821                })
822                .function_targets
823                .push(function_target);
824        }
825    }
826
827    /// Process MIR keys from non-local crates that match the `--crate` filter.
828    ///
829    /// `hir_visit_all_item_likes_in_crate` only visits the *local* crate, but
830    /// in a workspace the target crate (e.g. `core`) may be compiled as a
831    /// dependency of another crate (e.g. `std`).  This method iterates all
832    /// crates' MIR bodies and collects targets from those matching the filter.
833    fn collect_extern_crate_targets(&mut self) {
834        let local_crate = rustc_hir::def_id::LOCAL_CRATE;
835
836        for def_id in self.tcx.mir_keys(()) {
837            let def_id = def_id.to_def_id();
838            if def_id.krate == local_crate {
839                continue; // already visited via the HIR visitor
840            }
841            if !self.crate_name_matches(def_id) {
842                continue;
843            }
844            let def_kind = self.tcx.def_kind(def_id);
845            if !matches!(
846                def_kind,
847                rustc_hir::def::DefKind::Fn | rustc_hir::def::DefKind::AssocFn
848            ) {
849                continue;
850            }
851
852            // Skip `targeted` mode filtering — non-local crates don't have
853            // HIR attributes available (only metadata is present).
854            if matches!(self.mode, VerifyMode::Targeted) {
855                continue;
856            }
857
858            self.crate_filter_matched = true;
859
860            if !self.module_path_matches(def_id) {
861                continue;
862            }
863            self.module_filter_matched = true;
864
865            let function_target = self.build_function_target(def_id);
866            self.push_function_target(function_target);
867        }
868    }
869
870    fn crate_name_matches(&self, def_id: DefId) -> bool {
871        match self.crate_filter {
872            None => true,
873            Some(ref filter) => {
874                let crate_name = self.tcx.crate_name(def_id.krate);
875                if crate_name.as_str() == *filter {
876                    return true;
877                }
878                if let Ok(pkg_name) = std::env::var("CARGO_PKG_NAME") {
879                    if pkg_name == *filter {
880                        return true;
881                    }
882                }
883                false
884            }
885        }
886    }
887
888    fn module_path_matches(&self, def_id: DefId) -> bool {
889        let Some(ref filter) = self.module_filter else {
890            return true;
891        };
892        let def_path = self.tcx.def_path_str(def_id);
893
894        if def_path == *filter || def_path.starts_with(&format!("{}::", filter)) {
895            return true;
896        }
897        let crate_name = self.tcx.crate_name(def_id.krate);
898        let crate_prefix = format!("{}::", crate_name.as_str());
899
900        // Try matching filter after stripping the crate prefix.
901        // e.g. filter "slice" matches def_path "core::slice::raw::from_raw_parts"
902        // after stripping "core::".
903        if let Some(inner) = filter.strip_prefix(&crate_prefix) {
904            if def_path == inner || def_path.starts_with(&format!("{}::", inner)) {
905                return true;
906            }
907        }
908
909        // Try matching def_path after stripping the crate prefix.
910        // e.g. filter "core::slice" matches def_path "slice::raw::from_raw_parts"
911        // after stripping "core::" from the filter.
912        if let Some(inner) = def_path.strip_prefix(&crate_prefix) {
913            if inner == *filter || inner.starts_with(&format!("{}::", filter)) {
914                return true;
915            }
916        }
917
918        false
919    }
920
921    pub(crate) fn check_module_filter_result(&self) {
922        if let Some(ref filter) = self.crate_filter {
923            if !self.crate_filter_matched {
924                rap_warn!("[rapx::verify] --crate \"{filter}\" matched no targets");
925            }
926        }
927        if let Some(ref filter) = self.module_filter {
928            if !self.module_filter_matched {
929                rap_warn!("[rapx::verify] --module \"{filter}\" matched no functions in the crate");
930            }
931        }
932    }
933}
934
935fn get_trait_method_requires<'tcx>(tcx: TyCtxt<'tcx>, callee_def_id: DefId) -> FnContracts<'tcx> {
936    let Some(assoc_item) = tcx.opt_associated_item(callee_def_id) else {
937        return Vec::new();
938    };
939    let Some(trait_item_def_id) = assoc_item.trait_item_def_id() else {
940        return Vec::new();
941    };
942    get_contract_from_annotation(tcx, trait_item_def_id)
943}
944
945impl<'tcx> Visitor<'tcx> for VerifyTargetCollector<'tcx> {
946    type NestedFilter = nested_filter::OnlyBodies;
947
948    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
949        self.tcx
950    }
951
952    /// Detect `impl unsafe Trait for Type` blocks and record them as
953    /// [`TraitEnsurance`] placeholders.
954    ///
955    /// In `targeted` mode, only `impl` blocks annotated with `#[rapx::verify]`
956    /// are recorded.  In `scan` mode, all `unsafe trait` impls
957    /// are recorded.
958    fn visit_item(&mut self, item: &'tcx rustc_hir::Item<'tcx>) {
959        if let ItemKind::Impl(rustc_hir::Impl { of_trait, .. }) = &item.kind
960            && of_trait.is_some()
961        {
962            if matches!(self.mode, VerifyMode::Targeted)
963                && !has_rapx_verify_attr(self.tcx, item.owner_id.def_id)
964            {
965                rustc_hir::intravisit::walk_item(self, item);
966                return;
967            }
968
969            let impl_def_id = item.owner_id.to_def_id();
970
971            if !self.crate_name_matches(impl_def_id) {
972                rustc_hir::intravisit::walk_item(self, item);
973                return;
974            }
975            self.crate_filter_matched = true;
976
977            if !self.module_path_matches(impl_def_id) {
978                rustc_hir::intravisit::walk_item(self, item);
979                return;
980            }
981            self.module_filter_matched = true;
982
983            let trait_ref = { self.tcx.impl_opt_trait_ref(impl_def_id) };
984
985            if let Some(trait_ref) = trait_ref {
986                let trait_def_id = trait_ref.skip_binder().def_id;
987
988                let self_ty_def_id = resolve_impl_self_ty_def_id(item);
989
990                // Marker traits (`Send`/`Sync`) carry type-level obligations;
991                // unsafe traits with methods carry method-level `ensures`.
992                if let Some(kind) = marker_trait_kind(self.tcx, trait_def_id) {
993                    let obligations =
994                        build_marker_trait_obligations(self.tcx, self_ty_def_id, kind);
995                    self.trait_targets.push(TraitEnsurance {
996                        def_id: trait_def_id,
997                        impl_def_id,
998                        self_ty_def_id,
999                        kind: TraitEnsuranceKind::Marker(kind, obligations),
1000                    });
1001                } else if is_trait_unsafe(self.tcx, trait_def_id) {
1002                    let ensures = get_trait_contracts_from_annotation(self.tcx, trait_def_id);
1003
1004                    self.trait_targets.push(TraitEnsurance {
1005                        def_id: trait_def_id,
1006                        impl_def_id,
1007                        self_ty_def_id,
1008                        kind: TraitEnsuranceKind::Unsafe(ensures),
1009                    });
1010                }
1011            }
1012        }
1013
1014        rustc_hir::intravisit::walk_item(self, item);
1015    }
1016
1017    /// Visits each function body and records verification targets.
1018    ///
1019    /// In `targeted` mode, only functions annotated with `#[rapx::verify]` are collected.
1020    /// In `scan` mode, a HIR-level pre-filter (`contains_unsafe`
1021    /// and `function_has_struct_invariant`) avoids expensive MIR scanning for functions
1022    /// that have no unsafe content and no struct invariants.
1023    fn visit_fn(
1024        &mut self,
1025        _: FnKind<'tcx>,
1026        _: &'tcx FnDecl<'tcx>,
1027        body_id: BodyId,
1028        _: Span,
1029        id: LocalDefId,
1030    ) -> Self::Result {
1031        if matches!(self.mode, VerifyMode::Targeted) && !has_rapx_verify_attr(self.tcx, id) {
1032            // Drop impls on structs with invariants are implicitly verified.
1033            if !is_drop_impl(self.tcx, id.to_def_id()) {
1034                return;
1035            }
1036        }
1037
1038        // HIR pre-filter: skip functions that have nothing to verify.
1039        // `contains_unsafe` catches functions with unsafe blocks/declarations;
1040        // `function_has_struct_invariant` catches methods on structs with invariants;
1041        // `function_has_trait_ensurance` catches methods on unsafe trait impls with contracts.
1042        let def_id = id.to_def_id();
1043
1044        // Skip never-returning (divergent) functions — they have no return
1045        // paths and can trigger stack overflows in downstream analysis.
1046        if let rustc_hir::def::DefKind::Fn = self.tcx.def_kind(def_id) {
1047            let fn_sig = self.tcx.fn_sig(def_id).skip_binder();
1048            if matches!(
1049                fn_sig.output().skip_binder().kind(),
1050                rustc_type_ir::TyKind::Never
1051            ) {
1052                return;
1053            }
1054        }
1055
1056        if !matches!(self.mode, VerifyMode::Targeted) {
1057            if !hir_contains_unsafe(self.tcx, body_id)
1058                && !function_has_struct_invariant(self.tcx, def_id)
1059                && !function_has_trait_ensurance(self.tcx, def_id)
1060            {
1061                return;
1062            }
1063        }
1064
1065        if !self.crate_name_matches(def_id) {
1066            return;
1067        }
1068        self.crate_filter_matched = true;
1069
1070        if !self.module_path_matches(def_id) {
1071            return;
1072        }
1073        self.module_filter_matched = true;
1074
1075        let function_target = self.build_function_target(def_id);
1076
1077        match self.mode {
1078            VerifyMode::Targeted => {}
1079            VerifyMode::Scan => {
1080                if function_target.checkpoints.is_empty()
1081                    && function_target.raw_ptr_deref_checks.is_empty()
1082                    && function_target.static_mut_checks.is_empty()
1083                {
1084                    if !function_target.struct_invariants.is_empty() {
1085                        if self.skip_invariant {
1086                            return;
1087                        }
1088                    } else {
1089                        let root = crate::analysis::safety_flow::root::scan_mir(self.tcx, def_id);
1090                        if root.is_none() {
1091                            return;
1092                        }
1093                    }
1094                }
1095            }
1096        }
1097
1098        self.push_function_target(function_target);
1099    }
1100}
1101
1102/// Analysis pass that finds all verification targets.
1103///
1104/// In `targeted` mode, only functions annotated with `#[rapx::verify]` are listed.
1105/// In `scan` mode, all functions with unsafe callees or struct invariants are listed.
1106pub(crate) struct PrepareTargets<'tcx> {
1107    tcx: TyCtxt<'tcx>,
1108    mode: VerifyMode,
1109    skip_invariant: bool,
1110    crate_filter: Option<String>,
1111    module_filter: Option<String>,
1112}
1113
1114impl<'tcx> Analysis for PrepareTargets<'tcx> {
1115    fn run(&mut self) {
1116        let collector = VerifyTargetCollector::collect_all(
1117            self.tcx,
1118            self.mode,
1119            self.skip_invariant,
1120            self.crate_filter.clone(),
1121            self.module_filter.clone(),
1122        );
1123
1124        // Free functions (no owning struct)
1125        let free_targets: Vec<_> = collector
1126            .function_targets
1127            .iter()
1128            .filter(|target| target.owner_struct_def_id.is_none())
1129            .collect();
1130        for target in &free_targets {
1131            let target_path = self.tcx.def_path_str(target.def_id);
1132            rap_info!("============================================================");
1133            rap_info!(
1134                "[rapx::verify] prepare targets for free function: {}",
1135                target_path
1136            );
1137            rap_info!("============================================================");
1138            self.log_free_function_unsafe_callees(target);
1139            rap_info!("");
1140        }
1141
1142        // Structs with methods
1143        let mut struct_ids: Vec<_> = collector.struct_targets.keys().copied().collect();
1144        struct_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
1145
1146        for struct_def_id in struct_ids {
1147            let Some(struct_target) = collector.struct_targets.get(&struct_def_id) else {
1148                continue;
1149            };
1150            let struct_path = self.tcx.def_path_str(struct_target.def_id);
1151
1152            rap_info!("============================================================");
1153            rap_info!("[rapx::verify] prepare targets for struct: {}", struct_path);
1154            rap_info!("============================================================");
1155
1156            self.log_struct_invariants(struct_target);
1157
1158            for target in &struct_target.function_targets {
1159                self.log_method_target(target);
1160            }
1161        }
1162
1163        // Traits with impl methods
1164        let mut trait_targets: Vec<_> = collector.trait_targets.iter().collect();
1165        trait_targets.sort_by_key(|t| self.tcx.def_path_str(t.def_id));
1166
1167        for trait_target in trait_targets {
1168            let trait_path = self.tcx.def_path_str(trait_target.def_id);
1169
1170            match &trait_target.kind {
1171                TraitEnsuranceKind::Unsafe(_) => {
1172                    rap_info!("============================================================");
1173                    rap_info!(
1174                        "[rapx::verify] prepare targets for unsafe trait: {}",
1175                        trait_path
1176                    );
1177                    rap_info!("============================================================");
1178
1179                    self.log_trait_ensurance(trait_target);
1180                }
1181                TraitEnsuranceKind::Marker(kind, obligations) => {
1182                    let name = match kind {
1183                        MarkerTraitKind::Send => "Send",
1184                        MarkerTraitKind::Sync => "Sync",
1185                    };
1186                    rap_info!("============================================================");
1187                    rap_info!("[rapx::verify] prepare targets for marker trait: {}", name);
1188                    rap_info!("============================================================");
1189
1190                    self.log_marker_trait(trait_target, obligations);
1191                }
1192            }
1193
1194            rap_info!("");
1195        }
1196
1197        let total_free = free_targets.len();
1198        let total_method = collector
1199            .function_targets
1200            .iter()
1201            .filter(|target| target.owner_struct_def_id.is_some())
1202            .count();
1203        let total_struct = collector.struct_targets.len();
1204        let total_trait = collector.trait_targets.len();
1205
1206        rap_info!("============================================================");
1207        rap_info!(
1208            "[rapx::verify] total: {} free function(s), {} method(s), {} struct(s), {} trait(s)",
1209            total_free,
1210            total_method,
1211            total_struct,
1212            total_trait
1213        );
1214        rap_info!("============================================================");
1215    }
1216}
1217
1218impl<'tcx> PrepareTargets<'tcx> {
1219    pub(crate) fn new(
1220        tcx: TyCtxt<'tcx>,
1221        mode: VerifyMode,
1222        skip_invariant: bool,
1223        crate_filter: Option<String>,
1224        module_filter: Option<String>,
1225    ) -> Self {
1226        PrepareTargets {
1227            tcx,
1228            mode,
1229            skip_invariant,
1230            crate_filter,
1231            module_filter,
1232        }
1233    }
1234
1235    fn log_struct_invariants(&self, struct_target: &StructTarget<'tcx>) {
1236        if struct_target.invariants.is_empty() {
1237            rap_info!("  struct invariants: <none>");
1238        } else {
1239            rap_info!("  struct invariants:");
1240            for property in
1241                crate::verify::display::dedup_compound_props(struct_target.invariants.iter())
1242            {
1243                rap_info!(
1244                    "    - {}",
1245                    property.display_for_report(self.tcx, Some(struct_target.def_id), None,)
1246                );
1247            }
1248        }
1249    }
1250
1251    fn log_trait_ensurance(&self, trait_target: &TraitEnsurance<'tcx>) {
1252        if let Some(self_ty) = trait_target.self_ty_def_id {
1253            rap_info!("  impl for: {}", self.tcx.def_path_str(self_ty));
1254        }
1255        let TraitEnsuranceKind::Unsafe(ensures) = &trait_target.kind else {
1256            return;
1257        };
1258        if ensures.is_empty() {
1259            rap_info!("  ensures: <none>");
1260        } else {
1261            rap_info!("  ensures (implementor must satisfy):");
1262            for (method_name, contracts) in ensures {
1263                rap_info!("    fn {}:", method_name);
1264                for property in crate::verify::display::dedup_compound_props(contracts.iter()) {
1265                    let (call, _meaning) = crate::verify::display::fmt_contract_expanded(
1266                        self.tcx,
1267                        property,
1268                        trait_target.self_ty_def_id,
1269                        None,
1270                    );
1271                    rap_info!("      - {call}");
1272                }
1273            }
1274        }
1275    }
1276
1277    fn log_marker_trait(
1278        &self,
1279        trait_target: &TraitEnsurance<'tcx>,
1280        obligations: &[Property<'tcx>],
1281    ) {
1282        if let Some(self_ty) = trait_target.self_ty_def_id {
1283            rap_info!("  impl for: {}", self.tcx.def_path_str(self_ty));
1284        }
1285        if obligations.is_empty() {
1286            rap_info!("  obligations: <none>");
1287        } else {
1288            rap_info!("  obligations:");
1289            for property in obligations {
1290                let (call, _meaning) = crate::verify::display::fmt_contract_expanded(
1291                    self.tcx,
1292                    property,
1293                    trait_target.self_ty_def_id,
1294                    None,
1295                );
1296                rap_info!("    - {call}");
1297            }
1298        }
1299    }
1300
1301    fn log_method_target(&self, target: &FunctionTarget<'tcx>) {
1302        let name = short_fn_name(self.tcx, target.def_id);
1303        let dashes = 62usize.saturating_sub(10 + name.len());
1304        rap_info!("  --- method: {name} {}", "-".repeat(dashes));
1305
1306        let return_blocks = collect_return_block_indices(self.tcx, target.def_id);
1307        rap_info!(
1308            "      return checkpoints: {} block(s) {:?}",
1309            return_blocks.len(),
1310            return_blocks
1311                .iter()
1312                .map(|bb| bb.as_usize())
1313                .collect::<Vec<_>>()
1314        );
1315
1316        let path_map = self.build_checkpoint_path_map(target);
1317        self.log_unsafe_callees_and_contracts(target, &path_map);
1318    }
1319
1320    fn log_free_function_unsafe_callees(&self, target: &FunctionTarget<'tcx>) {
1321        let path_map = self.build_checkpoint_path_map(target);
1322        self.log_unsafe_callees_and_contracts(target, &path_map);
1323    }
1324
1325    fn log_unsafe_callees_and_contracts(
1326        &self,
1327        target: &FunctionTarget<'tcx>,
1328        path_map: &FxHashMap<DefId, Vec<(usize, Vec<String>)>>,
1329    ) {
1330        if target.callee_requires.is_empty() {
1331            rap_info!("      unsafe checkpoints: <none>");
1332            return;
1333        }
1334
1335        let mut unsafe_callee_ids: Vec<_> = target.callee_requires.keys().copied().collect();
1336        unsafe_callee_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
1337
1338        for unsafe_callee_def_id in unsafe_callee_ids {
1339            let fn_sig = self.tcx.fn_sig(unsafe_callee_def_id).skip_binder();
1340            let unsafe_callee_path = self.tcx.def_path_str(unsafe_callee_def_id);
1341            let inputs: Vec<String> = fn_sig
1342                .inputs()
1343                .skip_binder()
1344                .iter()
1345                .map(|ty| format!("{}", ty))
1346                .collect();
1347            let output = format!("{}", fn_sig.output().skip_binder());
1348            rap_info!(
1349                "      unsafe callee: {}({}) -> {}",
1350                unsafe_callee_path,
1351                inputs.join(", "),
1352                output,
1353            );
1354
1355            if let Some(requires) = target.callee_requires.get(&unsafe_callee_def_id) {
1356                if requires.is_empty() {
1357                    rap_info!("        safety contracts: <none>");
1358                } else {
1359                    rap_info!("        safety contracts:");
1360                    for property in crate::verify::display::dedup_compound_props(requires.iter()) {
1361                        rap_info!(
1362                            "          - {}",
1363                            property.display_for_report(
1364                                self.tcx,
1365                                target.owner_struct_def_id,
1366                                Some(unsafe_callee_def_id),
1367                            )
1368                        );
1369                    }
1370                }
1371            }
1372
1373            if let Some(path_entries) = path_map.get(&unsafe_callee_def_id) {
1374                for (_block_idx, path_strings) in path_entries {
1375                    if path_strings.is_empty() {
1376                        rap_info!("        path: <none>");
1377                    } else {
1378                        for desc in path_strings {
1379                            rap_info!("        path: shortest path: {desc}");
1380                        }
1381                    }
1382                }
1383            }
1384        }
1385    }
1386
1387    fn build_checkpoint_path_map(
1388        &self,
1389        target: &FunctionTarget<'tcx>,
1390    ) -> FxHashMap<DefId, Vec<(usize, Vec<String>)>> {
1391        let mut path_map: FxHashMap<DefId, Vec<(usize, Vec<String>)>> = FxHashMap::default();
1392
1393        if target.checkpoints.is_empty() {
1394            return path_map;
1395        }
1396
1397        let groups =
1398            PathExtractor::new(self.tcx, target.def_id, target.checkpoints.clone(), 0).run();
1399
1400        for group in &groups {
1401            for checkpoint in &group.checkpoints {
1402                if let Some(callee_def_id) = checkpoint.callee {
1403                    let block_idx = checkpoint.block.as_usize();
1404                    let mut path_strings: Vec<String> = Vec::new();
1405                    let _ = group.tree.walk_prefixes(
1406                        checkpoint.block.as_usize(),
1407                        &mut |prefix: &[usize]| -> bool {
1408                            let desc = prefix
1409                                .iter()
1410                                .map(usize::to_string)
1411                                .collect::<Vec<_>>()
1412                                .join(" -> ");
1413                            path_strings.push(desc);
1414                            true
1415                        },
1416                    );
1417
1418                    path_map
1419                        .entry(callee_def_id)
1420                        .or_insert_with(Vec::new)
1421                        .push((block_idx, path_strings));
1422                }
1423            }
1424        }
1425
1426        path_map
1427    }
1428}
1429
1430fn is_rapx_named_attr(attr: &Attribute, name: &str) -> bool {
1431    let path = attr.path();
1432    if path.len() >= 2
1433        && path[path.len() - 2].as_str() == "rapx"
1434        && path[path.len() - 1].as_str() == name
1435    {
1436        return true;
1437    }
1438    // In newer rustc, tool attrs may have the tool prefix stripped from the path.
1439    // Match bare name when the attribute has exactly one path segment.
1440    path.len() == 1 && path[0].as_str() == name
1441}
1442
1443/// Resolve whether an `unsafe impl` is implementing the `Send`/`Sync` marker trait.
1444fn marker_trait_kind(tcx: TyCtxt<'_>, trait_def_id: DefId) -> Option<MarkerTraitKind> {
1445    if tcx.get_diagnostic_item(rustc_span::sym::Send) == Some(trait_def_id) {
1446        Some(MarkerTraitKind::Send)
1447    } else if tcx.get_diagnostic_item(rustc_span::sym::Sync) == Some(trait_def_id) {
1448        Some(MarkerTraitKind::Sync)
1449    } else {
1450        None
1451    }
1452}
1453
1454/// Generate the type-level `ensures` obligations for a marker-trait impl from
1455/// the bundled `std-trait-ensures.json` template, substituting `ty:Self` with
1456/// the implementing type.
1457fn build_marker_trait_obligations<'tcx>(
1458    tcx: TyCtxt<'tcx>,
1459    self_ty_def_id: Option<DefId>,
1460    trait_kind: MarkerTraitKind,
1461) -> Vec<Property<'tcx>> {
1462    let Some(self_ty_def_id) = self_ty_def_id else {
1463        return Vec::new();
1464    };
1465    let self_ty = tcx.type_of(self_ty_def_id).skip_binder();
1466
1467    let trait_def_id = match trait_kind {
1468        MarkerTraitKind::Send => tcx.get_diagnostic_item(rustc_span::sym::Send),
1469        MarkerTraitKind::Sync => tcx.get_diagnostic_item(rustc_span::sym::Sync),
1470    };
1471    let Some(trait_def_id) = trait_def_id else {
1472        return Vec::new();
1473    };
1474
1475    let templates = super::contract::json::query_trait_ensures(tcx, trait_def_id);
1476    let mut obligations = Vec::new();
1477    for entry in templates {
1478        if let Some(prop) = build_type_atom(tcx, self_ty_def_id, &entry, self_ty) {
1479            obligations.push(prop);
1480        }
1481    }
1482    obligations
1483}
1484
1485/// Build a single type-level obligation from a `std-trait-ensures.json` entry,
1486/// substituting `ty:Self` with `self_ty`.  Supports the `any` disjunction and
1487/// falls back to named compound properties (`expand_compound`) for tags that are
1488/// not built-in primitives.
1489fn build_type_atom<'tcx>(
1490    tcx: TyCtxt<'tcx>,
1491    def_id: DefId,
1492    entry: &super::contract::json::JsonProperty,
1493    self_ty: rustc_middle::ty::Ty<'tcx>,
1494) -> Option<Property<'tcx>> {
1495    if let Some(items) = &entry.any {
1496        let disjuncts: Vec<Property<'tcx>> = items
1497            .iter()
1498            .filter_map(|item| match item {
1499                super::contract::json::AnyItem::Single(e) => {
1500                    build_type_atom(tcx, def_id, e, self_ty)
1501                }
1502                super::contract::json::AnyItem::And(es) => {
1503                    let conjuncts: Vec<Property<'tcx>> = es
1504                        .iter()
1505                        .filter_map(|e| build_type_atom(tcx, def_id, e, self_ty))
1506                        .collect();
1507                    if conjuncts.is_empty() {
1508                        None
1509                    } else {
1510                        Some(Property::new_and(conjuncts))
1511                    }
1512                }
1513            })
1514            .collect();
1515        return if disjuncts.is_empty() {
1516            None
1517        } else {
1518            Some(Property::new_or(disjuncts))
1519        };
1520    }
1521
1522    match entry.tag.as_str() {
1523        "ContainNoType" => {
1524            let mut args = vec![PropertyArg::Ty(self_ty)];
1525            args.extend(entry.args[1..].iter().map(|s| {
1526                PropertyArg::Ident(s.strip_prefix("ty:").unwrap_or(s).to_string())
1527            }));
1528            Some(Property::new_atom(PropertyKind::ContainNoType, args))
1529        }
1530        "NoRawPtr" => Some(Property::new_atom(
1531            PropertyKind::NoRawPtr,
1532            vec![PropertyArg::Ty(self_ty)],
1533        )),
1534        "NoInternalMut" => Some(Property::new_atom(
1535            PropertyKind::NoInternalMut,
1536            vec![PropertyArg::Ty(self_ty)],
1537        )),
1538        "UniInternalMut" => Some(Property::new_atom(
1539            PropertyKind::UniInternalMut,
1540            vec![PropertyArg::Ty(self_ty)],
1541        )),
1542        "AtomicUpdate" => Some(Property::new_atom(
1543            PropertyKind::AtomicUpdate,
1544            vec![PropertyArg::Ty(self_ty)],
1545        )),
1546        "RefSend" => Some(Property::new_atom(
1547            PropertyKind::RefSend,
1548            vec![PropertyArg::Ty(self_ty)],
1549        )),
1550        // Fall back to a named compound property (e.g. `TamedRawPtr` defined in
1551        // `std-compound-properties.rs`).  The `ty:Self` placeholder is normalized
1552        // to `Self` and resolved by the compound's `Ty` parameter; a `Ptr`
1553        // parameter not supplied by the JSON template is filled in from the
1554        // struct's own `Allocated`/`Owning` invariant field.
1555        _ => {
1556            let mut exprs: Vec<syn::Expr> = entry
1557                .args
1558                .iter()
1559                .filter_map(|s| {
1560                    let normalized = super::contract::json::normalize_json_contract_arg(s);
1561                    syn::parse_str::<syn::Expr>(&normalized).ok()
1562                })
1563                .collect();
1564            if exprs.len() != entry.args.len() {
1565                return None;
1566            }
1567
1568            if let Some(spec) = super::contract::compound::find_compound(def_id.krate, &entry.tag) {
1569                for i in exprs.len()..spec.param_tys.len() {
1570                    if spec.param_tys.get(i).map(|s| s.as_str()) != Some("Ptr") {
1571                        return None;
1572                    }
1573                    let Some(field) = extract_tamed_field(tcx, def_id) else {
1574                        return None;
1575                    };
1576                    let Ok(e) = syn::parse_str::<syn::Expr>(&field) else {
1577                        return None;
1578                    };
1579                    exprs.push(e);
1580                }
1581            }
1582
1583            match super::contract::compound::expand_compound(tcx, def_id, &entry.tag, &exprs) {
1584                Some(mut props) if !props.is_empty() => {
1585                    // A compound body expands into one property per conjunct, each
1586                    // tagged with the compound's origin.  Hoist that origin onto the
1587                    // combined node so the report shows `Name(args)` once instead of
1588                    // `And(Name, Name, Name)`.
1589                    let origin = props.first().and_then(|p| p.origin()).cloned();
1590                    for p in &mut props {
1591                        p.clear_origin();
1592                    }
1593                    let mut combined = Property::conjunction(props);
1594                    if let Some(o) = origin {
1595                        combined.set_origin(o.name, o.args, o.meaning);
1596                    }
1597                    Some(combined)
1598                }
1599                _ => None,
1600            }
1601        }
1602    }
1603}
1604
1605/// The raw-pointer field name a struct declares via `#[rapx::invariant(Allocated(field))]`
1606/// or `#[rapx::invariant(Owning(field))]`, used to bind a `TamedRawPtr` compound's
1607/// `Ptr` parameter.
1608fn extract_tamed_field<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<String> {
1609    let invariants = get_struct_invariants_from_annotation(tcx, def_id, def_id);
1610    invariants
1611        .iter()
1612        .find(|p| {
1613            matches!(
1614                p.kind(),
1615                Some(PropertyKind::Allocated) | Some(PropertyKind::Owning)
1616            )
1617        })
1618        .and_then(|p| p.args().first())
1619        .and_then(|a| super::contract::place::field_name_from_arg(tcx, def_id, a))
1620}
1621
1622fn collect_properties_from_named_attrs<'tcx>(
1623    tcx: TyCtxt<'tcx>,
1624    attrs: impl IntoIterator<Item = &'tcx Attribute>,
1625    property_def_id: DefId,
1626    parse_error_label: &str,
1627    attr_name: &str,
1628) -> Vec<Property<'tcx>> {
1629    let mut results = Vec::new();
1630
1631    for attr in attrs {
1632        if !is_rapx_named_attr(attr, attr_name) {
1633            continue;
1634        }
1635
1636        let attr_str = crate::compat::attribute_to_string(tcx, attr);
1637        let parsed = match parse_rapx_attr(attr_str.as_str(), attr_name) {
1638            Ok(parsed) => parsed,
1639            Err(err) => {
1640                rap_error!(
1641                    "Failed to parse RAPx {} attr '{}': {}",
1642                    parse_error_label,
1643                    attr_str,
1644                    err
1645                );
1646                continue;
1647            }
1648        };
1649
1650        let Some(property) = parsed else { continue };
1651        results.extend(
1652            Property::parse_list(tcx, property_def_id, property.tag.as_str(), &property.args)
1653                .into_iter()
1654                .map(move |mut p| {
1655                    p.apply_kind(property.kind.as_deref());
1656                    p
1657                }),
1658        );
1659    }
1660
1661    results
1662}
1663
1664/// Parses `requires` contracts from source-level RAPx annotations attached to a definition.
1665pub(crate) fn get_contract_from_annotation<'tcx>(
1666    tcx: TyCtxt<'tcx>,
1667    def_id: DefId,
1668) -> FnContracts<'tcx> {
1669    // Prefer HIR-level attrs for local defs (tool attributes visible),
1670    // fall back to get_all_attrs for external defs.
1671    if let Some(local_def_id) = def_id.as_local() {
1672        let hir_id = tcx.local_def_id_to_hir_id(local_def_id);
1673        let hir_attrs = tcx.hir_attrs(hir_id);
1674        // hir_attrs is &'tcx [Attribute<'tcx>]; iter yields &'tcx Attribute<'tcx>
1675        return collect_properties_from_named_attrs(tcx, hir_attrs, def_id, "requires", "requires");
1676    }
1677
1678    let attrs = crate::compat::get_all_attrs(tcx, def_id);
1679    collect_properties_from_named_attrs(tcx, attrs, def_id, "requires", "requires")
1680}
1681
1682/// Parses struct invariants from source-level RAPx annotations attached to a struct definition.
1683pub(crate) fn get_struct_invariants_from_annotation<'tcx>(
1684    tcx: TyCtxt<'tcx>,
1685    struct_def_id: DefId,
1686    context_def_id: DefId,
1687) -> StructInvariants<'tcx> {
1688    let Some(local_def_id) = struct_def_id.as_local() else {
1689        return Vec::new();
1690    };
1691
1692    let item = tcx.hir_expect_item(local_def_id);
1693    if !matches!(item.kind, ItemKind::Struct(..)) {
1694        return Vec::new();
1695    }
1696
1697    let mut invariants = collect_properties_from_named_attrs(
1698        tcx,
1699        crate::compat::get_all_attrs(tcx, struct_def_id),
1700        context_def_id,
1701        "invariant",
1702        "requires",
1703    );
1704    invariants.extend(collect_properties_from_named_attrs(
1705        tcx,
1706        crate::compat::get_all_attrs(tcx, struct_def_id),
1707        context_def_id,
1708        "invariant",
1709        "invariant",
1710    ));
1711    // A lifetime in a struct invariant (`Alive(ptr, 'a)`) names a lifetime on
1712    // the *struct*, so bind it here against the struct's own generics. Parsing
1713    // used `context_def_id` (the method), whose generics lack `'a`.
1714    for inv in &mut invariants {
1715        bind_alive_regions(tcx, struct_def_id, inv);
1716    }
1717    invariants
1718}
1719
1720/// Bind `Alive(p, 'a)` region idents in a contract to `Region`s, resolving `'a`
1721/// against `def_id` (the item that declares `'a`: the struct for its
1722/// invariants, the function for its `requires`).
1723fn bind_alive_regions<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, property: &mut Property<'tcx>) {
1724    match property {
1725        Property::Atom(atom) => {
1726            if atom.kind == PropertyKind::Alive
1727                && let Some(PropertyArg::Ident(name)) = atom.args.get(1).cloned()
1728                && let Some(region) =
1729                    crate::verify::vm::region::resolve_region_name(tcx, def_id, &name)
1730            {
1731                atom.args[1] = PropertyArg::Region(region);
1732            }
1733        }
1734        Property::And(and) => {
1735            for conjunct in &mut and.conjuncts {
1736                bind_alive_regions(tcx, def_id, conjunct);
1737            }
1738        }
1739        Property::Or(or) => {
1740            for disjunct in &mut or.disjuncts {
1741                bind_alive_regions(tcx, def_id, disjunct);
1742            }
1743        }
1744    }
1745}
1746
1747/// Parses trait safety contracts from `#[rapx::ensures(...)]` on unsafe trait
1748/// methods, grouped by method name.
1749fn get_trait_contracts_from_annotation<'tcx>(
1750    tcx: TyCtxt<'tcx>,
1751    trait_def_id: DefId,
1752) -> Vec<(String, FnContracts<'tcx>)> {
1753    let Some(local_id) = trait_def_id.as_local() else {
1754        return Vec::new();
1755    };
1756
1757    let item = tcx.hir_expect_item(local_id);
1758
1759    let trait_items = {
1760        #[cfg(not(rapx_ge_99))]
1761        if let ItemKind::Trait(.., items) = &item.kind {
1762            items
1763        } else {
1764            return Vec::new();
1765        }
1766        #[cfg(rapx_ge_99)]
1767        if let ItemKind::Trait { items, .. } = &item.kind {
1768            items
1769        } else {
1770            return Vec::new();
1771        }
1772    };
1773
1774    let mut ensures: Vec<(String, FnContracts<'tcx>)> = Vec::new();
1775
1776    for trait_item_id in trait_items.iter() {
1777        let trait_item_def_id = trait_item_id.owner_id.to_def_id();
1778        let method_name = tcx.def_path_str(trait_item_def_id);
1779        let attrs = crate::compat::get_all_attrs(tcx, trait_item_def_id);
1780
1781        let method_ensures = collect_properties_from_named_attrs(
1782            tcx,
1783            attrs,
1784            trait_item_def_id,
1785            "trait ensures",
1786            "ensures",
1787        );
1788
1789        if !method_ensures.is_empty() {
1790            ensures.push((method_name, method_ensures));
1791        }
1792    }
1793
1794    ensures
1795}
1796
1797/// Build (pseudo-checkpoint, properties) pairs for every raw pointer dereference
1798/// in the target function.
1799fn build_raw_ptr_deref_checks<'tcx>(
1800    tcx: TyCtxt<'tcx>,
1801    def_id: DefId,
1802) -> Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)> {
1803    let infos = collect_raw_ptr_deref_info(tcx, def_id);
1804    if infos.is_empty() {
1805        return Vec::new();
1806    }
1807
1808    infos
1809        .into_iter()
1810        .map(|info| {
1811            let target = PropertyArg::Expr(ContractExpr::Place(ContractPlace {
1812                base: PlaceBase::Arg(0),
1813                projections: vec![],
1814            }));
1815            let ty = PropertyArg::Ty(info.pointee_ty);
1816            let count = PropertyArg::Expr(ContractExpr::Const(1));
1817
1818            let mut properties = if info.is_ptr2ref {
1819                vec![
1820                    Property::new_atom(
1821                        PropertyKind::Init,
1822                        vec![target.clone(), ty.clone(), count.clone()],
1823                    ),
1824                    Property::new_atom(PropertyKind::NonNull, vec![target.clone()]),
1825                    Property::new_atom(
1826                        PropertyKind::Allocated,
1827                        vec![target.clone(), ty.clone(), count.clone()],
1828                    ),
1829                    Property::new_atom(
1830                        PropertyKind::InBound,
1831                        vec![target.clone(), ty.clone(), count.clone()],
1832                    ),
1833                    Property::new_atom(PropertyKind::Align, vec![target.clone(), ty.clone()]),
1834                    {
1835                        let mut p = Property::new_atom(PropertyKind::Alias, vec![target.clone()]);
1836                        p.set_contract_kind(crate::verify::contract::ContractKind::Hazard);
1837                        p
1838                    },
1839                ]
1840            } else {
1841                vec![
1842                    Property::new_atom(
1843                        PropertyKind::Allocated,
1844                        vec![target.clone(), ty.clone(), count.clone()],
1845                    ),
1846                    Property::new_atom(
1847                        PropertyKind::InBound,
1848                        vec![target.clone(), ty.clone(), count.clone()],
1849                    ),
1850                    Property::new_atom(PropertyKind::Align, vec![target.clone(), ty.clone()]),
1851                ]
1852            };
1853
1854            if info.is_read && !info.is_ptr2ref {
1855                properties.push(Property::new_atom(PropertyKind::Typed, vec![target, ty]));
1856            }
1857
1858            (
1859                Checkpoint {
1860                    caller: def_id,
1861                    callee: None,
1862                    block: info.block,
1863                    args: vec![info.ptr_operand],
1864                    kind: crate::helpers::mir_scan::CheckpointKind::RawPtrDeref,
1865                    destination: Some(info.destination),
1866                    is_mut_ref: info.is_mut_ref,
1867                    statement_index: info.statement_index,
1868                },
1869                properties,
1870            )
1871        })
1872        .collect()
1873}
1874
1875/// Build (pseudo-checkpoint, properties) pairs for every static mut access
1876/// in the target function.
1877fn build_static_mut_checks<'tcx>(
1878    tcx: TyCtxt<'tcx>,
1879    def_id: DefId,
1880) -> Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)> {
1881    let infos = collect_static_mut_access_info(tcx, def_id);
1882    if infos.is_empty() {
1883        return Vec::new();
1884    }
1885
1886    infos
1887        .into_iter()
1888        .map(|info| {
1889            let target = PropertyArg::Expr(ContractExpr::Place(ContractPlace {
1890                base: PlaceBase::Arg(0),
1891                projections: vec![],
1892            }));
1893            let ty = PropertyArg::Ty(info.ty);
1894            let count = PropertyArg::Expr(ContractExpr::Const(1));
1895
1896            let properties = vec![
1897                Property::new_atom(
1898                    PropertyKind::Allocated,
1899                    vec![target.clone(), ty.clone(), count.clone()],
1900                ),
1901                Property::new_atom(
1902                    PropertyKind::InBound,
1903                    vec![target.clone(), ty.clone(), count.clone()],
1904                ),
1905                Property::new_atom(PropertyKind::Align, vec![target.clone(), ty.clone()]),
1906                Property::new_atom(PropertyKind::Init, vec![target, ty, count]),
1907            ];
1908
1909            (
1910                Checkpoint {
1911                    caller: def_id,
1912                    callee: None,
1913                    block: info.block,
1914                    args: vec![info.ptr_operand],
1915                    kind: crate::helpers::mir_scan::CheckpointKind::StaticMutAccess,
1916                    destination: None,
1917                    is_mut_ref: false,
1918                    statement_index: 0,
1919                },
1920                properties,
1921            )
1922        })
1923        .collect()
1924}
1925
1926fn is_drop_impl(tcx: TyCtxt<'_>, fn_did: DefId) -> bool {
1927    let Some(impl_id) = tcx.trait_impl_of_assoc(fn_did) else {
1928        return false;
1929    };
1930    let trait_did = tcx.impl_trait_id(impl_id);
1931    tcx.is_lang_item(trait_did, LangItem::Drop)
1932}