1use crate::analysis::Analysis;
2use crate::analysis::safety_flow::root::{
3 function_has_struct_invariant, function_has_trait_ensurance, hir_contains_unsafe,
4};
5use crate::cli::VerifyMode;
6use crate::helpers::mir_scan::{collect_raw_ptr_deref_info, collect_static_mut_access_info};
7use crate::helpers::name::short_fn_name;
8use rustc_hir::{
9 Attribute, BodyId, FnDecl, ItemKind,
10 def_id::{DefId, LocalDefId},
11 intravisit::{FnKind, Visitor},
12};
13#[cfg(not(rapx_ge_100))]
14use rustc_hir::LangItem;
15#[cfg(rapx_ge_100)]
16use rustc_hir::attrs::lang_items::LangItem;
17use rustc_middle::{hir::nested_filter, ty::TyCtxt};
18use rustc_span::Span;
19use std::collections::{HashMap, HashSet};
20use crate::compat::FxHashMap;
21
22use super::{
23 contract::{
24 ContractExpr, ContractPlace, PlaceBase, Property, PropertyArg, PropertyKind,
25 attr::parse_rapx_attr,
26 },
27 path_extractor::PathExtractor,
28};
29use crate::helpers::mir_utils::{
30 collect_return_block_indices, get_owner_struct_def_id, has_rapx_verify_attr,
31 is_std_crate_def_id, is_trait_unsafe, resolve_impl_self_ty_def_id,
32};
33use crate::helpers::mir_scan::{Checkpoint, collect_unsafe_callsites};
34
35pub type FnContracts<'tcx> = Vec<Property<'tcx>>;
37
38pub type StructInvariants<'tcx> = Vec<Property<'tcx>>;
40
41#[derive(Clone, Debug)]
69pub struct FunctionTarget<'tcx> {
70 pub def_id: DefId,
72
73 pub owner_struct_def_id: Option<DefId>,
79
80 pub checkpoints: Vec<Checkpoint<'tcx>>,
85
86 pub callee_requires: HashMap<DefId, FnContracts<'tcx>>,
94
95 pub caller_requires: FnContracts<'tcx>,
102
103 pub struct_invariants: Vec<Property<'tcx>>,
109
110 pub raw_ptr_deref_checks: Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)>,
119
120 pub static_mut_checks: Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)>,
129}
130
131impl<'tcx> FunctionTarget<'tcx> {
132 pub fn all_checkpoints(&self) -> Vec<&Checkpoint<'tcx>> {
133 self.checkpoints
134 .iter()
135 .chain(
136 self.raw_ptr_deref_checks
137 .iter()
138 .map(|(checkpoint, _)| checkpoint),
139 )
140 .chain(
141 self.static_mut_checks
142 .iter()
143 .map(|(checkpoint, _)| checkpoint),
144 )
145 .collect()
146 }
147
148 pub fn properties_for_callsite(&self, checkpoint: &Checkpoint<'tcx>) -> &[Property<'tcx>] {
149 let loc = checkpoint.location();
150 match checkpoint.kind {
151 crate::helpers::mir_scan::CheckpointKind::RawPtrDeref => self
152 .raw_ptr_deref_checks
153 .iter()
154 .find(|(candidate, _)| candidate.location() == loc)
155 .map(|(_, properties)| properties.as_slice())
156 .unwrap_or(&[]),
157 crate::helpers::mir_scan::CheckpointKind::StaticMutAccess => self
158 .static_mut_checks
159 .iter()
160 .find(|(candidate, _)| candidate.location() == loc)
161 .map(|(_, properties)| properties.as_slice())
162 .unwrap_or(&[]),
163 crate::helpers::mir_scan::CheckpointKind::UnsafeCall => checkpoint
164 .callee
165 .and_then(|callee| self.callee_requires.get(&callee))
166 .map(Vec::as_slice)
167 .unwrap_or(&[]),
168 }
169 }
170}
171
172pub struct StructTarget<'tcx> {
174 pub def_id: DefId,
176 pub invariants: StructInvariants<'tcx>,
178 pub function_targets: Vec<FunctionTarget<'tcx>>,
180}
181
182pub struct TraitEnsurance<'tcx> {
187 pub def_id: DefId,
189 pub self_ty_def_id: Option<DefId>,
191 pub ensures: Vec<(String, FnContracts<'tcx>)>,
193}
194
195fn resolve_chain_contracts<'tcx>(
204 tcx: TyCtxt<'tcx>,
205 callee_def_id: DefId,
206 visited: &mut HashSet<DefId>,
207) -> FnContracts<'tcx> {
208 if !visited.insert(callee_def_id) {
209 return Vec::new();
210 }
211
212 if !tcx.is_mir_available(callee_def_id) {
213 return Vec::new();
214 }
215
216 let body = tcx.optimized_mir(callee_def_id);
217 let mut contracts = Vec::new();
218
219 for bb in body.basic_blocks.iter() {
220 let Some(terminator) = &bb.terminator else {
221 continue;
222 };
223 if let rustc_middle::mir::TerminatorKind::Call { func, .. } = &terminator.kind {
224 if let rustc_middle::mir::Operand::Constant(c) = func {
225 let rustc_middle::ty::TyKind::FnDef(sub_def_id, _) = c.const_.ty().kind() else {
226 continue;
227 };
228 let sub_def_id = *sub_def_id;
229
230 let fn_sig = tcx.fn_sig(sub_def_id).skip_binder();
231 if fn_sig.safety() != rustc_hir::Safety::Unsafe {
232 continue;
233 }
234
235 let mut reqs = get_contract_from_annotation(tcx, sub_def_id);
237
238 if reqs.is_empty() {
240 reqs = get_trait_method_requires(tcx, sub_def_id);
241 }
242
243 if reqs.is_empty() && is_std_crate_def_id(tcx, sub_def_id) {
245 reqs = super::contract::query_json_contracts(tcx, sub_def_id);
246 }
247
248 if reqs.is_empty() {
250 reqs = resolve_chain_contracts(tcx, sub_def_id, visited);
251 }
252
253 contracts.extend(reqs);
254 }
255 }
256 }
257
258 contracts
259}
260
261pub struct VerifyTargetCollector<'tcx> {
263 tcx: TyCtxt<'tcx>,
264 mode: VerifyMode,
265 skip_invariant: bool,
266 crate_filter: Option<String>,
267 crate_filter_matched: bool,
268 module_filter: Option<String>,
269 module_filter_matched: bool,
270 pub function_targets: Vec<FunctionTarget<'tcx>>,
272 pub struct_targets: HashMap<DefId, StructTarget<'tcx>>,
274 pub trait_targets: HashMap<DefId, TraitEnsurance<'tcx>>,
276 fn_contract_cache: HashMap<DefId, FnContracts<'tcx>>,
278}
279
280impl<'tcx> VerifyTargetCollector<'tcx> {
281 pub fn collect_all(
284 tcx: TyCtxt<'tcx>,
285 mode: VerifyMode,
286 skip_invariant: bool,
287 crate_filter: Option<String>,
288 module_filter: Option<String>,
289 ) -> Self {
290 let mut collector = Self::new(tcx, mode, skip_invariant, crate_filter.clone(), module_filter);
291 tcx.hir_visit_all_item_likes_in_crate(&mut collector);
292 if crate_filter.is_some() {
293 collector.collect_extern_crate_targets();
294 }
295 collector.check_module_filter_result();
296 collector
297 }
298
299 pub fn new(
301 tcx: TyCtxt<'tcx>,
302 mode: VerifyMode,
303 skip_invariant: bool,
304 crate_filter: Option<String>,
305 module_filter: Option<String>,
306 ) -> Self {
307 VerifyTargetCollector {
308 tcx,
309 mode,
310 skip_invariant,
311 crate_filter,
312 crate_filter_matched: false,
313 module_filter,
314 module_filter_matched: false,
315 function_targets: Vec::new(),
316 struct_targets: HashMap::new(),
317 trait_targets: HashMap::new(),
318 fn_contract_cache: HashMap::new(),
319 }
320 }
321
322 fn get_fn_contracts(&mut self, callee_def_id: DefId) -> FnContracts<'tcx> {
333 let is_std = is_std_crate_def_id(self.tcx, callee_def_id);
334
335 let trait_requires = get_trait_method_requires(self.tcx, callee_def_id);
336
337 self.fn_contract_cache
338 .entry(callee_def_id)
339 .or_insert_with(|| {
340 let mut requires = get_contract_from_annotation(self.tcx, callee_def_id);
341
342 if requires.is_empty() && !trait_requires.is_empty() {
343 requires = trait_requires.clone();
344 }
345
346 if requires.is_empty() && is_std {
347 requires = super::contract::query_json_contracts(
348 self.tcx,
349 callee_def_id,
350 );
351
352 if requires.is_empty() {
353 let mut visited = HashSet::new();
357 requires = resolve_chain_contracts(
358 self.tcx,
359 callee_def_id,
360 &mut visited,
361 );
362 if requires.is_empty() {
363 let path = crate::helpers::name::get_cleaned_def_path_name(
364 self.tcx,
365 callee_def_id,
366 );
367 rap_warn!(
368 "no safety contracts found for callee \"{path}\""
369 );
370 } else {
371 let path = crate::helpers::name::get_cleaned_def_path_name(
372 self.tcx,
373 callee_def_id,
374 );
375 rap_debug!(
376 "resolved {} safety contract(s) for std callee \"{path}\" via call chain",
377 requires.len()
378 );
379 }
380 }
381 }
382
383 if requires.is_empty() {
384 requires.push(Property::new(
385 self.tcx,
386 callee_def_id,
387 "Unknown",
388 &[],
389 ));
390 }
391
392 requires
393 })
394 .clone()
395 }
396
397 fn build_function_target(&mut self, def_id: DefId) -> FunctionTarget<'tcx> {
399 let checkpoints = collect_unsafe_callsites(self.tcx, def_id);
400 let unsafe_callees: HashSet<_> = checkpoints
401 .iter()
402 .filter_map(|checkpoint| checkpoint.callee)
403 .collect();
404 let callee_requires = unsafe_callees
405 .iter()
406 .map(|callee_def_id| {
407 let mut contracts = self.get_fn_contracts(*callee_def_id);
408 contracts
409 .retain(|p| {
410 !matches!(
411 p.kind(),
412 Some(crate::verify::contract::PropertyKind::Unknown)
413 )
414 });
415 (*callee_def_id, contracts)
416 })
417 .collect();
418
419 let mut caller_requires = self.get_fn_contracts(def_id);
420 if is_std_crate_def_id(self.tcx, def_id) {
426 let json_contracts =
427 super::contract::query_json_contracts(self.tcx, def_id);
428 caller_requires.extend(json_contracts);
429 }
430
431 let raw_ptr_deref_checks = build_raw_ptr_deref_checks(self.tcx, def_id);
432 let static_mut_checks = build_static_mut_checks(self.tcx, def_id);
433
434 let owner_struct_def_id = get_owner_struct_def_id(self.tcx, def_id);
435 let mut struct_invariants = owner_struct_def_id
436 .map(|struct_def_id| {
437 get_struct_invariants_from_annotation(self.tcx, struct_def_id, def_id)
438 })
439 .unwrap_or_default();
440
441 caller_requires.extend(struct_invariants.clone());
445
446 if is_drop_impl(self.tcx, def_id) {
449 struct_invariants.clear();
450 }
451
452 let type_invariants = build_type_invariants_from_params(self.tcx, def_id);
456 caller_requires.extend(type_invariants);
457
458 FunctionTarget {
459 def_id,
460 owner_struct_def_id,
461 checkpoints,
462 callee_requires,
463 caller_requires,
464 struct_invariants,
465 raw_ptr_deref_checks,
466 static_mut_checks,
467 }
468 }
469
470 fn push_function_target(&mut self, function_target: FunctionTarget<'tcx>) {
472 self.function_targets.push(function_target.clone());
473
474 if let Some(struct_def_id) = function_target.owner_struct_def_id {
475 self.struct_targets
476 .entry(struct_def_id)
477 .or_insert_with(|| StructTarget {
478 def_id: struct_def_id,
479 invariants: get_struct_invariants_from_annotation(
480 self.tcx,
481 struct_def_id,
482 function_target.def_id,
483 ),
484 function_targets: Vec::new(),
485 })
486 .function_targets
487 .push(function_target);
488 }
489 }
490
491 fn collect_extern_crate_targets(&mut self) {
498 let local_crate = rustc_hir::def_id::LOCAL_CRATE;
499
500 for def_id in self.tcx.mir_keys(()) {
501 let def_id = def_id.to_def_id();
502 if def_id.krate == local_crate {
503 continue; }
505 if !self.crate_name_matches(def_id) {
506 continue;
507 }
508 let def_kind = self.tcx.def_kind(def_id);
509 if !matches!(
510 def_kind,
511 rustc_hir::def::DefKind::Fn | rustc_hir::def::DefKind::AssocFn
512 ) {
513 continue;
514 }
515
516 if matches!(self.mode, VerifyMode::Targeted) {
519 continue;
520 }
521
522 self.crate_filter_matched = true;
523
524 if !self.module_path_matches(def_id) {
525 continue;
526 }
527 self.module_filter_matched = true;
528
529 let function_target = self.build_function_target(def_id);
530 self.push_function_target(function_target);
531 }
532 }
533
534 fn crate_name_matches(&self, def_id: DefId) -> bool {
535 match self.crate_filter {
536 None => true,
537 Some(ref filter) => {
538 let crate_name = self.tcx.crate_name(def_id.krate);
539 if crate_name.as_str() == *filter {
540 return true;
541 }
542 if let Ok(pkg_name) = std::env::var("CARGO_PKG_NAME") {
543 if pkg_name == *filter {
544 return true;
545 }
546 }
547 false
548 }
549 }
550 }
551
552 fn module_path_matches(&self, def_id: DefId) -> bool {
553 let Some(ref filter) = self.module_filter else {
554 return true;
555 };
556 let def_path = self.tcx.def_path_str(def_id);
557
558 if def_path == *filter || def_path.starts_with(&format!("{}::", filter)) {
559 return true;
560 }
561 let crate_name = self.tcx.crate_name(def_id.krate);
562 let crate_prefix = format!("{}::", crate_name.as_str());
563
564 if let Some(inner) = filter.strip_prefix(&crate_prefix) {
568 if def_path == inner || def_path.starts_with(&format!("{}::", inner)) {
569 return true;
570 }
571 }
572
573 if let Some(inner) = def_path.strip_prefix(&crate_prefix) {
577 if inner == *filter || inner.starts_with(&format!("{}::", filter)) {
578 return true;
579 }
580 }
581
582 false
583 }
584
585 pub fn check_module_filter_result(&self) {
586 if let Some(ref filter) = self.crate_filter {
587 if !self.crate_filter_matched {
588 rap_warn!("[rapx::verify] --crate \"{filter}\" matched no targets");
589 }
590 }
591 if let Some(ref filter) = self.module_filter {
592 if !self.module_filter_matched {
593 rap_warn!("[rapx::verify] --module \"{filter}\" matched no functions in the crate");
594 }
595 }
596 }
597}
598
599fn get_trait_method_requires<'tcx>(tcx: TyCtxt<'tcx>, callee_def_id: DefId) -> FnContracts<'tcx> {
600 let Some(assoc_item) = tcx.opt_associated_item(callee_def_id) else {
601 return Vec::new();
602 };
603 let Some(trait_item_def_id) = assoc_item.trait_item_def_id() else {
604 return Vec::new();
605 };
606 get_contract_from_annotation(tcx, trait_item_def_id)
607}
608
609impl<'tcx> Visitor<'tcx> for VerifyTargetCollector<'tcx> {
610 type NestedFilter = nested_filter::OnlyBodies;
611
612 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
613 self.tcx
614 }
615
616 fn visit_item(&mut self, item: &'tcx rustc_hir::Item<'tcx>) {
623 if let ItemKind::Impl(rustc_hir::Impl { of_trait, .. }) = &item.kind
624 && of_trait.is_some()
625 {
626 if matches!(self.mode, VerifyMode::Targeted)
627 && !has_rapx_verify_attr(self.tcx, item.owner_id.def_id)
628 {
629 rustc_hir::intravisit::walk_item(self, item);
630 return;
631 }
632
633 let impl_def_id = item.owner_id.to_def_id();
634
635 if !self.crate_name_matches(impl_def_id) {
636 rustc_hir::intravisit::walk_item(self, item);
637 return;
638 }
639 self.crate_filter_matched = true;
640
641 if !self.module_path_matches(impl_def_id) {
642 rustc_hir::intravisit::walk_item(self, item);
643 return;
644 }
645 self.module_filter_matched = true;
646
647 let trait_ref = {
648 self.tcx.impl_opt_trait_ref(impl_def_id)
649 };
650
651 if let Some(trait_ref) = trait_ref {
652 let trait_def_id = trait_ref.skip_binder().def_id;
653 if is_trait_unsafe(self.tcx, trait_def_id) {
654 let ensures = get_trait_contracts_from_annotation(self.tcx, trait_def_id);
655
656 let self_ty_def_id = resolve_impl_self_ty_def_id(&item);
657
658 self.trait_targets
659 .entry(trait_def_id)
660 .or_insert_with(|| TraitEnsurance {
661 def_id: trait_def_id,
662 self_ty_def_id,
663 ensures,
664 });
665 }
666 }
667 }
668
669 rustc_hir::intravisit::walk_item(self, item);
670 }
671
672 fn visit_fn(
679 &mut self,
680 _fk: FnKind<'tcx>,
681 _fd: &'tcx FnDecl<'tcx>,
682 body_id: BodyId,
683 _span: Span,
684 id: LocalDefId,
685 ) -> Self::Result {
686 if matches!(self.mode, VerifyMode::Targeted) && !has_rapx_verify_attr(self.tcx, id) {
687 if !is_drop_impl(self.tcx, id.to_def_id()) {
689 return;
690 }
691 }
692
693 let def_id = id.to_def_id();
698
699 if let rustc_hir::def::DefKind::Fn = self.tcx.def_kind(def_id) {
702 let fn_sig = self.tcx.fn_sig(def_id).skip_binder();
703 if matches!(
704 fn_sig.output().skip_binder().kind(),
705 rustc_type_ir::TyKind::Never
706 ) {
707 return;
708 }
709 }
710
711 if !matches!(self.mode, VerifyMode::Targeted) {
712 if !hir_contains_unsafe(self.tcx, body_id)
713 && !function_has_struct_invariant(self.tcx, def_id)
714 && !function_has_trait_ensurance(self.tcx, def_id)
715 {
716 return;
717 }
718 }
719
720 let function_target = self.build_function_target(def_id);
721
722 match self.mode {
723 VerifyMode::Targeted => {}
724 VerifyMode::Scan => {
725 if function_target.checkpoints.is_empty()
726 && function_target.raw_ptr_deref_checks.is_empty()
727 && function_target.static_mut_checks.is_empty()
728 {
729 if !function_target.struct_invariants.is_empty() {
730 if self.skip_invariant {
731 return;
732 }
733 } else {
734 let root =
735 crate::analysis::safety_flow::root::scan_mir(self.tcx, def_id);
736 if root.is_none() {
737 return;
738 }
739 }
740 }
741 }
742 }
743
744 if !self.crate_name_matches(def_id) {
745 return;
746 }
747 self.crate_filter_matched = true;
748
749 if !self.module_path_matches(def_id) {
750 return;
751 }
752 self.module_filter_matched = true;
753
754 self.push_function_target(function_target);
755 }
756}
757
758pub struct PrepareTargets<'tcx> {
763 tcx: TyCtxt<'tcx>,
764 mode: VerifyMode,
765 skip_invariant: bool,
766 crate_filter: Option<String>,
767 module_filter: Option<String>,
768}
769
770impl<'tcx> Analysis for PrepareTargets<'tcx> {
771 fn run(&mut self) {
772 let collector = VerifyTargetCollector::collect_all(
773 self.tcx,
774 self.mode,
775 self.skip_invariant,
776 self.crate_filter.clone(),
777 self.module_filter.clone(),
778 );
779
780 let free_targets: Vec<_> = collector
782 .function_targets
783 .iter()
784 .filter(|target| target.owner_struct_def_id.is_none())
785 .collect();
786 for target in &free_targets {
787 let target_path = self.tcx.def_path_str(target.def_id);
788 rap_info!("============================================================");
789 rap_info!(
790 "[rapx::verify] prepare targets for free function: {}",
791 target_path
792 );
793 rap_info!("============================================================");
794 self.log_free_function_unsafe_callees(target);
795 rap_info!("");
796 }
797
798 let mut struct_ids: Vec<_> = collector.struct_targets.keys().copied().collect();
800 struct_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
801
802 for struct_def_id in struct_ids {
803 let Some(struct_target) = collector.struct_targets.get(&struct_def_id) else {
804 continue;
805 };
806 let struct_path = self.tcx.def_path_str(struct_target.def_id);
807
808 rap_info!("============================================================");
809 rap_info!("[rapx::verify] prepare targets for struct: {}", struct_path);
810 rap_info!("============================================================");
811
812 self.log_struct_invariants(struct_target);
813
814 for target in &struct_target.function_targets {
815 self.log_method_target(target);
816 }
817 }
818
819 let mut trait_ids: Vec<_> = collector.trait_targets.keys().copied().collect();
821 trait_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
822
823 for trait_def_id in trait_ids {
824 let Some(trait_target) = collector.trait_targets.get(&trait_def_id) else {
825 continue;
826 };
827 let trait_path = self.tcx.def_path_str(trait_target.def_id);
828
829 rap_info!("============================================================");
830 rap_info!(
831 "[rapx::verify] prepare targets for unsafe trait: {}",
832 trait_path
833 );
834 rap_info!("============================================================");
835
836 self.log_trait_ensurance(trait_target);
837
838 rap_info!("");
839 }
840
841 let total_free = free_targets.len();
842 let total_method = collector
843 .function_targets
844 .iter()
845 .filter(|target| target.owner_struct_def_id.is_some())
846 .count();
847 let total_struct = collector.struct_targets.len();
848 let total_trait = collector.trait_targets.len();
849
850 rap_info!("============================================================");
851 rap_info!(
852 "[rapx::verify] total: {} free function(s), {} method(s), {} struct(s), {} trait(s)",
853 total_free,
854 total_method,
855 total_struct,
856 total_trait
857 );
858 rap_info!("============================================================");
859 }
860
861}
862
863impl<'tcx> PrepareTargets<'tcx> {
864 pub fn new(
865 tcx: TyCtxt<'tcx>,
866 mode: VerifyMode,
867 skip_invariant: bool,
868 crate_filter: Option<String>,
869 module_filter: Option<String>,
870 ) -> Self {
871 PrepareTargets {
872 tcx,
873 mode,
874 skip_invariant,
875 crate_filter,
876 module_filter,
877 }
878 }
879
880 fn log_struct_invariants(&self, struct_target: &StructTarget<'tcx>) {
881 if struct_target.invariants.is_empty() {
882 rap_info!(" struct invariants: <none>");
883 } else {
884 rap_info!(" struct invariants:");
885 for property in crate::verify::display::dedup_compound_props(struct_target.invariants.iter()) {
886 rap_info!(
887 " - {}",
888 property.display_for_report(self.tcx, Some(struct_target.def_id), None,)
889 );
890 }
891 }
892 }
893
894 fn log_trait_ensurance(&self, trait_target: &TraitEnsurance<'tcx>) {
895 if let Some(self_ty) = trait_target.self_ty_def_id {
896 rap_info!(" impl for: {}", self.tcx.def_path_str(self_ty));
897 }
898 if trait_target.ensures.is_empty() {
899 rap_info!(" ensures: <none>");
900 } else {
901 rap_info!(" ensures (implementor must satisfy):");
902 for (method_name, contracts) in &trait_target.ensures {
903 rap_info!(" fn {}:", method_name);
904 for property in crate::verify::display::dedup_compound_props(contracts.iter()) {
905 rap_info!(
906 " - {}",
907 property.display_for_report(self.tcx, trait_target.self_ty_def_id, None,)
908 );
909 }
910 }
911 }
912 }
913
914 fn log_method_target(&self, target: &FunctionTarget<'tcx>) {
915 let name = short_fn_name(self.tcx, target.def_id);
916 let dashes = 62usize.saturating_sub(10 + name.len());
917 rap_info!(" --- method: {name} {}", "-".repeat(dashes));
918
919 let return_blocks = collect_return_block_indices(self.tcx, target.def_id);
920 rap_info!(
921 " return checkpoints: {} block(s) {:?}",
922 return_blocks.len(),
923 return_blocks
924 .iter()
925 .map(|bb| bb.as_usize())
926 .collect::<Vec<_>>()
927 );
928
929 let path_map = self.build_checkpoint_path_map(target);
930 self.log_unsafe_callees_and_contracts(target, &path_map);
931 }
932
933 fn log_free_function_unsafe_callees(&self, target: &FunctionTarget<'tcx>) {
934 let path_map = self.build_checkpoint_path_map(target);
935 self.log_unsafe_callees_and_contracts(target, &path_map);
936 }
937
938 fn log_unsafe_callees_and_contracts(
939 &self,
940 target: &FunctionTarget<'tcx>,
941 path_map: &FxHashMap<DefId, Vec<(usize, Vec<String>)>>,
942 ) {
943 if target.callee_requires.is_empty() {
944 rap_info!(" unsafe checkpoints: <none>");
945 return;
946 }
947
948 let mut unsafe_callee_ids: Vec<_> = target.callee_requires.keys().copied().collect();
949 unsafe_callee_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
950
951 for unsafe_callee_def_id in unsafe_callee_ids {
952 let fn_sig = self.tcx.fn_sig(unsafe_callee_def_id).skip_binder();
953 let unsafe_callee_path = self.tcx.def_path_str(unsafe_callee_def_id);
954 let inputs: Vec<String> = fn_sig
955 .inputs()
956 .skip_binder()
957 .iter()
958 .map(|ty| format!("{}", ty))
959 .collect();
960 let output = format!("{}", fn_sig.output().skip_binder());
961 rap_info!(
962 " unsafe callee: {}({}) -> {}",
963 unsafe_callee_path,
964 inputs.join(", "),
965 output,
966 );
967
968 if let Some(requires) = target.callee_requires.get(&unsafe_callee_def_id) {
969 if requires.is_empty() {
970 rap_info!(" safety contracts: <none>");
971 } else {
972 rap_info!(" safety contracts:");
973 for property in crate::verify::display::dedup_compound_props(requires.iter()) {
974 rap_info!(
975 " - {}",
976 property.display_for_report(
977 self.tcx,
978 target.owner_struct_def_id,
979 Some(unsafe_callee_def_id),
980 )
981 );
982 }
983 }
984 }
985
986 if let Some(path_entries) = path_map.get(&unsafe_callee_def_id) {
987 for (_block_idx, path_strings) in path_entries {
988 if path_strings.is_empty() {
989 rap_info!(" path: <none>");
990 } else {
991 for desc in path_strings {
992 rap_info!(" path: shortest path: {desc}");
993 }
994 }
995 }
996 }
997 }
998 }
999
1000 fn build_checkpoint_path_map(
1001 &self,
1002 target: &FunctionTarget<'tcx>,
1003 ) -> FxHashMap<DefId, Vec<(usize, Vec<String>)>> {
1004 let mut path_map: FxHashMap<DefId, Vec<(usize, Vec<String>)>> = FxHashMap::default();
1005
1006 if target.checkpoints.is_empty() {
1007 return path_map;
1008 }
1009
1010 let groups =
1011 PathExtractor::new(self.tcx, target.def_id, target.checkpoints.clone(), 0).run();
1012
1013 for group in &groups {
1014 for checkpoint in &group.checkpoints {
1015 if let Some(callee_def_id) = checkpoint.callee {
1016 let block_idx = checkpoint.block.as_usize();
1017 let mut path_strings: Vec<String> = Vec::new();
1018 let _ = group.tree.walk_prefixes(
1019 checkpoint.block.as_usize(),
1020 &mut |prefix: &[usize]| -> bool {
1021 let desc = prefix
1022 .iter()
1023 .map(usize::to_string)
1024 .collect::<Vec<_>>()
1025 .join(" -> ");
1026 path_strings.push(desc);
1027 true
1028 },
1029 );
1030
1031 path_map
1032 .entry(callee_def_id)
1033 .or_insert_with(Vec::new)
1034 .push((block_idx, path_strings));
1035 }
1036 }
1037 }
1038
1039 path_map
1040 }
1041}
1042
1043fn is_rapx_named_attr(attr: &Attribute, name: &str) -> bool {
1044 let path = attr.path();
1045 if path.len() >= 2
1046 && path[path.len() - 2].as_str() == "rapx"
1047 && path[path.len() - 1].as_str() == name
1048 {
1049 return true;
1050 }
1051 path.len() == 1 && path[0].as_str() == name
1054}
1055
1056fn collect_properties_from_named_attrs<'tcx>(
1057 tcx: TyCtxt<'tcx>,
1058 attrs: impl IntoIterator<Item = &'tcx Attribute>,
1059 property_def_id: DefId,
1060 parse_error_label: &str,
1061 attr_name: &str,
1062) -> Vec<Property<'tcx>> {
1063 let mut results = Vec::new();
1064
1065 for attr in attrs {
1066 if !is_rapx_named_attr(attr, attr_name) {
1067 continue;
1068 }
1069
1070 let attr_str = crate::compat::attribute_to_string(tcx, attr);
1071 let parsed = match parse_rapx_attr(attr_str.as_str(), attr_name) {
1072 Ok(parsed) => parsed,
1073 Err(err) => {
1074 rap_error!(
1075 "Failed to parse RAPx {} attr '{}': {}",
1076 parse_error_label,
1077 attr_str,
1078 err
1079 );
1080 continue;
1081 }
1082 };
1083
1084 let Some(property) = parsed else { continue };
1085 results.extend(
1086 Property::parse_list(tcx, property_def_id, property.tag.as_str(), &property.args)
1087 .into_iter()
1088 .map(move |mut p| {
1089 p.apply_kind(property.kind.as_deref());
1090 p
1091 }),
1092 );
1093 }
1094
1095 results
1096}
1097
1098pub(crate) fn get_contract_from_annotation<'tcx>(
1100 tcx: TyCtxt<'tcx>,
1101 def_id: DefId,
1102) -> FnContracts<'tcx> {
1103 if let Some(local_def_id) = def_id.as_local() {
1106 let hir_id = tcx.local_def_id_to_hir_id(local_def_id);
1107 let hir_attrs = tcx.hir_attrs(hir_id);
1108 return collect_properties_from_named_attrs(tcx, hir_attrs, def_id, "requires", "requires");
1110 }
1111
1112 let attrs = crate::compat::get_all_attrs(tcx, def_id);
1113 collect_properties_from_named_attrs(tcx, attrs, def_id, "requires", "requires")
1114}
1115
1116fn get_struct_invariants_from_annotation<'tcx>(
1118 tcx: TyCtxt<'tcx>,
1119 struct_def_id: DefId,
1120 context_def_id: DefId,
1121) -> StructInvariants<'tcx> {
1122 let Some(local_def_id) = struct_def_id.as_local() else {
1123 return Vec::new();
1124 };
1125
1126 let item = tcx.hir_expect_item(local_def_id);
1127 if !matches!(item.kind, ItemKind::Struct(..)) {
1128 return Vec::new();
1129 }
1130
1131 let mut invariants = collect_properties_from_named_attrs(
1132 tcx,
1133 {
1134 crate::compat::get_all_attrs(tcx, struct_def_id)
1135 },
1136 context_def_id,
1137 "invariant",
1138 "requires",
1139 );
1140 invariants.extend(collect_properties_from_named_attrs(
1141 tcx,
1142 {
1143 crate::compat::get_all_attrs(tcx, struct_def_id)
1144 },
1145 context_def_id,
1146 "invariant",
1147 "invariant",
1148 ));
1149 invariants
1150}
1151
1152fn get_trait_contracts_from_annotation<'tcx>(
1155 tcx: TyCtxt<'tcx>,
1156 trait_def_id: DefId,
1157) -> Vec<(String, FnContracts<'tcx>)> {
1158 let Some(local_id) = trait_def_id.as_local() else {
1159 return Vec::new();
1160 };
1161
1162 let item = tcx.hir_expect_item(local_id);
1163
1164 let trait_items = {
1165 #[cfg(not(rapx_ge_99))]
1166 if let ItemKind::Trait(.., items) = &item.kind {
1167 items
1168 } else {
1169 return Vec::new();
1170 }
1171 #[cfg(rapx_ge_99)]
1172 if let ItemKind::Trait { items, .. } = &item.kind {
1173 items
1174 } else {
1175 return Vec::new();
1176 }
1177 };
1178
1179 let mut ensures: Vec<(String, FnContracts<'tcx>)> = Vec::new();
1180
1181 for trait_item_id in trait_items.iter() {
1182 let trait_item_def_id = trait_item_id.owner_id.to_def_id();
1183 let method_name = tcx.def_path_str(trait_item_def_id);
1184 let attrs = crate::compat::get_all_attrs(tcx, trait_item_def_id);
1185
1186 let method_ensures =
1187 collect_properties_from_named_attrs(tcx, attrs, trait_item_def_id, "trait ensures", "ensures");
1188
1189 if !method_ensures.is_empty() {
1190 ensures.push((method_name, method_ensures));
1191 }
1192 }
1193
1194 ensures
1195}
1196
1197fn build_raw_ptr_deref_checks<'tcx>(
1200 tcx: TyCtxt<'tcx>,
1201 def_id: DefId,
1202) -> Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)> {
1203 let infos = collect_raw_ptr_deref_info(tcx, def_id);
1204 if infos.is_empty() {
1205 return Vec::new();
1206 }
1207
1208 infos
1209 .into_iter()
1210 .map(|info| {
1211 let target = PropertyArg::Expr(ContractExpr::Place(ContractPlace {
1212 base: PlaceBase::Arg(0),
1213 projections: vec![],
1214 }));
1215 let ty = PropertyArg::Ty(info.pointee_ty);
1216 let count = PropertyArg::Expr(ContractExpr::Const(1));
1217
1218 let mut properties = if info.is_ref {
1219 vec![
1220 Property::new_leaf(PropertyKind::NonNull, vec![target.clone()]),
1221 Property::new_leaf(PropertyKind::Align, vec![target.clone(), ty.clone()]),
1222 { let mut p = Property::new_leaf(PropertyKind::Alias, vec![target.clone()]); p.set_contract_kind(crate::verify::contract::ContractKind::Hazard); p },
1223 ]
1224 } else {
1225 vec![
1226 Property::new_leaf(PropertyKind::Allocated, vec![target.clone(), ty.clone(), count.clone()]),
1227 Property::new_leaf(PropertyKind::InBound, vec![target.clone(), ty.clone(), count.clone()]),
1228 Property::new_leaf(PropertyKind::Align, vec![target.clone(), ty.clone()]),
1229 ]
1230 };
1231
1232 if info.is_read && !info.is_ref {
1233 properties.push(Property::new_leaf(PropertyKind::Typed, vec![target, ty]));
1234 }
1235
1236 (
1237 Checkpoint {
1238 caller: def_id,
1239 callee: None,
1240 block: info.block,
1241 span: rustc_span::DUMMY_SP,
1242 args: vec![info.ptr_operand],
1243 kind: crate::helpers::mir_scan::CheckpointKind::RawPtrDeref,
1244 is_ref: info.is_ref,
1245 is_mut_ref: info.is_mut_ref,
1246 destination: Some(info.destination),
1247 },
1248 properties,
1249 )
1250 })
1251 .collect()
1252}
1253
1254fn build_static_mut_checks<'tcx>(
1257 tcx: TyCtxt<'tcx>,
1258 def_id: DefId,
1259) -> Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)> {
1260 let infos = collect_static_mut_access_info(tcx, def_id);
1261 if infos.is_empty() {
1262 return Vec::new();
1263 }
1264
1265 infos
1266 .into_iter()
1267 .map(|info| {
1268 let target = PropertyArg::Expr(ContractExpr::Place(ContractPlace {
1269 base: PlaceBase::Arg(0),
1270 projections: vec![],
1271 }));
1272 let ty = PropertyArg::Ty(info.ty);
1273 let count = PropertyArg::Expr(ContractExpr::Const(1));
1274
1275 let properties = vec![
1276 Property::new_leaf(PropertyKind::Allocated, vec![target.clone(), ty.clone(), count.clone()]),
1277 Property::new_leaf(PropertyKind::InBound, vec![target.clone(), ty.clone(), count.clone()]),
1278 Property::new_leaf(PropertyKind::Align, vec![target.clone(), ty.clone()]),
1279 Property::new_leaf(PropertyKind::Init, vec![target, ty, count]),
1280 ];
1281
1282 (
1283 Checkpoint {
1284 caller: def_id,
1285 callee: None,
1286 block: info.block,
1287 span: rustc_span::DUMMY_SP,
1288 args: vec![info.ptr_operand],
1289 kind: crate::helpers::mir_scan::CheckpointKind::StaticMutAccess,
1290 is_ref: false,
1291 is_mut_ref: false,
1292 destination: None,
1293 },
1294 properties,
1295 )
1296 })
1297 .collect()
1298}
1299
1300fn build_type_invariants_from_params<'tcx>(
1303 tcx: TyCtxt<'tcx>,
1304 def_id: DefId,
1305) -> Vec<Property<'tcx>> {
1306 let db = crate::verify::contract::assets::get_std_type_invariants();
1307 if db.is_empty() {
1308 return Vec::new();
1309 }
1310
1311 let fn_sig = tcx.fn_sig(def_id).skip_binder();
1312 let inputs = fn_sig.inputs().skip_binder();
1313 let output = fn_sig.output().skip_binder();
1314
1315 let mut results = Vec::new();
1316
1317 let (param_names, _param_tys) = crate::helpers::name::parse_signature(tcx, def_id);
1319
1320 for (index, ¶m_ty) in inputs.iter().enumerate() {
1322 if param_ty.is_primitive() {
1323 continue;
1324 }
1325 let param_name = param_names.get(index).cloned().unwrap_or_default();
1326 let type_path = type_path_key(tcx, param_ty);
1327 collect_type_invariants(tcx, def_id, &db, &type_path, ¶m_name, &mut results);
1328 }
1329
1330 if !output.is_unit() && !output.is_primitive() {
1332 let type_path = type_path_key(tcx, output);
1333 collect_type_invariants(tcx, def_id, &db, &type_path, "return", &mut results);
1334 }
1335
1336 results
1337}
1338
1339fn collect_type_invariants<'tcx>(
1341 tcx: TyCtxt<'tcx>,
1342 def_id: DefId,
1343 db: &std::collections::HashMap<String, crate::verify::contract::assets::TypeInvariantEntry>,
1344 type_path: &str,
1345 param_name: &str,
1346 results: &mut Vec<Property<'tcx>>,
1347) {
1348 if let Some(entry) = db.get(type_path) {
1349 for prop_entry in &entry.invariants {
1350 if let Some(property) = instantiate_type_invariant(tcx, def_id, prop_entry, param_name)
1351 {
1352 results.push(property);
1353 }
1354 }
1355 }
1356 for prefix in ["alloc::", "std::"] {
1358 let prefixed = format!("{prefix}{type_path}");
1359 if prefixed != type_path {
1360 if let Some(entry) = db.get(&prefixed) {
1361 for prop_entry in &entry.invariants {
1362 if let Some(property) =
1363 instantiate_type_invariant(tcx, def_id, prop_entry, param_name)
1364 {
1365 results.push(property);
1366 }
1367 }
1368 }
1369 }
1370 }
1371}
1372
1373fn instantiate_type_invariant<'tcx>(
1375 tcx: TyCtxt<'tcx>,
1376 def_id: DefId,
1377 entry: &crate::verify::contract::assets::PropertyEntry,
1378 param_name: &str,
1379) -> Option<Property<'tcx>> {
1380 let mut exprs: Vec<syn::Expr> = Vec::new();
1381 for arg_str in &entry.args {
1382 let substituted = arg_str.replace("$self", param_name);
1385 let resolved = if is_numeric_field_access(&substituted) {
1387 format!("{}.{}", param_name, substituted)
1388 } else {
1389 substituted
1390 };
1391 match syn::parse_str::<syn::Expr>(&resolved) {
1392 Ok(expr) => exprs.push(expr),
1393 Err(_) => {
1394 rap_debug!(
1395 " [type-invariant] failed to parse arg '{}' for tag {}",
1396 resolved,
1397 entry.tag
1398 );
1399 return None;
1400 }
1401 }
1402 }
1403 if exprs.is_empty() {
1404 return None;
1405 }
1406 let mut property = Property::new(tcx, def_id, &entry.tag, &exprs);
1407 property.apply_kind(entry.kind.as_deref());
1408 if !matches!(
1409 property.kind(),
1410 Some(crate::verify::contract::PropertyKind::Unknown)
1411 ) {
1412 Some(property)
1413 } else {
1414 None
1415 }
1416}
1417
1418fn is_numeric_field_access(s: &str) -> bool {
1420 let trimmed = s.trim();
1421 !trimmed.is_empty()
1422 && trimmed
1423 .split('.')
1424 .all(|part| !part.is_empty() && part.chars().all(|c| c.is_ascii_digit()))
1425}
1426
1427fn type_path_key<'tcx>(tcx: TyCtxt<'tcx>, ty: rustc_middle::ty::Ty<'tcx>) -> String {
1429 match ty.kind() {
1430 rustc_middle::ty::TyKind::Adt(adt_def, _) => {
1431 let path = tcx.def_path_str(adt_def.did());
1432 path
1433 }
1434 _ => format!("{ty:?}"),
1435 }
1436}
1437
1438fn is_drop_impl(tcx: TyCtxt<'_>, fn_did: DefId) -> bool {
1439 let Some(impl_id) = tcx.trait_impl_of_assoc(fn_did) else {
1440 return false;
1441 };
1442 let trait_did = tcx.impl_trait_id(impl_id);
1443 tcx.is_lang_item(trait_did, LangItem::Drop)
1444}