1use crate::analysis::Analysis;
10use crate::analysis::path_analysis::{
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};
16use crate::verify::contract::PropertyKind;
17use crate::verify::target::get_contract_from_annotation;
18
19use crate::compat::{FxHashMap, FxHashSet};
20use indexmap::IndexMap;
21use rustc_middle::mir::BasicBlock;
22use rustc_middle::ty::TyCtxt;
23use std::panic::{AssertUnwindSafe, catch_unwind};
24
25use super::{
26 contract::Property,
27 engine::VerifyEngine,
28 helpers::{Checkpoint, CheckpointKind, CheckpointLocation, collect_return_block_indices},
29 path_extractor::{CallGroup, PATH_LIMIT, PathExtractor},
30 report::{PropertyCheckResult, VerificationReport, VisitDiagnostics},
31 slicer::BackwardItem,
32 target::{FunctionTarget, VerifyTargetCollector},
33};
34
35pub struct VerifyDriver<'target, 'tcx> {
64 tcx: TyCtxt<'tcx>,
67
68 target: &'target FunctionTarget<'tcx>,
75
76 path_info: Vec<CallGroup<'tcx>>,
80
81 engine: VerifyEngine<'tcx>,
85
86 allow_repeat: usize,
98}
99
100impl<'target, 'tcx> VerifyDriver<'target, 'tcx> {
101 pub fn new(tcx: TyCtxt<'tcx>, target: &'target FunctionTarget<'tcx>) -> Self {
103 Self::new_with_repeat(tcx, target, 0)
104 }
105
106 pub fn new_with_repeat(
108 tcx: TyCtxt<'tcx>,
109 target: &'target FunctionTarget<'tcx>,
110 allow_repeat: usize,
111 ) -> Self {
112 let mut all_checkpoints = target.checkpoints.clone();
113 for (checkpoint, _) in &target.raw_ptr_deref_checks {
114 all_checkpoints.push(checkpoint.clone());
115 }
116 for (checkpoint, _) in &target.static_mut_checks {
117 all_checkpoints.push(checkpoint.clone());
118 }
119 let path_info = PathExtractor::new(tcx, target.def_id, all_checkpoints, allow_repeat).run();
120 let engine = VerifyEngine::new(tcx);
121 Self {
122 tcx,
123 target,
124 path_info,
125 engine,
126 allow_repeat,
127 }
128 }
129
130 pub fn tcx(&self) -> TyCtxt<'tcx> {
132 self.tcx
133 }
134
135 pub fn target(&self) -> &'target FunctionTarget<'tcx> {
137 self.target
138 }
139
140 pub fn path_info(&self) -> &[CallGroup<'tcx>] {
142 &self.path_info
143 }
144
145 pub fn verify_function(&self) -> VerificationReport<'tcx> {
147 let mut report = VerificationReport::new(self.target.def_id);
148
149 for view in self.iter_callsite_checks() {
150 for (property_index, property) in view.properties.iter().enumerate() {
151 let bulk = self.engine.check_callsite_from_tree(
152 view.tree,
153 view.checkpoint,
154 property,
155 &self.target.caller_requires,
156 );
157 for (path_index, (forward, smt_check)) in bulk.iter().enumerate() {
158 let check_diagnostics =
159 format!("{}\n{}", forward.describe(), smt_check.describe());
160 report.push(PropertyCheckResult {
161 checkpoint: view.checkpoint.location(),
162 checkpoint_index: view.checkpoint_index,
163 path_index,
164 property_index,
165 property: property.clone(),
166 result: smt_check.result.clone(),
167 diagnostics: Some(VisitDiagnostics::new(String::new(), check_diagnostics)),
168 path_description: forward.path.describe_indices(),
169 callee_name: view.checkpoint.callee_name(self.tcx),
170 });
171 }
172 }
173 }
174
175 report
176 }
177
178 pub fn properties_for_callsite(
185 &self,
186 checkpoint: &Checkpoint<'tcx>,
187 ) -> &'target [Property<'tcx>] {
188 let loc = checkpoint.location();
189 match checkpoint.kind {
190 CheckpointKind::RawPtrDeref => {
191 for (cs, props) in &self.target.raw_ptr_deref_checks {
192 if cs.location() == loc {
193 return props.as_slice();
194 }
195 }
196 &[]
197 }
198 CheckpointKind::StaticMutAccess => {
199 for (cs, props) in &self.target.static_mut_checks {
200 if cs.location() == loc {
201 return props.as_slice();
202 }
203 }
204 &[]
205 }
206 CheckpointKind::UnsafeCall => {
207 if let Some(callee) = checkpoint.callee {
208 self.target
209 .callee_requires
210 .get(&callee)
211 .map(Vec::as_slice)
212 .unwrap_or(&[])
213 } else {
214 &[]
215 }
216 }
217 }
218 }
219
220 pub fn iter_callsite_checks(
222 &self,
223 ) -> impl Iterator<Item = CheckpointCheckView<'_, 'target, 'tcx>> + '_ {
224 let mut checkpoint_index = 0usize;
225 self.path_info.iter().flat_map(move |group| {
226 group.checkpoints.iter().filter_map(move |checkpoint| {
227 let properties = self.properties_for_callsite(checkpoint);
228 if properties.is_empty() {
229 return None;
230 }
231 let view = CheckpointCheckView {
232 checkpoint_index,
233 checkpoint,
234 tree: &group.tree,
235 properties,
236 };
237 checkpoint_index += 1;
238 Some(view)
239 })
240 })
241 }
242
243 pub fn verify_struct_invariants(&self) -> VerificationReport<'tcx> {
250 let mut report = VerificationReport::new(self.target.def_id);
251 let invariants = &self.target.struct_invariants;
252 if invariants.is_empty() {
253 return report;
254 }
255
256 let is_constructor = get_type(self.tcx, self.target.def_id) == FnKind::Constructor;
257 let caller_contracts = &self.target.caller_requires;
258
259 let entry_facts: Vec<BackwardItem<'tcx>> = if is_constructor {
260 caller_contracts
261 .iter()
262 .filter(|c| !matches!(c.kind, PropertyKind::Unknown))
263 .map(|c| BackwardItem::ContractFact {
264 property: c.clone(),
265 })
266 .collect()
267 } else {
268 invariants
269 .iter()
270 .map(|inv| BackwardItem::ContractFact {
271 property: inv.clone(),
272 })
273 .collect()
274 };
275
276 for (checkpoint, tree) in self.build_invariant_trees(is_constructor) {
277 rap_debug!(
278 "[rapx::verify] struct invariant checkpoint bb{}: {} tree node(s)",
279 checkpoint.block.as_usize(),
280 tree.len()
281 );
282
283 for (property_index, invariant) in invariants.iter().enumerate() {
284 let results = self.engine.check_invariant_from_tree(
285 self.target.def_id,
286 &tree,
287 checkpoint,
288 invariant,
289 &entry_facts,
290 );
291
292 for (path_index, check) in results.iter().enumerate() {
293 report.push(PropertyCheckResult {
294 checkpoint: checkpoint,
295 checkpoint_index: checkpoint.block.as_usize(),
296 path_index,
297 property_index,
298 property: invariant.clone(),
299 result: check.result.clone(),
300 diagnostics: Some(VisitDiagnostics::new(
301 check.slicing_diag.clone(),
302 check.verification_diag.clone(),
303 )),
304 path_description: String::new(),
305 callee_name: format!("struct-invariant(bb{})", checkpoint.block.as_usize()),
306 });
307 }
308 }
309 }
310
311 report
312 }
313
314 fn build_invariant_trees(
315 &self,
316 is_constructor: bool,
317 ) -> FxHashMap<CheckpointLocation, PathTree> {
318 let mut pg = PathGraph::new(self.tcx, self.target.def_id);
319 pg.find_scc();
320 let mut enumerator = PathEnumerator::new(&pg);
321 let all_paths = enumerator.enumerate_paths_repeat(self.allow_repeat);
322
323 let kind_label = if is_constructor {
324 "constructor"
325 } else {
326 "method"
327 };
328 rap_debug!(
329 "[rapx::verify] struct invariant ({kind_label}): {} whole-cfg path(s) for {}",
330 all_paths.len(),
331 self.tcx.def_path_str(self.target.def_id),
332 );
333
334 let mut trees_by_checkpoint: FxHashMap<CheckpointLocation, PathTree> = FxHashMap::default();
335
336 if is_constructor {
337 let return_blocks = collect_return_block_indices(self.tcx, self.target.def_id);
338 for &return_block in &return_blocks {
339 let checkpoint = CheckpointLocation {
340 caller: self.target.def_id,
341 block: return_block,
342 };
343 let mut tree = PathTree::new();
344 let _ = all_paths.walk_prefixes(
345 return_block.as_usize(),
346 &mut |prefix: &[usize]| -> bool {
347 if tree.len() >= PATH_LIMIT {
348 return false;
349 }
350 tree.insert(prefix);
351 true
352 },
353 );
354 if !tree.is_empty() {
355 trees_by_checkpoint.insert(checkpoint, tree);
356 }
357 }
358 } else {
359 let mut seen_paths = FxHashSet::default();
360 for path in all_paths.iter() {
361 if path.is_empty() {
362 continue;
363 }
364 if !seen_paths.insert(path.clone()) {
365 continue;
366 }
367 let last_block = BasicBlock::from(*path.last().unwrap());
368 let checkpoint = CheckpointLocation {
369 caller: self.target.def_id,
370 block: last_block,
371 };
372 trees_by_checkpoint
373 .entry(checkpoint)
374 .or_insert_with(PathTree::new)
375 .insert(path.as_slice());
376 }
377 }
378
379 trees_by_checkpoint
380 }
381}
382
383pub struct CheckpointCheckView<'view, 'target, 'tcx> {
386 pub checkpoint_index: usize,
388 pub checkpoint: &'view Checkpoint<'tcx>,
390 pub tree: &'view PathTree,
392 pub properties: &'target [Property<'tcx>],
394}
395
396pub struct VerifyRun<'tcx> {
398 tcx: TyCtxt<'tcx>,
399 postfix_repeat: usize,
400 mode: VerifyMode,
401 crate_filter: Option<String>,
402 module_filter: Option<String>,
403}
404
405impl<'tcx> VerifyRun<'tcx> {
406 pub fn new(
408 tcx: TyCtxt<'tcx>,
409 postfix_repeat: usize,
410 mode: VerifyMode,
411 crate_filter: Option<String>,
412 module_filter: Option<String>,
413 ) -> Self {
414 Self {
415 tcx,
416 postfix_repeat,
417 mode,
418 crate_filter,
419 module_filter,
420 }
421 }
422
423 fn run_invless_sequences(&self, targets: &[FunctionTarget<'tcx>]) {
433 for target in targets {
434 let read_def_id = target.def_id;
435 let cons = get_cons(self.tcx, read_def_id);
436 if cons.is_empty() {
437 continue;
438 }
439 let muts = get_muts(self.tcx, read_def_id);
440
441 for &con_id in &cons {
442 let con_target = self.build_virtual_target(target, read_def_id, con_id, &[]);
443 self.verify_and_emit_sequence(target, read_def_id, &con_target, con_id, &[], 0);
444
445 for (mut_idx, &mut_id) in muts.iter().enumerate() {
446 let con_target =
447 self.build_virtual_target(target, read_def_id, con_id, &[mut_id]);
448 self.verify_and_emit_sequence(
449 target,
450 read_def_id,
451 &con_target,
452 con_id,
453 &[mut_id],
454 1 + mut_idx,
455 );
456 }
457 }
458 }
459 }
460
461 fn build_virtual_target(
462 &self,
463 read_target: &FunctionTarget<'tcx>,
464 read_def_id: rustc_hir::def_id::DefId,
465 con_id: rustc_hir::def_id::DefId,
466 mut_ids: &[rustc_hir::def_id::DefId],
467 ) -> FunctionTarget<'tcx> {
468 let mut accumulated_requires: Vec<Property<'tcx>> = Vec::new();
469
470 let con_contracts = get_contract_from_annotation(self.tcx, con_id);
472 accumulated_requires.extend(con_contracts);
473
474 if !mut_ids.is_empty() {
476 let mut mutated_fields: Vec<usize> = Vec::new();
477 for &mut_id in mut_ids {
478 for field_idx in get_mutated_fields(self.tcx, mut_id) {
479 if !mutated_fields.contains(&field_idx) {
480 mutated_fields.push(field_idx);
481 }
482 }
483 }
484 if !mutated_fields.is_empty() {
485 accumulated_requires.retain(|prop| {
486 let prop_fields = property_field_indices(prop);
487 !prop_fields.iter().any(|f| mutated_fields.contains(f))
488 });
489 }
490 }
491
492 let own_requires = get_contract_from_annotation(self.tcx, read_def_id);
494 for req in own_requires {
495 if !accumulated_requires.iter().any(|p| same_property(p, &req)) {
496 accumulated_requires.push(req);
497 }
498 }
499
500 FunctionTarget {
501 def_id: read_def_id,
502 owner_struct_def_id: read_target.owner_struct_def_id,
503 checkpoints: read_target.checkpoints.clone(),
504 callee_requires: read_target.callee_requires.clone(),
505 caller_requires: accumulated_requires,
506 struct_invariants: Vec::new(),
507 raw_ptr_deref_checks: read_target.raw_ptr_deref_checks.clone(),
508 static_mut_checks: read_target.static_mut_checks.clone(),
509 }
510 }
511
512 fn verify_and_emit_sequence(
513 &self,
514 _read_target: &FunctionTarget<'tcx>,
515 read_def_id: rustc_hir::def_id::DefId,
516 con_target: &FunctionTarget<'tcx>,
517 con_id: rustc_hir::def_id::DefId,
518 mut_ids: &[rustc_hir::def_id::DefId],
519 _seq_index: usize,
520 ) {
521 let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
522
523 for repeat in 0..=self.postfix_repeat {
524 let driver = VerifyDriver::new_with_repeat(self.tcx, con_target, repeat);
525 let result = catch_unwind(AssertUnwindSafe(|| driver.verify_function()));
526 match result {
527 Ok(report) => {
528 rap_debug!("{}", report.describe());
529 all_results.extend(report.results);
530 }
531 Err(e) => {
532 let msg = e
533 .downcast_ref::<String>()
534 .map(|s| s.as_str())
535 .or_else(|| e.downcast_ref::<&str>().copied())
536 .unwrap_or("<rustc ICE>");
537 rap_warn!(
538 "Skipping invless constructor {} (repeat {}): {}",
539 self.tcx.def_path_str(con_id),
540 repeat,
541 msg
542 );
543 all_results.clear();
544 break;
545 }
546 }
547 }
548
549 let read_name = short_fn_name(self.tcx, read_def_id);
550 let con_name = short_fn_name(self.tcx, con_id);
551 let mut chain_parts: Vec<String> = vec![con_name];
552 for &mut_id in mut_ids {
553 chain_parts.push(short_fn_name(self.tcx, mut_id));
554 }
555 chain_parts.push(read_name);
556 let chain_label = chain_parts.join(" -> ");
557
558 rap_info!("============================================================");
559 rap_info!("[rapx::verify] sequence: {chain_label}");
560 rap_info!("============================================================");
561
562 let unproved = all_results
563 .iter()
564 .filter(|r| !matches!(r.result, super::report::CheckResult::Proved))
565 .count();
566
567 let mut groups: IndexMap<(CheckpointLocation, String), Vec<&PropertyCheckResult<'_>>> =
568 IndexMap::new();
569 for r in &all_results {
570 groups
571 .entry((r.checkpoint, r.callee_name.clone()))
572 .or_default()
573 .push(r);
574 }
575
576 let checkpoint_groups: Vec<_> = groups
577 .iter()
578 .filter(|((_, name), _)| !name.starts_with("struct-invariant"))
579 .collect();
580
581 if !checkpoint_groups.is_empty() {
582 rap_info!(" --- unsafe checkpoints ---");
583 for ((checkpoint, callee_name), results) in &checkpoint_groups {
584 rap_info!(
585 " unsafe checkpoint: bb{} -> {callee_name}",
586 checkpoint.block.as_usize(),
587 );
588 emit_property_rows(results);
589 }
590 }
591
592 if unproved == 0 && !all_results.is_empty() {
593 rap_info!(green," result: SOUND");
594 } else {
595 rap_warn!(" result: UNSOUND ({unproved} unproved)");
596 }
597 rap_info!("");
598 }
599}
600
601impl<'tcx> Analysis for VerifyRun<'tcx> {
602 fn name(&self) -> &'static str {
603 "Verify Driver"
604 }
605
606 fn run(&mut self) {
613 let mut collector = VerifyTargetCollector::new(
614 self.tcx,
615 self.mode,
616 self.crate_filter.clone(),
617 self.module_filter.clone(),
618 );
619 self.tcx.hir_visit_all_item_likes_in_crate(&mut collector);
620 collector.check_module_filter_result();
621
622 for target in &collector.function_targets {
623 let target_path = self.tcx.def_path_str(target.def_id);
624 let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
625
626 for repeat in 0..=self.postfix_repeat {
628 let driver = VerifyDriver::new_with_repeat(self.tcx, target, repeat);
629 let result = catch_unwind(AssertUnwindSafe(|| driver.verify_function()));
630 match result {
631 Ok(report) => {
632 rap_debug!("{}", report.describe());
633 all_results.extend(report.results);
634 }
635 Err(e) => {
636 let msg = e
637 .downcast_ref::<String>()
638 .map(|s| s.as_str())
639 .or_else(|| e.downcast_ref::<&str>().copied())
640 .unwrap_or("<rustc ICE>");
641 rap_warn!(
642 "Skipping function {} (repeat {}): {}",
643 target_path,
644 repeat,
645 msg
646 );
647 all_results.clear();
648 break;
649 }
650 }
651 }
652
653 const MAX_LOOP_INBOUND_UNROLL: usize = 16;
665
666 if self.postfix_repeat == 0 && !all_results.is_empty() {
667 let has_inbound = all_results
668 .iter()
669 .any(|r| matches!(r.property.kind, PropertyKind::InBound));
670 let all_inbound_proven = all_results
671 .iter()
672 .filter(|r| matches!(r.property.kind, PropertyKind::InBound))
673 .all(|r| matches!(r.result, super::report::CheckResult::Proved));
674
675 if has_inbound && all_inbound_proven {
676 let mut proven_history: Vec<Vec<(usize, usize)>> = Vec::new();
679
680 let seed: Vec<_> = {
682 let mut v: Vec<_> = all_results
683 .iter()
684 .filter(|r| {
685 matches!(r.property.kind, PropertyKind::InBound)
686 && matches!(r.result, super::report::CheckResult::Proved)
687 })
688 .map(|r| (r.checkpoint_index, r.property_index))
689 .collect();
690 v.sort();
691 v
692 };
693 proven_history.push(seed);
694
695 for repeat in 1..=MAX_LOOP_INBOUND_UNROLL {
696 let detector_driver =
697 VerifyDriver::new_with_repeat(self.tcx, target, repeat);
698 let result =
699 catch_unwind(AssertUnwindSafe(|| detector_driver.verify_function()));
700 match result {
701 Ok(report) => {
702 let current_unproven: FxHashSet<_> = report
705 .results
706 .iter()
707 .filter(|r| {
708 matches!(r.property.kind, PropertyKind::InBound)
709 && !matches!(
710 r.result,
711 super::report::CheckResult::Proved
712 )
713 && !r
714 .diagnostics
715 .as_ref()
716 .is_some_and(|d| {
717 d.forward.contains("path has precision loss")
718 || d.forward
719 .contains("could not connect")
720 })
721 })
722 .map(|r| (r.checkpoint_index, r.property_index))
723 .collect();
724
725 if !current_unproven.is_empty() {
727 all_results.extend(report.results);
728 break;
729 }
730
731 let mut current_proven: Vec<_> = report
733 .results
734 .iter()
735 .filter(|r| {
736 matches!(r.property.kind, PropertyKind::InBound)
737 && matches!(
738 r.result,
739 super::report::CheckResult::Proved
740 )
741 })
742 .map(|r| (r.checkpoint_index, r.property_index))
743 .collect();
744 current_proven.sort();
745
746 if proven_history.contains(¤t_proven) {
748 break;
749 }
750
751 let len = proven_history.len();
753 if len >= 3
754 && proven_history[len - 1] == proven_history[len - 3]
755 && current_proven == proven_history[len - 2]
756 {
757 break;
758 }
759
760 proven_history.push(current_proven);
761 }
762 Err(_) => break,
763 }
764 }
765 }
766 }
767
768 if !target.struct_invariants.is_empty() && !matches!(self.mode, VerifyMode::Invless) {
770 let driver = VerifyDriver::new_with_repeat(self.tcx, target, self.postfix_repeat);
771 let struct_report = driver.verify_struct_invariants();
772 rap_debug!("{}", struct_report.describe());
773 all_results.extend(struct_report.results);
774 }
775
776 if all_results.is_empty() {
777 if target.checkpoints.is_empty()
778 && target.raw_ptr_deref_checks.is_empty()
779 && target.static_mut_checks.is_empty()
780 && target.struct_invariants.is_empty()
781 {
782 rap_info!("============================================================");
783 rap_info!("[rapx::verify] function: {target_path}");
784 rap_info!("============================================================");
785 if matches!(self.mode, VerifyMode::Invless) {
786 let cons = get_cons(self.tcx, target.def_id);
787 for con in &cons {
788 rap_info!(" + constructor: {}", self.tcx.def_path_str(*con));
789 }
790 }
791 rap_info!(" --- unsafe checkpoints ---");
792 rap_info!(" <none>");
793 rap_info!(" <none>");
794 rap_info!(" result: SOUND (no unsafe checkpoints)");
795 rap_info!("");
796 }
797 continue;
798 }
799
800 if matches!(self.mode, VerifyMode::Invless)
803 && !get_cons(self.tcx, target.def_id).is_empty()
804 {
805 continue;
806 }
807
808 emit_verify_summary(
809 self.tcx,
810 &target_path,
811 target.def_id,
812 &all_results,
813 self.mode,
814 );
815 }
816
817 if !collector.trait_targets.is_empty() {
819 let mut trait_ids: Vec<_> = collector.trait_targets.keys().copied().collect();
820 trait_ids.sort_by_key(|def_id| self.tcx.def_path_str(*def_id));
821 for trait_def_id in trait_ids {
822 let Some(trait_target) = collector.trait_targets.get(&trait_def_id) else {
823 continue;
824 };
825 rap_info!("============================================================");
826 rap_info!(
827 "[rapx::verify] unsafe trait impl: {}",
828 self.tcx.def_path_str(trait_target.def_id)
829 );
830 rap_info!("============================================================");
831 if let Some(self_ty) = trait_target.self_ty_def_id {
832 rap_info!(" impl for: {}", self.tcx.def_path_str(self_ty));
833 }
834 if trait_target.ensures.is_empty() {
835 rap_info!(" ensures: <none>");
836 } else {
837 rap_info!(" ensures (implementor must satisfy):");
838 for (method_name, contracts) in &trait_target.ensures {
839 rap_info!(" fn {}:", method_name);
840 for property in contracts {
841 rap_info!(" - {:?}, args={:?}", property.kind, property.args);
842 }
843 }
844 }
845 rap_info!(" verification: deferred");
846 rap_info!("");
847 }
848 }
849
850 if matches!(self.mode, VerifyMode::Invless) {
852 self.run_invless_sequences(&collector.function_targets);
853 }
854 }
855
856 fn reset(&mut self) {}
857}
858
859fn emit_verify_summary<'tcx>(
860 tcx: TyCtxt<'tcx>,
861 target_path: &str,
862 def_id: rustc_hir::def_id::DefId,
863 all_results: &[PropertyCheckResult<'tcx>],
864 mode: VerifyMode,
865) {
866 let unproved = all_results
867 .iter()
868 .filter(|r| !matches!(r.result, super::report::CheckResult::Proved))
869 .count();
870
871 rap_info!("============================================================");
872 rap_info!("[rapx::verify] function: {target_path}");
873 rap_info!("============================================================");
874
875 if matches!(mode, VerifyMode::Invless) {
876 let cons = get_cons(tcx, def_id);
877 for con in &cons {
878 rap_info!(" + constructor: {}", tcx.def_path_str(*con));
879 }
880 }
881
882 let mut groups: IndexMap<(CheckpointLocation, String), Vec<&PropertyCheckResult<'_>>> =
884 IndexMap::new();
885 for r in all_results {
886 groups
887 .entry((r.checkpoint, r.callee_name.clone()))
888 .or_default()
889 .push(r);
890 }
891
892 let checkpoint_groups: Vec<_> = groups
894 .iter()
895 .filter(|((_, name), _)| !name.starts_with("struct-invariant"))
896 .collect();
897 let invariant_groups: Vec<_> = groups
898 .iter()
899 .filter(|((_, name), _)| name.starts_with("struct-invariant"))
900 .collect();
901
902 if !checkpoint_groups.is_empty() {
904 rap_info!(" --- unsafe checkpoints ---");
905 for ((checkpoint, callee_name), results) in &checkpoint_groups {
906 rap_info!(
907 " unsafe checkpoint: bb{} -> {callee_name}",
908 checkpoint.block.as_usize(),
909 );
910 emit_property_rows(results);
911 }
912 }
913
914 if !invariant_groups.is_empty() {
916 rap_info!(" --- struct invariants ---");
917 for ((checkpoint, _), results) in &invariant_groups {
918 rap_info!(" checkpoint bb{}:", checkpoint.block.as_usize(),);
919 emit_property_rows(results);
920 }
921 }
922
923 if unproved == 0 {
924 rap_info!(green," result: SOUND");
925 } else {
926 rap_warn!(" result: UNSOUND ({unproved} unproved)");
927 }
928
929 rap_info!("");
930}
931
932fn emit_property_rows(results: &[&PropertyCheckResult<'_>]) {
933 let mut path_groups: FxHashMap<&str, Vec<_>> = FxHashMap::default();
934 for r in results.iter() {
935 path_groups
936 .entry(r.path_description.as_str())
937 .or_default()
938 .push(r);
939 }
940 for (path_desc, props) in &path_groups {
941 rap_info!(" path {path_desc}:");
942 for r in props.iter() {
943 let line = format!(" {:?} | {:?}", r.property.kind, r.result);
944 if matches!(r.result, super::report::CheckResult::Proved) {
945 rap_info!(green,"{line}");
946 } else {
947 rap_warn!("{line}");
948 }
949 }
950 }
951}
952
953pub struct VerifyVisitDump<'tcx> {
955 tcx: TyCtxt<'tcx>,
956 postfix_repeat: usize,
957 mode: VerifyMode,
958}
959
960fn short_fn_name(tcx: TyCtxt<'_>, def_id: rustc_hir::def_id::DefId) -> String {
962 let path = tcx.def_path_str(def_id);
963 path.rsplit("::").next().unwrap_or(&path).to_string()
964}
965
966fn same_property(
968 a: &crate::verify::contract::Property<'_>,
969 b: &crate::verify::contract::Property<'_>,
970) -> bool {
971 matches!(
972 (&a.kind, &b.kind),
973 (PropertyKind::Align, PropertyKind::Align)
974 | (PropertyKind::InBound, PropertyKind::InBound)
975 | (PropertyKind::Init, PropertyKind::Init)
976 | (PropertyKind::NonNull, PropertyKind::NonNull)
977 | (PropertyKind::ValidPtr, PropertyKind::ValidPtr)
978 )
979}
980
981fn property_field_indices(property: &crate::verify::contract::Property<'_>) -> Vec<usize> {
986 use crate::verify::contract::{ContractExpr, PropertyArg};
987 let mut indices = Vec::new();
988 for arg in &property.args {
989 let place = match arg {
990 PropertyArg::Place(p) => Some(p),
991 PropertyArg::Expr(ContractExpr::Place(p)) => Some(p),
992 _ => None,
993 };
994 if let Some(place) = place {
995 for proj in &place.projections {
996 let crate::verify::contract::ContractProjection::Field { index, .. } = proj;
997 let idx = *index;
998 if !indices.contains(&idx) {
999 indices.push(idx);
1000 }
1001 }
1002 }
1003 }
1004 indices
1005}
1006
1007impl<'tcx> VerifyVisitDump<'tcx> {
1008 pub fn new(tcx: TyCtxt<'tcx>, postfix_repeat: usize, mode: VerifyMode) -> Self {
1010 Self {
1011 tcx,
1012 postfix_repeat,
1013 mode,
1014 }
1015 }
1016}
1017
1018impl<'tcx> Analysis for VerifyVisitDump<'tcx> {
1019 fn name(&self) -> &'static str {
1020 "Verify Visitor Diagnostic Dump"
1021 }
1022
1023 fn run(&mut self) {
1025 rap_debug!("======== #[rapx::verify] visitor diagnostics ========");
1026 let mut collector = VerifyTargetCollector::new(self.tcx, self.mode, None, None);
1027 self.tcx.hir_visit_all_item_likes_in_crate(&mut collector);
1028
1029 for target in &collector.function_targets {
1030 let target_path = self.tcx.def_path_str(target.def_id);
1031 rap_debug!(
1032 "[rapx::verify::diagnostics] target: {} (DefId: {:?})",
1033 target_path,
1034 target.def_id
1035 );
1036
1037 for repeat in 0..=self.postfix_repeat {
1038 if self.postfix_repeat > 0 {
1039 rap_debug!(
1040 "[rapx::verify::diagnostics] round {}/{}: postfix-repeat={}",
1041 repeat,
1042 self.postfix_repeat,
1043 repeat
1044 );
1045 }
1046 let driver = VerifyDriver::new_with_repeat(self.tcx, target, repeat);
1047 let result = catch_unwind(AssertUnwindSafe(|| driver.verify_function()));
1048 match result {
1049 Ok(report) => {
1050 rap_debug!("{}", report.describe());
1051 }
1052 Err(_) => {
1053 rap_debug!(
1054 "[rapx::verify::diagnostics] function {} skipped due to ICE",
1055 self.tcx.def_path_str(target.def_id)
1056 );
1057 }
1058 }
1059 }
1060 }
1061
1062 rap_debug!("=======================================");
1063 }
1064
1065 fn reset(&mut self) {}
1066}