1use crate::analysis::Analysis;
10use crate::analysis::path::{
11 PathTree,
12 graph::{PathEnumerator, PathGraph},
13};
14use crate::cli::VerifyMode;
15use crate::helpers::fn_info::{FnKind, get_cons, get_mutated_fields, get_muts, get_type, returns_wrapped_self};
16use crate::verify::contract::PropertyKind;
17use crate::verify::target::get_contract_from_annotation;
18
19use crate::compat::{FxHashMap, FxHashSet};
20use rustc_middle::mir::BasicBlock;
21use rustc_middle::ty::TyCtxt;
22
23use super::{
24 contract::Property,
25 display::{
26 dedup_compound_props, emit_results_and_verdict, emit_verify_summary,
27 fmt_contract_expanded, fmt_fn_path_with_bounds, fmt_fn_path_with_generics,
28 fmt_fn_with_params,
29 },
30 engine::VerifyEngine,
31 loop_sensitivity::{LoopSensitivityAnalyzer, RepeatStrategy},
32 path_extractor::{CallGroup, PATH_LIMIT, PathExtractor},
33 report::{CheckResult, PropertyCheckResult, VerificationReport},
34 slicer::RelevantItem,
35 target::{FunctionTarget, VerifyTargetCollector},
36};
37
38use crate::helpers::mir_utils::collect_return_block_indices;
39
40use crate::helpers::mir_scan::{Checkpoint, CheckpointLocation};
41
42pub struct VerifyDriver<'target, 'tcx> {
71 tcx: TyCtxt<'tcx>,
72
73 target: &'target FunctionTarget<'tcx>,
74
75 path_info: Vec<CallGroup<'tcx>>,
76
77 engine: VerifyEngine<'tcx>,
78
79 allow_repeat: usize,
80}
81
82impl<'target, 'tcx> VerifyDriver<'target, 'tcx> {
83 pub fn new(tcx: TyCtxt<'tcx>, target: &'target FunctionTarget<'tcx>) -> Self {
84 Self::new_with_repeat(tcx, target, 0)
85 }
86
87 pub fn new_with_repeat(
88 tcx: TyCtxt<'tcx>,
89 target: &'target FunctionTarget<'tcx>,
90 allow_repeat: usize,
91 ) -> Self {
92 let all_checkpoints: Vec<_> = target.all_checkpoints().into_iter().cloned().collect();
93 let path_info = PathExtractor::new(tcx, target.def_id, all_checkpoints, allow_repeat).run();
94 Self {
95 tcx,
96 target,
97 path_info,
98 engine: VerifyEngine::new(tcx),
99 allow_repeat,
100 }
101 }
102
103 pub fn tcx(&self) -> TyCtxt<'tcx> {
105 self.tcx
106 }
107
108 pub fn target(&self) -> &'target FunctionTarget<'tcx> {
110 self.target
111 }
112
113 pub fn path_info(&self) -> &[CallGroup<'tcx>] {
115 &self.path_info
116 }
117
118 pub fn verify_function(&self) -> VerificationReport<'tcx> {
120 let mut report = VerificationReport::new(self.target.def_id);
121
122 for view in self.iter_callsite_checks() {
123 let mut view_results: Vec<PropertyCheckResult<'tcx>> = Vec::new();
124
125 for (property_index, property) in view.properties.iter().enumerate() {
126 if property.is_or() {
127 self.check_or_property(&mut report, &view, property_index, property);
128 continue;
129 }
130 let bulk = self.engine.check_callsite_from_tree(
131 view.tree,
132 view.checkpoint,
133 property,
134 &self.target.caller_requires,
135 );
136 for (path_index, (result, path_desc)) in bulk.iter().enumerate() {
137 let item = PropertyCheckResult {
138 checkpoint: view.checkpoint.location(),
139 checkpoint_index: view.checkpoint_index,
140 path_index,
141 property_index,
142 property: property.clone(),
143 result: result.clone(),
144 diagnostics: Some(format!("vm-check: {:?}", result)),
145 path_description: path_desc.clone(),
146 callee_name: view.checkpoint.callee_name(self.tcx),
147 };
148 view_results.push(item);
149 }
150 }
151
152 for item in view_results {
153 report.push(item);
154 }
155 }
156
157 report
158 }
159
160 fn check_or_property(
161 &self,
162 report: &mut VerificationReport<'tcx>,
163 view: &CheckpointCheckView<'_, '_, 'tcx>,
164 property_index: usize,
165 or_property: &Property<'tcx>,
166 ) {
167 let mut per_path: Vec<Option<(super::report::CheckResult, String)>> = Vec::new();
169
170 for group in or_property.groups().iter() {
171 let mut group_per_path: Vec<Option<(super::report::CheckResult, String)>> = Vec::new();
173 for sub_prop in group.iter() {
174 let bulk = self.engine.check_callsite_from_tree(
175 view.tree,
176 view.checkpoint,
177 sub_prop,
178 &self.target.caller_requires,
179 );
180 if group_per_path.is_empty() {
181 group_per_path.resize(bulk.len(), None);
182 }
183 for (path_idx, (result, path_desc)) in bulk.iter().enumerate() {
184 let slot = group_per_path[path_idx]
185 .get_or_insert_with(|| (result.clone(), path_desc.clone()));
186 slot.0 = slot.0.clone().and(result.clone());
187 if matches!(result, super::report::CheckResult::Failed | super::report::CheckResult::Unknown) {
188 slot.1 = path_desc.clone();
189 }
190 }
191 }
192
193 if per_path.is_empty() {
195 per_path.resize(group_per_path.len(), None);
196 }
197 for (path_idx, g) in group_per_path.iter().enumerate() {
198 if let Some((g_result, g_desc)) = g {
199 let slot = per_path[path_idx]
200 .get_or_insert_with(|| (g_result.clone(), g_desc.clone()));
201 slot.0 = slot.0.clone().or(g_result.clone());
202 if matches!(g_result, super::report::CheckResult::Proved) {
203 slot.1 = g_desc.clone();
204 }
205 }
206 }
207 }
208
209 for (path_index, best) in per_path.iter().enumerate() {
210 if let Some((result, path_desc)) = best {
211 report.push(PropertyCheckResult {
212 checkpoint: view.checkpoint.location(),
213 checkpoint_index: view.checkpoint_index,
214 path_index,
215 property_index,
216 property: or_property.clone(),
217 result: result.clone(),
218 diagnostics: Some(path_desc.clone()),
219 path_description: path_desc.clone(),
220 callee_name: view.checkpoint.callee_name(self.tcx),
221 });
222 }
223 }
224 }
225
226 pub fn properties_for_callsite(
233 &self,
234 checkpoint: &Checkpoint<'tcx>,
235 ) -> &'target [Property<'tcx>] {
236 self.target.properties_for_callsite(checkpoint)
237 }
238
239 pub fn iter_callsite_checks(
241 &self,
242 ) -> impl Iterator<Item = CheckpointCheckView<'_, 'target, 'tcx>> + '_ {
243 let mut checkpoint_index = 0usize;
244 self.path_info.iter().flat_map(move |group| {
245 group.checkpoints.iter().filter_map(move |checkpoint| {
246 let properties = self.properties_for_callsite(checkpoint);
247 if properties.is_empty() {
248 return None;
249 }
250 let view = CheckpointCheckView {
251 checkpoint_index,
252 checkpoint,
253 tree: &group.tree,
254 properties,
255 };
256 checkpoint_index += 1;
257 Some(view)
258 })
259 })
260 }
261
262 pub fn verify_struct_invariants(&self) -> VerificationReport<'tcx> {
269 let mut report = VerificationReport::new(self.target.def_id);
270 let invariants = &self.target.struct_invariants;
271 if invariants.is_empty() {
272 return report;
273 }
274
275 let is_constructor = get_type(self.tcx, self.target.def_id) == FnKind::Constructor;
276 let caller_contracts = &self.target.caller_requires;
277
278 let fn_sig = self.tcx.fn_sig(self.target.def_id).skip_binder();
279 let output = fn_sig.output().skip_binder();
280 let returns_self = is_constructor || output.is_param(0);
281
282 let entry_facts: Vec<RelevantItem<'tcx>> = if is_constructor {
283 caller_contracts
284 .iter()
285 .filter(|c| !matches!(c.kind(), Some(PropertyKind::Unknown)))
286 .map(|c| RelevantItem::ContractFact {
287 property: c.clone(),
288 })
289 .collect()
290 } else {
291 invariants
292 .iter()
293 .map(|inv| RelevantItem::ContractFact {
294 property: inv.clone(),
295 })
296 .collect()
297 };
298
299 for (checkpoint, tree) in self.build_invariant_trees(is_constructor) {
300 rap_debug!(
301 "[rapx::verify] struct invariant checkpoint bb{}: {} tree node(s)",
302 checkpoint.block.as_usize(),
303 tree.len()
304 );
305
306 let paths = tree.to_vecs();
307
308 for (property_index, invariant) in invariants.iter().enumerate() {
309 let results = self.engine.check_invariant_from_tree(
310 self.target.def_id,
311 &tree,
312 checkpoint,
313 invariant,
314 &entry_facts,
315 );
316
317 for (path_index, (result, _path_desc)) in results.iter().enumerate() {
318 let path_description = paths
319 .get(path_index)
320 .map(|p| {
321 p.iter()
322 .map(|b| b.to_string())
323 .collect::<Vec<_>>()
324 .join(", ")
325 })
326 .unwrap_or_default();
327 report.push(PropertyCheckResult {
328 checkpoint: checkpoint,
329 checkpoint_index: checkpoint.block.as_usize(),
330 path_index,
331 property_index,
332 property: invariant.clone(),
333 result: result.clone(),
334 diagnostics: Some(format!("vm-invariant: {:?}", result)),
335 path_description,
336 callee_name: format!("struct-invariant(bb{})", checkpoint.block.as_usize()),
337 });
338 }
339 }
340 }
341
342 let wrapped_self = returns_wrapped_self(self.tcx, self.target.def_id);
348 if (!returns_self && !is_constructor) || (is_constructor && wrapped_self) {
349 let has_failed = report.results.iter().any(|r| matches!(r.result, CheckResult::Failed));
350 if !has_failed {
351 report.results.retain(|r| !matches!(r.result, CheckResult::Unknown));
352 }
353 }
354
355 report
356 }
357
358 fn build_invariant_trees(
359 &self,
360 is_constructor: bool,
361 ) -> FxHashMap<CheckpointLocation, PathTree> {
362 let mut pg = PathGraph::new(self.tcx, self.target.def_id);
363 pg.find_scc();
364 let mut enumerator = PathEnumerator::new(&pg);
365 let all_paths = enumerator.enumerate_paths_repeat(self.allow_repeat);
366
367 let kind_label = if is_constructor {
368 "constructor"
369 } else {
370 "method"
371 };
372 rap_debug!(
373 "[rapx::verify] struct invariant ({kind_label}): {} whole-cfg path(s) for {}",
374 all_paths.len(),
375 self.tcx.def_path_str(self.target.def_id),
376 );
377
378 let mut trees_by_checkpoint: FxHashMap<CheckpointLocation, PathTree> = FxHashMap::default();
379
380 if is_constructor {
381 let return_blocks = collect_return_block_indices(self.tcx, self.target.def_id);
382 for &return_block in &return_blocks {
383 let checkpoint = CheckpointLocation {
384 caller: self.target.def_id,
385 block: return_block,
386 };
387 let mut tree = PathTree::new();
388 let _ = all_paths.walk_prefixes(
389 return_block.as_usize(),
390 &mut |prefix: &[usize]| -> bool {
391 if tree.len() >= PATH_LIMIT {
392 return false;
393 }
394 tree.insert(prefix);
395 true
396 },
397 );
398 if !tree.is_empty() {
399 trees_by_checkpoint.insert(checkpoint, tree);
400 }
401 }
402 } else {
403 let mut seen_paths = FxHashSet::default();
404 for path in all_paths.iter() {
405 if path.is_empty() {
406 continue;
407 }
408 if !seen_paths.insert(path.clone()) {
409 continue;
410 }
411 let last_block = BasicBlock::from(*path.last().unwrap());
412 let checkpoint = CheckpointLocation {
413 caller: self.target.def_id,
414 block: last_block,
415 };
416 trees_by_checkpoint
417 .entry(checkpoint)
418 .or_insert_with(PathTree::new)
419 .insert(path.as_slice());
420 }
421 }
422
423 trees_by_checkpoint
424 }
425}
426
427pub struct CheckpointCheckView<'view, 'target, 'tcx> {
430 pub checkpoint_index: usize,
432 pub checkpoint: &'view Checkpoint<'tcx>,
434 pub tree: &'view PathTree,
436 pub properties: &'target [Property<'tcx>],
438}
439
440pub struct VerifyRun<'tcx> {
442 tcx: TyCtxt<'tcx>,
443 repeat_strategy: RepeatStrategy,
444 mode: VerifyMode,
445 skip_invariant: bool,
446 crate_filter: Option<String>,
447 module_filter: Option<String>,
448 debug_contracts: bool,
449}
450
451impl<'tcx> VerifyRun<'tcx> {
452 pub fn new(
454 tcx: TyCtxt<'tcx>,
455 repeat_strategy: RepeatStrategy,
456 mode: VerifyMode,
457 skip_invariant: bool,
458 crate_filter: Option<String>,
459 module_filter: Option<String>,
460 debug_contracts: bool,
461 ) -> Self {
462 Self {
463 tcx,
464 repeat_strategy,
465 mode,
466 skip_invariant,
467 crate_filter,
468 module_filter,
469 debug_contracts,
470 }
471 }
472
473 fn repeat_rounds_for_target(&self, target: &FunctionTarget<'tcx>) -> (usize, Vec<usize>) {
474 match self.repeat_strategy {
475 RepeatStrategy::Fixed(n) => (n, (0..=n).collect()),
476 RepeatStrategy::Auto => {
477 let plan = LoopSensitivityAnalyzer::new(self.tcx).analyze(target);
478 let repeat = plan.repeat;
479 (repeat, (0..=repeat).collect())
480 }
481 }
482 }
483
484 fn run_invless_sequences(&self, targets: &[FunctionTarget<'tcx>]) {
494 for target in targets {
495 let read_def_id = target.def_id;
496 let cons = get_cons(self.tcx, read_def_id);
497 if cons.is_empty() {
498 continue;
499 }
500 let muts = get_muts(self.tcx, read_def_id);
501
502 for &con_id in &cons {
503 let con_target = self.build_virtual_target(target, read_def_id, con_id, &[]);
504 self.verify_and_emit_sequence(read_def_id, &con_target, con_id, &[]);
505
506 for &mut_id in &muts {
507 let con_target =
508 self.build_virtual_target(target, read_def_id, con_id, &[mut_id]);
509 self.verify_and_emit_sequence(
510 read_def_id,
511 &con_target,
512 con_id,
513 &[mut_id],
514 );
515 }
516 }
517 }
518 }
519
520 fn build_virtual_target(
521 &self,
522 read_target: &FunctionTarget<'tcx>,
523 read_def_id: rustc_hir::def_id::DefId,
524 con_id: rustc_hir::def_id::DefId,
525 mut_ids: &[rustc_hir::def_id::DefId],
526 ) -> FunctionTarget<'tcx> {
527 let mut accumulated_requires: Vec<Property<'tcx>> = Vec::new();
528
529 let con_contracts: Vec<Property<'tcx>> = get_contract_from_annotation(self.tcx, con_id)
532 .into_iter()
533 .map(|c| remap_constructor_contract(c))
534 .collect();
535 accumulated_requires.extend(con_contracts);
536
537 if !mut_ids.is_empty() {
539 let mut mutated_fields: Vec<usize> = Vec::new();
540 for &mut_id in mut_ids {
541 for field_idx in get_mutated_fields(self.tcx, mut_id) {
542 if !mutated_fields.contains(&field_idx) {
543 mutated_fields.push(field_idx);
544 }
545 }
546 }
547 if !mutated_fields.is_empty() {
548 accumulated_requires.retain(|prop| {
549 let prop_fields = property_field_indices(prop);
550 !prop_fields.iter().any(|f| mutated_fields.contains(f))
551 });
552 }
553 }
554
555 accumulated_requires.extend(read_target.caller_requires.clone());
561
562 FunctionTarget {
563 def_id: read_def_id,
564 owner_struct_def_id: read_target.owner_struct_def_id,
565 checkpoints: read_target.checkpoints.clone(),
566 callee_requires: read_target.callee_requires.clone(),
567 caller_requires: accumulated_requires,
568 struct_invariants: Vec::new(),
569 raw_ptr_deref_checks: read_target.raw_ptr_deref_checks.clone(),
570 static_mut_checks: read_target.static_mut_checks.clone(),
571 }
572 }
573
574 fn verify_and_emit_sequence(
575 &self,
576 read_def_id: rustc_hir::def_id::DefId,
577 con_target: &FunctionTarget<'tcx>,
578 con_id: rustc_hir::def_id::DefId,
579 mut_ids: &[rustc_hir::def_id::DefId],
580 ) {
581 let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
582 let mut crashed: Option<String> = None;
583
584 let (_, repeat_rounds) = self.repeat_rounds_for_target(con_target);
585 for repeat in repeat_rounds {
586 let driver = VerifyDriver::new_with_repeat(
587 self.tcx, con_target, repeat,
588 );
589 match crate::helpers::mir_utils::catch_panic(|| driver.verify_function()) {
590 Ok(report) => {
591 rap_debug!("{}", report.describe());
592 all_results.extend(report.results);
593 }
594 Err(msg) => {
595 rap_warn!(
596 "Skipping constructor {} (repeat {}): {msg}",
597 self.tcx.def_path_str(con_id),
598 repeat,
599 );
600 all_results.clear();
601 crashed = Some(format!("repeat {repeat}: {msg}"));
602 break;
603 }
604 }
605 }
606
607 let read_name = short_fn_name(self.tcx, read_def_id);
608 let con_name = short_fn_name(self.tcx, con_id);
609 let mut chain_parts: Vec<String> = vec![con_name];
610 for &mut_id in mut_ids {
611 chain_parts.push(short_fn_name(self.tcx, mut_id));
612 }
613 chain_parts.push(read_name);
614 let chain_label = chain_parts.join(" -> ");
615
616 rap_info!("============================================================");
617 rap_info!("[rapx::verify] sequence: {chain_label}");
618 rap_info!("============================================================");
619
620 if let Some(msg) = &crashed {
621 rap_warn!(" result: UNKNOWN (verifier crashed: {msg})");
622 } else if all_results.is_empty() {
623 rap_info!(" result: SOUND (no unsafe checkpoints)");
624 } else {
625 emit_results_and_verdict(self.tcx, &all_results);
626 }
627 rap_info!("");
628 }
629}
630
631impl<'tcx> Analysis for VerifyRun<'tcx> {
632 fn run(&mut self) {
639 crate::verify::contract::def::register_contract_defs(self.tcx);
641
642 let collector = VerifyTargetCollector::collect_all(
643 self.tcx,
644 self.mode,
645 self.skip_invariant,
646 self.crate_filter.clone(),
647 self.module_filter.clone(),
648 );
649
650 if self.debug_contracts {
651 self.print_contracts_debug(&collector.function_targets);
652 return;
653 }
654
655 for target in &collector.function_targets {
656 let target_path = fmt_fn_path_with_bounds(self.tcx, target.def_id);
657 let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
658 let mut fn_crashed: Option<String> = None;
659
660 let (planned_repeat, repeat_rounds) = self.repeat_rounds_for_target(target);
661
662 for repeat in repeat_rounds {
664 let driver = VerifyDriver::new_with_repeat(
665 self.tcx, target, repeat,
666 );
667 match crate::helpers::mir_utils::catch_panic(|| driver.verify_function()) {
668 Ok(report) => {
669 rap_debug!("{}", report.describe());
670 all_results.extend(report.results);
671 }
672 Err(msg) => {
673 rap_warn!(
674 "Skipping function {} (repeat {}): {msg}",
675 target_path,
676 repeat,
677 );
678 all_results.clear();
679 fn_crashed = Some(format!("repeat {repeat}: {msg}"));
680 break;
681 }
682 }
683 }
684
685 if !target.struct_invariants.is_empty() && !self.skip_invariant {
687 let driver = VerifyDriver::new_with_repeat(
688 self.tcx, target, planned_repeat,
689 );
690 match crate::helpers::mir_utils::catch_panic(|| driver.verify_struct_invariants()) {
691 Ok(struct_report) => {
692 rap_debug!("{}", struct_report.describe());
693 all_results.extend(struct_report.results);
694 }
695 Err(msg) => {
696 rap_warn!("Skipping struct invariants for {} : {msg}", target_path);
697 all_results.clear();
698 fn_crashed = Some(format!("struct-invariant: {msg}"));
699 }
700 }
701 }
702
703 if let Some(msg) = &fn_crashed {
704 rap_info!("============================================================");
705 rap_info!("[rapx::verify] function: {target_path}");
706 rap_info!("============================================================");
707 rap_warn!(" result: UNKNOWN (verifier crashed: {msg})");
708 rap_info!("");
709 continue;
710 }
711
712 if all_results.is_empty() {
713 let all_callees_skipped = !target.checkpoints.is_empty()
714 && target.checkpoints.iter().all(|ckpt| {
715 ckpt.callee.map_or(false, |callee| {
716 target
717 .callee_requires
718 .get(&callee)
719 .map_or(true, |c| c.is_empty())
720 })
721 });
722 if (target.checkpoints.is_empty() || all_callees_skipped)
723 && target.raw_ptr_deref_checks.is_empty()
724 && target.static_mut_checks.is_empty()
725 && target.struct_invariants.is_empty()
726 {
727 rap_info!("============================================================");
728 rap_info!("[rapx::verify] function: {target_path}");
729 rap_info!("============================================================");
730 if self.skip_invariant {
731 let cons = get_cons(self.tcx, target.def_id);
732 for con in &cons {
733 rap_info!(" + constructor: {}", self.tcx.def_path_str(*con));
734 }
735 }
736 rap_info!(" --- unsafe checkpoints ---");
737 rap_info!(" <none>");
738 rap_info!(" <none>");
739 rap_info!(" result: SOUND (no unsafe checkpoints)");
740 rap_info!("");
741 }
742 continue;
743 }
744
745 if self.skip_invariant && !get_cons(self.tcx, target.def_id).is_empty() {
748 continue;
749 }
750
751 emit_verify_summary(
752 self.tcx,
753 &target_path,
754 target.def_id,
755 &all_results,
756 self.skip_invariant,
757 );
758 }
759
760 if !collector.trait_targets.is_empty() {
762 let mut trait_ids: Vec<_> = collector.trait_targets.keys().copied().collect();
763 trait_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
764 for trait_def_id in trait_ids {
765 let Some(trait_target) = collector.trait_targets.get(&trait_def_id) else {
766 continue;
767 };
768 rap_info!("============================================================");
769 rap_info!(
770 "[rapx::verify] unsafe trait impl: {}",
771 self.tcx.def_path_str(trait_target.def_id)
772 );
773 rap_info!("============================================================");
774 if let Some(self_ty) = trait_target.self_ty_def_id {
775 rap_info!(" impl for: {}", self.tcx.def_path_str(self_ty));
776 }
777 if trait_target.ensures.is_empty() {
778 rap_info!(" ensures: <none>");
779 } else {
780 rap_info!(" ensures (implementor must satisfy):");
781 for (method_name, contracts) in &trait_target.ensures {
782 rap_info!(" fn {}:", method_name);
783 for property in dedup_compound_props(contracts.iter()) {
784 rap_info!(
785 " - {}",
786 property.display_for_report(
787 self.tcx,
788 trait_target.self_ty_def_id,
789 None,
790 )
791 );
792 }
793 }
794 }
795 rap_info!(" verification: deferred");
796 rap_info!("");
797 }
798 }
799
800 if self.skip_invariant {
802 self.run_invless_sequences(&collector.function_targets);
803 }
804 }
805
806}
807
808impl<'tcx> VerifyRun<'tcx> {
809 fn print_contracts_debug(&self, targets: &[FunctionTarget<'tcx>]) {
810 rap_info!("{:=<1$}", "", 76);
811 rap_info!("[rapx::debug-contracts] Expanded Contract Assertions");
812 rap_info!("{:=<1$}", "", 76);
813 rap_info!("");
814
815 let mut struct_groups: FxHashMap<
816 rustc_hir::def_id::DefId,
817 Vec<&FunctionTarget<'tcx>>,
818 > = FxHashMap::default();
819 let mut free_targets: Vec<&FunctionTarget<'tcx>> = Vec::new();
820
821 for target in targets {
822 if let Some(sid) = target.owner_struct_def_id {
823 struct_groups.entry(sid).or_default().push(target);
824 } else {
825 free_targets.push(target);
826 }
827 }
828
829 let mut struct_ids: Vec<_> = struct_groups.keys().copied().collect();
830 struct_ids.sort_by_key(|did| self.tcx.def_path_str(*did));
831
832 for struct_def_id in struct_ids {
833 let methods = &struct_groups[&struct_def_id];
834 let struct_name = self.tcx.def_path_str(struct_def_id);
835
836 let inv_target = methods
838 .iter()
839 .find(|t| !t.struct_invariants.is_empty());
840 let have_invariants = inv_target.is_some();
841
842 if have_invariants || methods.iter().any(|t| {
843 self.has_printable_contracts(t)
844 }) {
845 rap_info!("{:=<1$}", "", 76);
846 rap_info!("[rapx::debug-contracts] struct: {struct_name}");
847 rap_info!("{:=<1$}", "", 76);
848 }
849
850 if let Some(tgt) = inv_target {
851 rap_info!(" [Struct Invariants]:");
852 let invariants = dedup_compound_props(tgt.struct_invariants.iter());
853 let inv_count = invariants.len();
854 for (ii, property) in invariants.iter().enumerate() {
855 let ibranch = if ii + 1 == inv_count { "`-" } else { "|-" };
856 let (call, meaning) = fmt_contract_expanded(
857 self.tcx,
858 property,
859 tgt.owner_struct_def_id,
860 Some(tgt.def_id),
861 );
862 self.print_contract_lines(" ", &ibranch, &call, &meaning);
863 }
864 rap_info!("");
865 }
866
867 let mut printed = false;
869 for (mi, target) in methods.iter().enumerate() {
870 let is_last_method = mi + 1 == methods.len();
871 let branch = if is_last_method { "`-" } else { "|-" };
872 let cont = if is_last_method { " " } else { "| " };
873 if self.print_target_contracts(target, branch, cont) {
874 printed = true;
875 }
876 }
877 if printed {
878 rap_info!("{:=<1$}", "", 76);
879 rap_info!("");
880 }
881 }
882
883 for target in &free_targets {
885 self.print_target_contracts(target, "- ", " ");
886 }
887 }
888
889 fn has_printable_contracts(&self, target: &FunctionTarget<'tcx>) -> bool {
890 use crate::verify::contract::PropertyKind;
891 let is_unsafe_fn = self
892 .tcx
893 .fn_sig(target.def_id)
894 .skip_binder()
895 .safety()
896 == rustc_hir::Safety::Unsafe;
897 let has_caller = is_unsafe_fn
898 && target
899 .caller_requires
900 .iter()
901 .any(|p| p.kind() != Some(PropertyKind::Unknown));
902 if has_caller {
903 return true;
904 }
905 target.callee_requires.values().any(|c| {
906 c.iter().any(|p| p.kind() != Some(PropertyKind::Unknown))
907 })
908 }
909
910 fn print_contract_lines(&self, prefix: &str, branch: &str, call: &str, meaning: &str) {
911 rap_info!("{prefix}{branch} {call}");
912 let cont = if branch == "`-" { " " } else { "| " };
913 for line in meaning.lines() {
914 rap_info!("{prefix}{cont} {line}");
915 }
916 }
917
918 fn print_target_contracts(
919 &self,
920 target: &FunctionTarget<'tcx>,
921 branch: &str,
922 cont: &str,
923 ) -> bool {
924 use crate::verify::contract::PropertyKind;
925
926 let (arg_names_typed, ret_ty) = self.resolve_arg_names_with_types(target.def_id);
927 let is_unsafe_fn = self
928 .tcx
929 .fn_sig(target.def_id)
930 .skip_binder()
931 .safety()
932 == rustc_hir::Safety::Unsafe;
933
934 let target_path = fmt_fn_path_with_generics(self.tcx, target.def_id);
935 let short_name = crate::helpers::name::short_fn_name(self.tcx, target.def_id);
936
937 let has_caller = is_unsafe_fn
939 && target
940 .caller_requires
941 .iter()
942 .any(|p| p.kind() != Some(PropertyKind::Unknown));
943 let mut callee_ids: Vec<_> = target.callee_requires.keys().copied().collect();
944 callee_ids.retain(|did| {
945 target
946 .callee_requires
947 .get(did)
948 .is_some_and(|c| c.iter().any(|p| p.kind() != Some(PropertyKind::Unknown)))
949 });
950 callee_ids.sort_by_key(|did| self.tcx.def_path_str(*did));
951 let has_callees = !callee_ids.is_empty();
952
953 if !has_caller && !has_callees {
954 return false;
955 }
956
957 let fn_display = fmt_fn_with_params(&target_path, &arg_names_typed, ret_ty.as_deref());
958 let header = format!("--- method: {short_name}");
959 let dashes = 72usize.saturating_sub(header.len());
960 rap_info!("{branch} {header} {}", "-".repeat(dashes));
961 rap_info!("{cont} {fn_display}");
962
963 if has_caller {
965 rap_info!("{cont} [Caller Contracts]:");
966 let caller_props = dedup_compound_props(
967 target
968 .caller_requires
969 .iter()
970 .filter(|p| p.kind() != Some(PropertyKind::Unknown)),
971 );
972 for (pi, property) in caller_props.iter().enumerate() {
973 let is_last = pi + 1 == caller_props.len();
974 let pbranch = if is_last { "`-" } else { "|-" };
975 let (call, meaning) = fmt_contract_expanded(
976 self.tcx,
977 property,
978 target.owner_struct_def_id,
979 Some(target.def_id),
980 );
981 self.print_contract_lines(
982 &format!("{cont} "),
983 pbranch,
984 &call,
985 &meaning,
986 );
987 }
988 if !has_callees {
989 rap_info!("");
990 }
991 }
992
993 if has_callees {
995 rap_info!("{cont} [Unsafe Callees]:");
996 for (ci, &callee_id) in callee_ids.iter().enumerate() {
997 let is_last_callee = ci + 1 == callee_ids.len();
998 let cbranch = if is_last_callee { "`-" } else { "|-" };
999 let ccont = if is_last_callee { " " } else { "| " };
1000 let contracts = target.callee_requires.get(&callee_id).unwrap();
1001 let (callee_typed, callee_ret) =
1002 self.resolve_arg_names_with_types(callee_id);
1003 let callee_path = fmt_fn_path_with_generics(self.tcx, callee_id);
1004 rap_info!(
1005 "{cont} {cbranch} {}",
1006 fmt_fn_with_params(
1007 &callee_path,
1008 &callee_typed,
1009 callee_ret.as_deref()
1010 )
1011 );
1012 let props = dedup_compound_props(
1013 contracts
1014 .iter()
1015 .filter(|p| p.kind() != Some(PropertyKind::Unknown)),
1016 );
1017 for (pi, property) in props.iter().enumerate() {
1018 let is_last_prop = pi + 1 == props.len();
1019 let pbranch = if is_last_prop { "`-" } else { "|-" };
1020 let (call, meaning) = fmt_contract_expanded(
1021 self.tcx,
1022 property,
1023 None,
1024 Some(callee_id),
1025 );
1026 self.print_contract_lines(
1027 &format!("{cont} {ccont}"),
1028 pbranch,
1029 &call,
1030 &meaning,
1031 );
1032 }
1033 }
1034 }
1035
1036 rap_info!("");
1037 true
1038 }
1039
1040 fn resolve_arg_names_with_types(
1041 &self,
1042 def_id: rustc_hir::def_id::DefId,
1043 ) -> (Vec<String>, Option<String>) {
1044 if !self.tcx.is_mir_available(def_id) {
1045 return (Vec::new(), None);
1046 }
1047 let body = self.tcx.optimized_mir(def_id);
1048 let args: Vec<String> = body
1049 .local_decls
1050 .iter()
1051 .enumerate()
1052 .skip(1)
1053 .take(body.arg_count)
1054 .map(|(i, decl)| {
1055 let name = {
1056 let span = decl.source_info.span;
1057 self.tcx
1058 .sess
1059 .source_map()
1060 .span_to_snippet(span)
1061 .unwrap_or_else(|_| format!("_{}", i))
1062 };
1063 let ty = decl.ty.to_string();
1064 format!("{name}: {ty}")
1065 })
1066 .collect();
1067 let ret_ty = self.tcx.fn_sig(def_id).skip_binder().output().skip_binder();
1068 let ret_ty = if ret_ty.is_unit() {
1069 None
1070 } else {
1071 Some(ret_ty.to_string())
1072 };
1073 (args, ret_ty)
1074 }
1075}
1076
1077use crate::helpers::name::short_fn_name;
1078
1079fn property_field_indices(property: &crate::verify::contract::Property<'_>) -> Vec<usize> {
1085 use crate::verify::contract::{ContractExpr, PropertyArg};
1086 let mut indices = Vec::new();
1087 for arg in property.args() {
1088 let place = match arg {
1089 PropertyArg::Expr(ContractExpr::Place(p)) => Some(p),
1090 _ => None,
1091 };
1092 if let Some(place) = place {
1093 for proj in &place.projections {
1094 match proj {
1095 crate::verify::contract::ContractProjection::Field { index, .. } => {
1096 let idx = *index;
1097 if !indices.contains(&idx) {
1098 indices.push(idx);
1099 }
1100 }
1101 crate::verify::contract::ContractProjection::Downcast { .. } => {}
1102 crate::verify::contract::ContractProjection::IterElements => {}
1103 }
1104 }
1105 }
1106 }
1107 indices
1108}
1109
1110fn remap_constructor_contract<'tcx>(
1111 property: crate::verify::contract::Property<'tcx>,
1112) -> crate::verify::contract::Property<'tcx> {
1113 use crate::verify::contract::{
1114 ContractExpr, ContractPlace, ContractProjection, PlaceBase, PropertyArg,
1115 };
1116
1117 fn remap_place_arg<'tcx>(arg: &PropertyArg<'tcx>) -> PropertyArg<'tcx> {
1118 let place = match arg {
1119 PropertyArg::Expr(ContractExpr::Place(p)) => p,
1120 _ => return arg.clone(),
1121 };
1122 let PlaceBase::Arg(field_idx) = place.base else {
1123 return arg.clone();
1124 };
1125 let projection = ContractProjection::Field {
1126 index: field_idx,
1127 ty: None,
1128 };
1129 let mut new_place = ContractPlace {
1130 base: PlaceBase::Arg(0),
1131 projections: vec![projection],
1132 };
1133 new_place
1134 .projections
1135 .extend(place.projections.iter().cloned());
1136 PropertyArg::Expr(ContractExpr::Place(new_place))
1137 }
1138
1139 let new_args: Vec<PropertyArg<'tcx>> = property
1140 .args()
1141 .iter()
1142 .map(|arg| remap_place_arg(arg))
1143 .collect();
1144
1145 match property {
1146 crate::verify::contract::Property::Leaf(mut leaf) => {
1147 leaf.args = new_args;
1148 crate::verify::contract::Property::Leaf(leaf)
1149 }
1150 crate::verify::contract::Property::Or(or) => crate::verify::contract::Property::Or(or),
1151 }
1152}