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
30pub type FnContracts<'tcx> = Vec<Property<'tcx>>;
32
33pub type StructInvariants<'tcx> = Vec<Property<'tcx>>;
35
36#[derive(Clone)]
64pub struct FunctionTarget<'tcx> {
65 pub def_id: DefId,
67
68 pub owner_struct_def_id: Option<DefId>,
74
75 pub checkpoints: Vec<Checkpoint<'tcx>>,
80
81 pub callee_requires: HashMap<DefId, FnContracts<'tcx>>,
89
90 pub caller_requires: FnContracts<'tcx>,
97
98 pub struct_invariants: Vec<Property<'tcx>>,
104
105 pub raw_ptr_deref_checks: Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)>,
114
115 pub static_mut_checks: Vec<(Checkpoint<'tcx>, Vec<Property<'tcx>>)>,
123}
124
125pub struct StructTarget<'tcx> {
127 pub def_id: DefId,
129 pub invariants: StructInvariants<'tcx>,
131 pub function_targets: Vec<FunctionTarget<'tcx>>,
133}
134
135pub struct TraitEnsurance<'tcx> {
140 pub def_id: DefId,
142 pub self_ty_def_id: Option<DefId>,
144 pub ensures: Vec<(String, FnContracts<'tcx>)>,
146}
147
148fn 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 let mut reqs = get_contract_from_annotation(tcx, sub_def_id);
191
192 if reqs.is_empty() {
194 reqs = get_trait_method_requires(tcx, sub_def_id);
195 }
196
197 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 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
221pub 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 pub function_targets: Vec<FunctionTarget<'tcx>>,
231 pub struct_targets: HashMap<DefId, StructTarget<'tcx>>,
233 pub trait_targets: HashMap<DefId, TraitEnsurance<'tcx>>,
235 fn_contract_cache: HashMap<DefId, FnContracts<'tcx>>,
237}
238
239impl<'tcx> VerifyTargetCollector<'tcx> {
240 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 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 requires = resolve_chain_contracts(
304 self.tcx,
305 callee_def_id,
306 3, 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 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 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 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 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; }
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 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 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 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 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 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 let def_id = id.to_def_id();
633
634 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
696pub 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 if self.crate_filter.is_some() {
725 collector.collect_extern_crate_targets();
726 }
727
728 collector.check_module_filter_result();
729
730 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 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 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
962fn 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
1015fn 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 path.len() == 1 && path[0].as_str() == name
1096}
1097
1098fn 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
1108fn 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
1118fn 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
1164pub(crate) fn get_contract_from_annotation<'tcx>(
1166 tcx: TyCtxt<'tcx>,
1167 def_id: DefId,
1168) -> FnContracts<'tcx> {
1169 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 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
1188fn 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
1228fn 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
1274fn 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
1342fn 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}