rapx/verify/
target.rs

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