1use crate::analysis::Analysis;
10use crate::analysis::path::{
11 PathTree,
12 graph::{PathEnumerator, PathGraph},
13};
14use crate::cli::VerifyMode;
15use crate::helpers::fn_info::{
16 FnKind, get_cons, get_mutated_fields, get_muts, get_type, returns_wrapped_self,
17};
18use crate::verify::contract::PropertyKind;
19use crate::verify::property_checker::{
20 atomic_update_check, contain_no_type_check, field_invariant_check, no_internal_mut_check,
21 no_raw_ptr_check, ref_send_check, uni_internal_mut_check,
22};
23use crate::verify::target::get_contract_from_annotation;
24
25use crate::compat::FxHashMap;
26use crate::compat::FxHashSet;
27use rustc_middle::mir::BasicBlock;
28use rustc_middle::ty::TyCtxt;
29
30use super::{
31 contract::{PlaceBase, Property, PropertyArg},
32 display::{
33 dedup_compound_props, emit_results_and_verdict, emit_verify_summary, fmt_contract_expanded,
34 fmt_fn_path_with_bounds, fmt_fn_path_with_generics, fmt_fn_with_params,
35 },
36 engine::VerifyEngine,
37 loop_sensitivity::{LoopSensitivityAnalyzer, RepeatStrategy},
38 path_extractor::{CallGroup, PathExtractor},
39 report::{CheckResult, PropertyCheckResult, UnknownReason, VerificationReport},
40 slicer::RelevantItem,
41 target::{
42 FunctionTarget, MarkerTraitKind, TraitEnsurance, TraitEnsuranceKind, VerifyTargetCollector,
43 },
44};
45
46use crate::helpers::mir_utils::{collect_return_block_indices, is_return_block};
47
48use crate::helpers::mir_scan::{Checkpoint, CheckpointLocation};
49
50pub(crate) struct VerifyDriver<'target, 'tcx> {
79 tcx: TyCtxt<'tcx>,
80
81 target: &'target FunctionTarget<'tcx>,
82
83 path_info: Vec<CallGroup<'tcx>>,
84
85 engine: VerifyEngine<'tcx>,
86
87 allow_repeat: usize,
88}
89
90impl<'target, 'tcx> VerifyDriver<'target, 'tcx> {
91 pub(crate) fn new_with_repeat(
92 tcx: TyCtxt<'tcx>,
93 target: &'target FunctionTarget<'tcx>,
94 allow_repeat: usize,
95 ) -> Self {
96 let all_checkpoints: Vec<_> = target.all_checkpoints().into_iter().cloned().collect();
97 let path_info = PathExtractor::new(tcx, target.def_id, all_checkpoints, allow_repeat).run();
98 Self {
99 tcx,
100 target,
101 path_info,
102 engine: VerifyEngine::new(tcx),
103 allow_repeat,
104 }
105 }
106
107 pub(crate) fn verify_function(&self) -> VerificationReport<'tcx> {
109 let mut report = VerificationReport::new(self.target.def_id);
110
111 for view in self.iter_callsite_checks() {
112 let mut view_results: Vec<PropertyCheckResult<'tcx>> = Vec::new();
113
114 for (property_index, property) in view.properties.iter().enumerate() {
115 let bulk = self.check_property_paths(&view, property);
116 for (path_index, (result, path_desc)) in bulk.iter().enumerate() {
117 let item = PropertyCheckResult {
118 checkpoint: view.checkpoint.location(),
119 checkpoint_index: view.checkpoint_index,
120 path_index,
121 property_index,
122 property: property.clone(),
123 result: result.clone(),
124 diagnostics: Some(format!("vm-check: {:?}", result)),
125 path_description: path_desc.clone(),
126 callee_name: view.checkpoint.callee_name(self.tcx),
127 };
128 view_results.push(item);
129 }
130 if view.tree.is_truncated() {
135 view_results.push(PropertyCheckResult {
136 checkpoint: view.checkpoint.location(),
137 checkpoint_index: view.checkpoint_index,
138 path_index: bulk.len(),
139 property_index,
140 property: property.clone(),
141 result: CheckResult::ProvedByRule,
142 diagnostics: Some("path enumeration stopped at its limit".to_string()),
143 path_description: "[not enumerated: path limit reached]".to_string(),
144 callee_name: view.checkpoint.callee_name(self.tcx),
145 });
146 }
147 }
148
149 for item in view_results {
150 report.push(item);
151 }
152 }
153
154 report
155 }
156
157 fn check_property_paths(
161 &self,
162 view: &CheckpointCheckView<'_, '_, 'tcx>,
163 property: &Property<'tcx>,
164 ) -> Vec<(CheckResult, String)> {
165 match property {
166 Property::Atom(atom)
167 if atom.kind == PropertyKind::Alias
168 && crate::verify::api_classify::is_manually_drop_drop(
169 view.checkpoint.callee,
170 ) =>
171 {
172 self.engine
173 .check_drop_from_tree(view.tree, view.checkpoint)
174 }
175 Property::Atom(_) => self.engine.check_callsite_from_tree(
176 view.tree,
177 view.checkpoint,
178 property,
179 &self.target.caller_requires,
180 ),
181 Property::And(and) => {
182 self.combine_check_paths(view, &and.conjuncts, CheckResult::and, |r| {
183 matches!(r, CheckResult::Failed | CheckResult::Unknown(_))
184 })
185 }
186 Property::Or(or) => {
187 self.combine_check_paths(view, &or.disjuncts, CheckResult::or, |r| r.is_proved())
188 }
189 }
190 }
191
192 fn combine_check_paths(
195 &self,
196 view: &CheckpointCheckView<'_, '_, 'tcx>,
197 children: &[Box<Property<'tcx>>],
198 fold: fn(CheckResult, CheckResult) -> CheckResult,
199 replace_desc_on: fn(&CheckResult) -> bool,
200 ) -> Vec<(CheckResult, String)> {
201 let mut per_path: Vec<Option<(CheckResult, String)>> = Vec::new();
202 for child in children {
203 let bulk = self.check_property_paths(view, child);
204 if per_path.is_empty() {
205 per_path.resize(bulk.len(), None);
206 }
207 for (i, (result, desc)) in bulk.iter().enumerate() {
208 let slot = per_path[i].get_or_insert_with(|| (result.clone(), desc.clone()));
209 slot.0 = fold(slot.0.clone(), result.clone());
210 if replace_desc_on(result) {
211 slot.1 = desc.clone();
212 }
213 }
214 }
215 per_path.into_iter().map(|x| x.unwrap()).collect()
216 }
217
218 pub(crate) fn properties_for_callsite(
225 &self,
226 checkpoint: &Checkpoint<'tcx>,
227 ) -> &'target [Property<'tcx>] {
228 self.target.properties_for_callsite(checkpoint)
229 }
230
231 pub(crate) fn iter_callsite_checks(
233 &self,
234 ) -> impl Iterator<Item = CheckpointCheckView<'_, 'target, 'tcx>> + '_ {
235 let mut checkpoint_index = 0usize;
236 self.path_info.iter().flat_map(move |group| {
237 group.checkpoints.iter().filter_map(move |checkpoint| {
238 let properties = self.properties_for_callsite(checkpoint);
239 if properties.is_empty() {
240 return None;
241 }
242 let view = CheckpointCheckView {
243 checkpoint_index,
244 checkpoint,
245 tree: &group.tree,
246 properties,
247 };
248 checkpoint_index += 1;
249 Some(view)
250 })
251 })
252 }
253
254 pub(crate) fn verify_struct_invariants(&self) -> VerificationReport<'tcx> {
261 let mut report = VerificationReport::new(self.target.def_id);
262 let invariants = &self.target.struct_invariants;
263 if invariants.is_empty() {
264 return report;
265 }
266
267 let is_constructor = get_type(self.tcx, self.target.def_id) == FnKind::Constructor;
268 let caller_contracts = &self.target.caller_requires;
269
270 let fn_sig = self.tcx.fn_sig(self.target.def_id).skip_binder();
271 let output = fn_sig.output().skip_binder();
272 let returns_self = is_constructor || output.is_param(0);
273
274 let entry_facts: Vec<RelevantItem<'tcx>> = caller_contracts
280 .iter()
281 .filter(|c| !matches!(c.kind(), Some(PropertyKind::Unknown)))
282 .map(|c| RelevantItem::ContractFact {
283 property: c.clone(),
284 })
285 .collect();
286
287 report.results.extend(self.run_invariant_checks(
288 invariants,
289 &entry_facts,
290 is_constructor,
291 !is_constructor,
292 "struct",
293 ));
294
295 let wrapped_self = returns_wrapped_self(self.tcx, self.target.def_id);
301 if (!returns_self && !is_constructor) || (is_constructor && wrapped_self) {
302 let has_failed = report
303 .results
304 .iter()
305 .any(|r| matches!(r.result, CheckResult::Failed));
306 if !has_failed {
307 report
308 .results
309 .retain(|r| !matches!(r.result, CheckResult::Unknown(_)));
310 }
311 }
312
313 report
314 }
315
316 pub(crate) fn verify_type_invariants(&self) -> VerificationReport<'tcx> {
321 let mut report = VerificationReport::new(self.target.def_id);
322 let invariants = &self.target.type_invariants;
323 if invariants.is_empty() {
324 return report;
325 }
326
327 let entry_facts: Vec<RelevantItem<'tcx>> = invariants
328 .iter()
329 .map(|inv| RelevantItem::ContractFact {
330 property: inv.clone(),
331 })
332 .collect();
333
334 report.results.extend(self.run_invariant_checks(
335 invariants,
336 &entry_facts,
337 false,
338 false,
339 "type",
340 ));
341
342 let has_failed = report
345 .results
346 .iter()
347 .any(|r| matches!(r.result, CheckResult::Failed));
348 if !has_failed {
349 report
350 .results
351 .retain(|r| !matches!(r.result, CheckResult::Unknown(_)));
352 }
353
354 report
355 }
356
357 fn run_invariant_checks(
362 &self,
363 invariants: &[Property<'tcx>],
364 entry_facts: &[RelevantItem<'tcx>],
365 is_constructor: bool,
366 check_unwind: bool,
367 label: &str,
368 ) -> Vec<PropertyCheckResult<'tcx>> {
369 let mut results = Vec::new();
370 for (checkpoint, tree) in self.build_invariant_trees(is_constructor, check_unwind) {
371 rap_debug!(
372 "[rapx::verify] {label} invariant checkpoint bb{}: {} tree node(s)",
373 checkpoint.block.as_usize(),
374 tree.len()
375 );
376
377 let paths = tree.to_vecs();
378
379 let is_return = is_return_block(self.tcx, self.target.def_id, checkpoint.block);
384
385 for (property_index, invariant) in invariants.iter().enumerate() {
386 if !is_return && targets_return_value(invariant) {
387 continue;
388 }
389 let check_results = self.engine.check_invariant_from_tree(
390 self.target.def_id,
391 &tree,
392 checkpoint,
393 invariant,
394 entry_facts,
395 );
396
397 for (path_index, (result, _path_desc)) in check_results.iter().enumerate() {
398 let path_description = paths
399 .get(path_index)
400 .map(|p| {
401 p.iter()
402 .map(|b| b.to_string())
403 .collect::<Vec<_>>()
404 .join(", ")
405 })
406 .unwrap_or_default();
407 results.push(PropertyCheckResult {
408 checkpoint,
409 checkpoint_index: checkpoint.block.as_usize(),
410 path_index,
411 property_index,
412 property: invariant.clone(),
413 result: result.clone(),
414 diagnostics: Some(format!("vm-{label}-invariant: {:?}", result)),
415 path_description,
416 callee_name: format!(
417 "{label}-invariant(bb{})",
418 checkpoint.block.as_usize()
419 ),
420 });
421 }
422 }
423 }
424 results
425 }
426
427 fn build_invariant_trees(
428 &self,
429 is_constructor: bool,
430 check_unwind: bool,
431 ) -> FxHashMap<CheckpointLocation, PathTree> {
432 let mut pg = PathGraph::new(self.tcx, self.target.def_id);
433 pg.find_scc();
434 let mut enumerator = PathEnumerator::new(&pg);
435 let all_paths = enumerator.enumerate_paths_repeat(self.allow_repeat);
436
437 let kind_label = if is_constructor {
438 "constructor"
439 } else {
440 "method"
441 };
442 rap_debug!(
443 "[rapx::verify] struct invariant ({kind_label}): {} whole-cfg path(s) for {}",
444 all_paths.len(),
445 self.tcx.def_path_str(self.target.def_id),
446 );
447
448 let mut trees_by_checkpoint: FxHashMap<CheckpointLocation, PathTree> = FxHashMap::default();
449
450 if !check_unwind {
451 let return_blocks = collect_return_block_indices(self.tcx, self.target.def_id);
452 for &return_block in &return_blocks {
453 let checkpoint = CheckpointLocation {
454 caller: self.target.def_id,
455 block: return_block,
456 };
457 let mut tree = PathTree::new();
458 let _ = all_paths.walk_prefixes(
459 return_block.as_usize(),
460 &mut |prefix: &[usize]| -> bool {
461 if tree.len() >= crate::limit::path_limit() {
462 return false;
463 }
464 tree.insert(prefix);
465 true
466 },
467 );
468 if !tree.is_empty() {
469 trees_by_checkpoint.insert(checkpoint, tree);
470 }
471 }
472 } else {
473 let mut exit_blocks: FxHashSet<BasicBlock> = FxHashSet::default();
482 for path in all_paths.to_vecs() {
483 if let Some(&last) = path.last() {
484 exit_blocks.insert(BasicBlock::from_usize(last));
485 }
486 }
487 for exit_block in exit_blocks {
488 let checkpoint = CheckpointLocation {
489 caller: self.target.def_id,
490 block: exit_block,
491 };
492 let mut tree = PathTree::new();
493 let _ = all_paths.walk_prefixes(
494 exit_block.as_usize(),
495 &mut |prefix: &[usize]| -> bool {
496 if tree.len() >= crate::limit::path_limit() {
497 return false;
498 }
499 tree.insert(prefix);
500 true
501 },
502 );
503 if !tree.is_empty() {
504 trees_by_checkpoint.insert(checkpoint, tree);
505 }
506 }
507 }
508
509 trees_by_checkpoint
510 }
511}
512
513pub(crate) struct CheckpointCheckView<'view, 'target, 'tcx> {
516 pub checkpoint_index: usize,
518 pub checkpoint: &'view Checkpoint<'tcx>,
520 pub tree: &'view PathTree,
522 pub properties: &'target [Property<'tcx>],
524}
525
526pub(crate) struct VerifyRun<'tcx> {
528 tcx: TyCtxt<'tcx>,
529 repeat_strategy: RepeatStrategy,
530 mode: VerifyMode,
531 skip_invariant: bool,
532 crate_filter: Option<String>,
533 module_filter: Option<String>,
534 debug_contracts: bool,
535 struct_invariant_results: FxHashMap<rustc_hir::def_id::DefId, CheckResult>,
538}
539
540impl<'tcx> VerifyRun<'tcx> {
541 pub(crate) fn new(
543 tcx: TyCtxt<'tcx>,
544 repeat_strategy: RepeatStrategy,
545 mode: VerifyMode,
546 skip_invariant: bool,
547 crate_filter: Option<String>,
548 module_filter: Option<String>,
549 debug_contracts: bool,
550 ) -> Self {
551 Self {
552 tcx,
553 repeat_strategy,
554 mode,
555 skip_invariant,
556 crate_filter,
557 module_filter,
558 debug_contracts,
559 struct_invariant_results: FxHashMap::default(),
560 }
561 }
562
563 fn repeat_rounds_for_target(&self, target: &FunctionTarget<'tcx>) -> (usize, Vec<usize>) {
564 match self.repeat_strategy {
565 RepeatStrategy::Fixed(n) => (n, (0..=n).collect()),
566 RepeatStrategy::Auto => {
567 let plan = LoopSensitivityAnalyzer::new(self.tcx).analyze(target);
568 let repeat = plan.repeat;
569 (repeat, (0..=repeat).collect())
570 }
571 }
572 }
573
574 fn run_invless_sequences(&self, targets: &[FunctionTarget<'tcx>]) {
584 for target in targets {
585 let read_def_id = target.def_id;
586 let cons = get_cons(self.tcx, read_def_id);
587 if cons.is_empty() {
588 continue;
589 }
590 let muts = get_muts(self.tcx, read_def_id);
591
592 for &con_id in &cons {
593 let con_target = self.build_virtual_target(target, read_def_id, con_id, &[]);
594 self.verify_and_emit_sequence(read_def_id, &con_target, con_id, &[]);
595
596 for &mut_id in &muts {
597 let con_target =
598 self.build_virtual_target(target, read_def_id, con_id, &[mut_id]);
599 self.verify_and_emit_sequence(read_def_id, &con_target, con_id, &[mut_id]);
600 }
601 }
602 }
603 }
604
605 fn build_virtual_target(
606 &self,
607 read_target: &FunctionTarget<'tcx>,
608 read_def_id: rustc_hir::def_id::DefId,
609 con_id: rustc_hir::def_id::DefId,
610 mut_ids: &[rustc_hir::def_id::DefId],
611 ) -> FunctionTarget<'tcx> {
612 let mut accumulated_requires: Vec<Property<'tcx>> = Vec::new();
613
614 let con_contracts: Vec<Property<'tcx>> = get_contract_from_annotation(self.tcx, con_id)
617 .into_iter()
618 .map(|c| remap_constructor_contract(c))
619 .collect();
620 accumulated_requires.extend(con_contracts);
621
622 if !mut_ids.is_empty() {
624 let mut mutated_fields: Vec<usize> = Vec::new();
625 for &mut_id in mut_ids {
626 for field_idx in get_mutated_fields(self.tcx, mut_id) {
627 if !mutated_fields.contains(&field_idx) {
628 mutated_fields.push(field_idx);
629 }
630 }
631 }
632 if !mutated_fields.is_empty() {
633 accumulated_requires.retain(|prop| {
634 let prop_fields = property_field_indices(prop);
635 !prop_fields.iter().any(|f| mutated_fields.contains(f))
636 });
637 }
638 }
639
640 accumulated_requires.extend(read_target.caller_requires.clone());
646
647 FunctionTarget {
648 def_id: read_def_id,
649 owner_struct_def_id: read_target.owner_struct_def_id,
650 checkpoints: read_target.checkpoints.clone(),
651 callee_requires: read_target.callee_requires.clone(),
652 caller_requires: accumulated_requires,
653 struct_invariants: Vec::new(),
654 type_invariants: Vec::new(),
655 raw_ptr_deref_checks: read_target.raw_ptr_deref_checks.clone(),
656 static_mut_checks: read_target.static_mut_checks.clone(),
657 }
658 }
659
660 fn run_repeat_rounds(
663 &self,
664 target: &FunctionTarget<'tcx>,
665 repeat_rounds: &[usize],
666 skip_label: &str,
667 all_results: &mut Vec<PropertyCheckResult<'tcx>>,
668 ) -> Option<String> {
669 for &repeat in repeat_rounds {
670 let driver = VerifyDriver::new_with_repeat(self.tcx, target, repeat);
671 match crate::helpers::mir_utils::catch_panic(|| driver.verify_function()) {
672 Ok(report) => {
673 rap_debug!("{}", report.describe());
674 all_results.extend(report.results);
675 }
676 Err(msg) => {
677 rap_warn!("Skipping {} (repeat {}): {msg}", skip_label, repeat);
678 all_results.clear();
679 return Some(format!("repeat {repeat}: {msg}"));
680 }
681 }
682 }
683 None
684 }
685
686 fn verify_and_emit_sequence(
687 &self,
688 read_def_id: rustc_hir::def_id::DefId,
689 con_target: &FunctionTarget<'tcx>,
690 con_id: rustc_hir::def_id::DefId,
691 mut_ids: &[rustc_hir::def_id::DefId],
692 ) {
693 let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
694
695 let (_, repeat_rounds) = self.repeat_rounds_for_target(con_target);
696 let crashed = self.run_repeat_rounds(
697 con_target,
698 &repeat_rounds,
699 &format!("constructor {}", self.tcx.def_path_str(con_id)),
700 &mut all_results,
701 );
702
703 let read_name = short_fn_name(self.tcx, read_def_id);
704 let con_name = short_fn_name(self.tcx, con_id);
705 let mut chain_parts: Vec<String> = vec![con_name];
706 for &mut_id in mut_ids {
707 chain_parts.push(short_fn_name(self.tcx, mut_id));
708 }
709 chain_parts.push(read_name);
710 let chain_label = chain_parts.join(" -> ");
711
712 rap_info!("============================================================");
713 rap_info!("[rapx::verify] sequence: {chain_label}");
714 rap_info!("============================================================");
715
716 if let Some(msg) = &crashed {
717 rap_warn!(" result: UNKNOWN (verifier crashed: {msg})");
718 } else if all_results.is_empty() {
719 rap_info!(" result: SOUND (no unsafe checkpoints)");
720 } else {
721 emit_results_and_verdict(self.tcx, &all_results);
722 }
723 rap_info!("");
724 }
725}
726
727impl<'tcx> Analysis for VerifyRun<'tcx> {
728 fn run(&mut self) {
735 crate::verify::contract::compound::register_compound_properties(self.tcx);
737
738 let collector = VerifyTargetCollector::collect_all(
739 self.tcx,
740 self.mode,
741 self.skip_invariant,
742 self.crate_filter.clone(),
743 self.module_filter.clone(),
744 );
745
746 if self.debug_contracts {
747 self.print_contracts_debug(&collector.function_targets, &collector.trait_targets);
748 return;
749 }
750
751 for target in &collector.function_targets {
752 let target_path = fmt_fn_path_with_bounds(self.tcx, target.def_id);
753 let mut all_results: Vec<PropertyCheckResult<'_>> = Vec::new();
754 let mut fn_crashed: Option<String>;
755
756 let (planned_repeat, repeat_rounds) = self.repeat_rounds_for_target(target);
757
758 fn_crashed = self.run_repeat_rounds(
760 target,
761 &repeat_rounds,
762 &format!("function {}", target_path),
763 &mut all_results,
764 );
765
766 if !target.struct_invariants.is_empty() && !self.skip_invariant {
768 let driver = VerifyDriver::new_with_repeat(self.tcx, target, planned_repeat);
769 match crate::helpers::mir_utils::catch_panic(|| driver.verify_struct_invariants()) {
770 Ok(struct_report) => {
771 rap_debug!("{}", struct_report.describe());
772 all_results.extend(struct_report.results.clone());
773 if let Some(struct_id) = target.owner_struct_def_id {
777 let verdict = struct_report
778 .results
779 .iter()
780 .fold(CheckResult::ProvedByRule, |acc, r| {
781 acc.and(r.result.clone())
782 });
783 self.struct_invariant_results
784 .entry(struct_id)
785 .and_modify(|v| *v = v.clone().and(verdict.clone()))
786 .or_insert(verdict);
787 }
788 }
789 Err(msg) => {
790 rap_warn!("Skipping struct invariants for {} : {msg}", target_path);
791 all_results.clear();
792 fn_crashed = Some(format!("struct-invariant: {msg}"));
793 }
794 }
795 }
796
797 if !target.type_invariants.is_empty() && !self.skip_invariant {
799 let driver = VerifyDriver::new_with_repeat(self.tcx, target, planned_repeat);
800 match crate::helpers::mir_utils::catch_panic(|| driver.verify_type_invariants()) {
801 Ok(type_report) => {
802 rap_debug!("{}", type_report.describe());
803 all_results.extend(type_report.results.clone());
804 }
805 Err(msg) => {
806 rap_warn!("Skipping type invariants for {} : {msg}", target_path);
807 all_results.clear();
808 fn_crashed = Some(format!("type-invariant: {msg}"));
809 }
810 }
811 }
812
813 if let Some(msg) = &fn_crashed {
814 rap_info!("============================================================");
815 rap_info!("[rapx::verify] function: {target_path}");
816 rap_info!("============================================================");
817 rap_warn!(" result: UNKNOWN (verifier crashed: {msg})");
818 rap_info!("");
819 continue;
820 }
821
822 if all_results.is_empty() {
823 let all_callees_skipped = !target.checkpoints.is_empty()
824 && target.checkpoints.iter().all(|ckpt| {
825 ckpt.callee.is_some_and(|callee| {
826 target
827 .callee_requires
828 .get(&callee)
829 .is_none_or(|c| c.is_empty())
830 })
831 });
832 if (target.checkpoints.is_empty() || all_callees_skipped)
833 && target.raw_ptr_deref_checks.is_empty()
834 && target.static_mut_checks.is_empty()
835 && target.struct_invariants.is_empty()
836 && target.type_invariants.is_empty()
837 {
838 rap_info!("============================================================");
839 rap_info!("[rapx::verify] function: {target_path}");
840 rap_info!("============================================================");
841 if self.skip_invariant {
842 let cons = get_cons(self.tcx, target.def_id);
843 for con in &cons {
844 rap_info!(" + constructor: {}", self.tcx.def_path_str(*con));
845 }
846 }
847 rap_info!(" --- unsafe checkpoints ---");
848 rap_info!(" <none>");
849 rap_info!(" <none>");
850 rap_info!(" result: SOUND (no unsafe checkpoints)");
851 rap_info!("");
852 }
853 continue;
854 }
855
856 if self.skip_invariant && !get_cons(self.tcx, target.def_id).is_empty() {
859 continue;
860 }
861
862 emit_verify_summary(
863 self.tcx,
864 &target_path,
865 target.def_id,
866 &all_results,
867 self.skip_invariant,
868 );
869 }
870
871 let mut unsafe_traits: Vec<_> = collector
873 .trait_targets
874 .iter()
875 .filter(|t| matches!(&t.kind, TraitEnsuranceKind::Unsafe(_)))
876 .collect();
877 unsafe_traits.sort_by_key(|t| self.tcx.def_path_str(t.def_id));
878 for trait_target in unsafe_traits {
879 let TraitEnsuranceKind::Unsafe(ensures) = &trait_target.kind else {
880 continue;
881 };
882 rap_info!("============================================================");
883 rap_info!(
884 "[rapx::verify] unsafe trait impl: {}",
885 self.tcx.def_path_str(trait_target.def_id)
886 );
887 rap_info!("============================================================");
888 if let Some(self_ty) = trait_target.self_ty_def_id {
889 rap_info!(" impl for: {}", self.tcx.def_path_str(self_ty));
890 }
891 if ensures.is_empty() {
892 rap_info!(" ensures: <none>");
893 } else {
894 rap_info!(" ensures (implementor must satisfy):");
895 for (method_name, contracts) in ensures {
896 rap_info!(" fn {}:", method_name);
897 for property in dedup_compound_props(contracts.iter()) {
898 rap_info!(
899 " - {}",
900 property.display_for_report(
901 self.tcx,
902 trait_target.self_ty_def_id,
903 None,
904 )
905 );
906 }
907 }
908 }
909 rap_info!(" verification: deferred");
910 rap_info!("");
911 }
912
913 let marker_targets: Vec<_> = collector
915 .trait_targets
916 .iter()
917 .filter(|t| matches!(&t.kind, TraitEnsuranceKind::Marker(..)))
918 .collect();
919 if !marker_targets.is_empty() {
920 self.emit_marker_trait_ensurance(&marker_targets);
921 }
922
923 if self.skip_invariant {
925 self.run_invless_sequences(&collector.function_targets);
926 }
927 }
928}
929
930impl<'tcx> VerifyRun<'tcx> {
931 fn emit_marker_trait_ensurance(&self, units: &[&TraitEnsurance<'tcx>]) {
933 for unit in units {
934 let TraitEnsuranceKind::Marker(kind, obligations) = &unit.kind else {
935 continue;
936 };
937 let trait_name = match kind {
938 MarkerTraitKind::Send => "Send",
939 MarkerTraitKind::Sync => "Sync",
940 };
941 let is_sync = matches!(kind, MarkerTraitKind::Sync);
942 let self_ty_label = unit
943 .self_ty_def_id
944 .map(|d| self.tcx.def_path_str(d))
945 .unwrap_or_else(|| "<unknown>".to_string());
946
947 rap_info!("============================================================");
948 rap_info!("[rapx::verify] unsafe impl {trait_name} for {self_ty_label}");
949 rap_info!("============================================================");
950
951 if obligations.is_empty() {
952 rap_info!(" ensures: <none>");
953 }
954
955 let mut any_failed = false;
956 let mut any_unknown = false;
957 for property in obligations {
958 let Some(self_ty) = unit
959 .self_ty_def_id
960 .map(|d| self.tcx.type_of(d).skip_binder())
961 else {
962 any_unknown = true;
963 continue;
964 };
965 let result =
966 self.check_type_obligation(property, unit.impl_def_id, self_ty, is_sync);
967 let label = property.display_for_report(self.tcx, unit.self_ty_def_id, None);
968 let verdict = match result {
969 CheckResult::ProvedByRule | CheckResult::ProvedBySmt => "PROVED",
970 CheckResult::Failed => "FAILED",
971 CheckResult::Unknown(_) => "UNKNOWN",
972 };
973 rap_info!(" - {label} => {verdict}");
974 any_failed |= matches!(result, CheckResult::Failed);
975 any_unknown |= matches!(result, CheckResult::Unknown(_));
976 }
977
978 let verdict = if any_failed {
979 "UNSAFE (obligation failed)"
980 } else if any_unknown {
981 "UNKNOWN"
982 } else {
983 "SAFE (all obligations proved)"
984 };
985 rap_info!(" verdict: {verdict}");
986 rap_info!("");
987 }
988 }
989
990 fn check_type_obligation(
994 &self,
995 property: &Property<'tcx>,
996 impl_def_id: rustc_hir::def_id::DefId,
997 self_ty: rustc_middle::ty::Ty<'tcx>,
998 is_sync: bool,
999 ) -> CheckResult {
1000 match property {
1001 Property::Atom(atom) => match atom.kind {
1002 PropertyKind::ContainNoType => {
1003 let Some(ty) = atom.args.first().and_then(|a| match a {
1004 PropertyArg::Ty(t) => Some(*t),
1005 _ => None,
1006 }) else {
1007 return CheckResult::Unknown(UnknownReason::Unimplemented);
1008 };
1009 let negatives: Vec<String> = atom.args[1..]
1010 .iter()
1011 .filter_map(|a| match a {
1012 PropertyArg::Ident(n) => Some(n.clone()),
1013 _ => None,
1014 })
1015 .collect();
1016 contain_no_type_check(self.tcx, ty, &negatives, impl_def_id, is_sync)
1017 }
1018 PropertyKind::NoRawPtr => {
1019 let Some(ty) = atom.args.first().and_then(|a| match a {
1020 PropertyArg::Ty(t) => Some(*t),
1021 _ => None,
1022 }) else {
1023 return CheckResult::Unknown(UnknownReason::Unimplemented);
1024 };
1025 no_raw_ptr_check(self.tcx, ty, impl_def_id, is_sync)
1026 }
1027 PropertyKind::NoInternalMut => {
1028 let Some(ty) = atom.args.first().and_then(|a| match a {
1029 PropertyArg::Ty(t) => Some(*t),
1030 _ => None,
1031 }) else {
1032 return CheckResult::Unknown(UnknownReason::Unimplemented);
1033 };
1034 no_internal_mut_check(self.tcx, ty)
1035 }
1036 PropertyKind::UniInternalMut => {
1037 let Some(ty) = atom.args.first().and_then(|a| match a {
1038 PropertyArg::Ty(t) => Some(*t),
1039 _ => None,
1040 }) else {
1041 return CheckResult::Unknown(UnknownReason::Unimplemented);
1042 };
1043 uni_internal_mut_check(self.tcx, ty)
1044 }
1045 PropertyKind::Allocated | PropertyKind::Owning => {
1046 let adt_def_id = match self_ty.kind() {
1051 rustc_middle::ty::TyKind::Adt(adt_def, _) => adt_def.did(),
1052 _ => return CheckResult::Unknown(UnknownReason::Unimplemented),
1053 };
1054 let field = atom.args.first().and_then(|a| {
1055 crate::verify::contract::place::field_name_from_arg(self.tcx, adt_def_id, a)
1056 });
1057 field_invariant_check(
1058 self.tcx,
1059 self_ty,
1060 atom.kind,
1061 field.as_deref(),
1062 &self.struct_invariant_results,
1063 )
1064 }
1065 PropertyKind::AtomicUpdate => {
1066 let Some(ty) = atom.args.first().and_then(|a| match a {
1067 PropertyArg::Ty(t) => Some(*t),
1068 _ => None,
1069 }) else {
1070 return CheckResult::Unknown(UnknownReason::Unimplemented);
1071 };
1072 atomic_update_check(self.tcx, ty, impl_def_id, is_sync)
1073 }
1074 PropertyKind::RefSend => {
1075 let Some(ty) = atom.args.first().and_then(|a| match a {
1076 PropertyArg::Ty(t) => Some(*t),
1077 _ => None,
1078 }) else {
1079 return CheckResult::Unknown(UnknownReason::Unimplemented);
1080 };
1081 ref_send_check(self.tcx, ty, impl_def_id, is_sync)
1082 }
1083 _ => CheckResult::Unknown(UnknownReason::Unimplemented),
1084 },
1085 Property::And(and) => {
1086 let mut overall = CheckResult::ProvedByRule;
1087 for conjunct in &and.conjuncts {
1088 overall = overall.and(self.check_type_obligation(
1089 conjunct,
1090 impl_def_id,
1091 self_ty,
1092 is_sync,
1093 ));
1094 }
1095 overall
1096 }
1097 Property::Or(or) => {
1098 let mut overall = CheckResult::Failed;
1099 for disjunct in &or.disjuncts {
1100 overall = overall.or(self.check_type_obligation(
1101 disjunct,
1102 impl_def_id,
1103 self_ty,
1104 is_sync,
1105 ));
1106 }
1107 overall
1108 }
1109 }
1110 }
1111
1112 fn print_contracts_debug(
1113 &self,
1114 targets: &[FunctionTarget<'tcx>],
1115 trait_targets: &[TraitEnsurance<'tcx>],
1116 ) {
1117 rap_info!("{:=<1$}", "", 76);
1118 rap_info!("[rapx::debug-contracts] Expanded Contract Assertions");
1119 rap_info!("{:=<1$}", "", 76);
1120 rap_info!("");
1121
1122 let mut struct_groups: FxHashMap<rustc_hir::def_id::DefId, Vec<&FunctionTarget<'tcx>>> =
1123 FxHashMap::default();
1124 let mut free_targets: Vec<&FunctionTarget<'tcx>> = Vec::new();
1125
1126 for target in targets {
1127 if let Some(sid) = target.owner_struct_def_id {
1128 struct_groups.entry(sid).or_default().push(target);
1129 } else {
1130 free_targets.push(target);
1131 }
1132 }
1133
1134 let mut struct_ids: Vec<_> = struct_groups.keys().copied().collect();
1135 struct_ids.sort_by_key(|did| self.tcx.def_path_str(*did));
1136
1137 for struct_def_id in struct_ids {
1138 let methods = &struct_groups[&struct_def_id];
1139 let struct_name = self.tcx.def_path_str(struct_def_id);
1140
1141 let inv_target = methods.iter().find(|t| !t.struct_invariants.is_empty());
1143 let have_invariants = inv_target.is_some();
1144
1145 if have_invariants || methods.iter().any(|t| self.has_printable_contracts(t)) {
1146 rap_info!("{:=<1$}", "", 76);
1147 rap_info!("[rapx::debug-contracts] struct: {struct_name}");
1148 rap_info!("{:=<1$}", "", 76);
1149 }
1150
1151 if let Some(tgt) = inv_target {
1152 rap_info!(" [Struct Invariants]:");
1153 let invariants = dedup_compound_props(tgt.struct_invariants.iter());
1154 let inv_count = invariants.len();
1155 for (ii, property) in invariants.iter().enumerate() {
1156 let ibranch = if ii + 1 == inv_count { "`-" } else { "|-" };
1157 let (call, meaning) = fmt_contract_expanded(
1158 self.tcx,
1159 property,
1160 tgt.owner_struct_def_id,
1161 Some(tgt.def_id),
1162 );
1163 self.print_contract_lines(" ", ibranch, &call, &meaning);
1164 }
1165 rap_info!("");
1166 }
1167
1168 let mut printed = false;
1170 for (mi, target) in methods.iter().enumerate() {
1171 let is_last_method = mi + 1 == methods.len();
1172 let branch = if is_last_method { "`-" } else { "|-" };
1173 let cont = if is_last_method { " " } else { "| " };
1174 if self.print_target_contracts(target, branch, cont) {
1175 printed = true;
1176 }
1177 }
1178 if printed {
1179 rap_info!("{:=<1$}", "", 76);
1180 rap_info!("");
1181 }
1182 }
1183
1184 for target in &free_targets {
1186 self.print_target_contracts(target, "- ", " ");
1187 }
1188
1189 let mut traits = trait_targets.iter().collect::<Vec<_>>();
1191 traits.sort_by_key(|t| self.tcx.def_path_str(t.def_id));
1192 for trait_target in traits {
1193 match &trait_target.kind {
1194 TraitEnsuranceKind::Unsafe(ensures) => {
1195 let trait_path = self.tcx.def_path_str(trait_target.def_id);
1196 rap_info!("{:=<1$}", "", 76);
1197 rap_info!("[rapx::debug-contracts] unsafe trait: {trait_path}");
1198 rap_info!("{:=<1$}", "", 76);
1199 if let Some(self_ty) = trait_target.self_ty_def_id {
1200 rap_info!(" impl for: {}", self.tcx.def_path_str(self_ty));
1201 }
1202 if ensures.is_empty() {
1203 rap_info!(" ensures: <none>");
1204 } else {
1205 for (method_name, contracts) in ensures {
1206 rap_info!(" fn {method_name}:");
1207 for property in dedup_compound_props(contracts.iter()) {
1208 let (call, meaning) = fmt_contract_expanded(
1209 self.tcx,
1210 property,
1211 trait_target.self_ty_def_id,
1212 Some(trait_target.def_id),
1213 );
1214 self.print_contract_lines(" ", "|-", &call, &meaning);
1215 }
1216 }
1217 }
1218 rap_info!("");
1219 }
1220 TraitEnsuranceKind::Marker(kind, obligations) => {
1221 let name = match kind {
1222 MarkerTraitKind::Send => "Send",
1223 MarkerTraitKind::Sync => "Sync",
1224 };
1225 rap_info!("{:=<1$}", "", 76);
1226 rap_info!("[rapx::debug-contracts] marker trait: {name}");
1227 rap_info!("{:=<1$}", "", 76);
1228 if let Some(self_ty) = trait_target.self_ty_def_id {
1229 rap_info!(" impl for: {}", self.tcx.def_path_str(self_ty));
1230 }
1231 if obligations.is_empty() {
1232 rap_info!(" obligations: <none>");
1233 } else {
1234 for property in obligations {
1235 let (call, meaning) = fmt_contract_expanded(
1236 self.tcx,
1237 property,
1238 trait_target.self_ty_def_id,
1239 None,
1240 );
1241 self.print_contract_lines(" ", "|-", &call, &meaning);
1242 }
1243 }
1244 rap_info!("");
1245 }
1246 }
1247 }
1248 }
1249
1250 fn is_unsafe_fn(&self, def_id: rustc_hir::def_id::DefId) -> bool {
1251 self.tcx.fn_sig(def_id).skip_binder().safety() == rustc_hir::Safety::Unsafe
1252 }
1253
1254 fn has_caller_contracts(&self, target: &FunctionTarget<'tcx>, is_unsafe_fn: bool) -> bool {
1255 is_unsafe_fn
1256 && target
1257 .caller_requires
1258 .iter()
1259 .any(|p| p.kind() != Some(PropertyKind::Unknown))
1260 }
1261
1262 fn has_printable_contracts(&self, target: &FunctionTarget<'tcx>) -> bool {
1263 let is_unsafe_fn = self.is_unsafe_fn(target.def_id);
1264 self.has_caller_contracts(target, is_unsafe_fn)
1265 || target
1266 .callee_requires
1267 .values()
1268 .any(|c| c.iter().any(|p| p.kind() != Some(PropertyKind::Unknown)))
1269 }
1270
1271 fn print_contract_lines(&self, prefix: &str, branch: &str, call: &str, meaning: &str) {
1272 rap_info!("{prefix}{branch} {call}");
1273 let cont = if branch == "`-" { " " } else { "| " };
1274 for line in meaning.lines() {
1275 rap_info!("{prefix}{cont} {line}");
1276 }
1277 }
1278
1279 fn print_target_contracts(
1280 &self,
1281 target: &FunctionTarget<'tcx>,
1282 branch: &str,
1283 cont: &str,
1284 ) -> bool {
1285 use crate::verify::contract::PropertyKind;
1286
1287 let (arg_names_typed, ret_ty) = self.resolve_arg_names_with_types(target.def_id);
1288 let is_unsafe_fn = self.is_unsafe_fn(target.def_id);
1289
1290 let target_path = fmt_fn_path_with_generics(self.tcx, target.def_id);
1291 let short_name = short_fn_name(self.tcx, target.def_id);
1292
1293 let has_caller = self.has_caller_contracts(target, is_unsafe_fn);
1295 let mut callee_ids: Vec<_> = target.callee_requires.keys().copied().collect();
1296 callee_ids.retain(|did| {
1297 target
1298 .callee_requires
1299 .get(did)
1300 .is_some_and(|c| c.iter().any(|p| p.kind() != Some(PropertyKind::Unknown)))
1301 });
1302 callee_ids.sort_by_key(|did| self.tcx.def_path_str(*did));
1303 let has_callees = !callee_ids.is_empty();
1304
1305 if !has_caller && !has_callees {
1306 return false;
1307 }
1308
1309 let fn_display = fmt_fn_with_params(&target_path, &arg_names_typed, ret_ty.as_deref());
1310 let header = format!("--- method: {short_name}");
1311 let dashes = 72usize.saturating_sub(header.len());
1312 rap_info!("{branch} {header} {}", "-".repeat(dashes));
1313 rap_info!("{cont} {fn_display}");
1314
1315 if has_caller {
1317 rap_info!("{cont} [Caller Contracts]:");
1318 let caller_props = dedup_compound_props(
1319 target
1320 .caller_requires
1321 .iter()
1322 .filter(|p| p.kind() != Some(PropertyKind::Unknown)),
1323 );
1324 for (pi, property) in caller_props.iter().enumerate() {
1325 let is_last = pi + 1 == caller_props.len();
1326 let pbranch = if is_last { "`-" } else { "|-" };
1327 let (call, meaning) = fmt_contract_expanded(
1328 self.tcx,
1329 property,
1330 target.owner_struct_def_id,
1331 Some(target.def_id),
1332 );
1333 self.print_contract_lines(&format!("{cont} "), pbranch, &call, &meaning);
1334 }
1335 if !has_callees {
1336 rap_info!("");
1337 }
1338 }
1339
1340 if has_callees {
1342 rap_info!("{cont} [Unsafe Callees]:");
1343 for (ci, &callee_id) in callee_ids.iter().enumerate() {
1344 let is_last_callee = ci + 1 == callee_ids.len();
1345 let cbranch = if is_last_callee { "`-" } else { "|-" };
1346 let ccont = if is_last_callee { " " } else { "| " };
1347 let contracts = target.callee_requires.get(&callee_id).unwrap();
1348 let (callee_typed, callee_ret) = self.resolve_arg_names_with_types(callee_id);
1349 let callee_path = fmt_fn_path_with_generics(self.tcx, callee_id);
1350 rap_info!(
1351 "{cont} {cbranch} {}",
1352 fmt_fn_with_params(&callee_path, &callee_typed, callee_ret.as_deref())
1353 );
1354 let props = dedup_compound_props(
1355 contracts
1356 .iter()
1357 .filter(|p| p.kind() != Some(PropertyKind::Unknown)),
1358 );
1359 for (pi, property) in props.iter().enumerate() {
1360 let is_last_prop = pi + 1 == props.len();
1361 let pbranch = if is_last_prop { "`-" } else { "|-" };
1362 let (call, meaning) =
1363 fmt_contract_expanded(self.tcx, property, None, Some(callee_id));
1364 self.print_contract_lines(
1365 &format!("{cont} {ccont}"),
1366 pbranch,
1367 &call,
1368 &meaning,
1369 );
1370 }
1371 }
1372 }
1373
1374 rap_info!("");
1375 true
1376 }
1377
1378 fn resolve_arg_names_with_types(
1379 &self,
1380 def_id: rustc_hir::def_id::DefId,
1381 ) -> (Vec<String>, Option<String>) {
1382 if !self.tcx.is_mir_available(def_id) {
1383 return (Vec::new(), None);
1384 }
1385 let body = self.tcx.optimized_mir(def_id);
1386 let args: Vec<String> = body
1387 .local_decls
1388 .iter()
1389 .enumerate()
1390 .skip(1)
1391 .take(body.arg_count)
1392 .map(|(i, decl)| {
1393 let name = {
1394 let span = decl.source_info.span;
1395 self.tcx
1396 .sess
1397 .source_map()
1398 .span_to_snippet(span)
1399 .unwrap_or_else(|_| format!("_{}", i))
1400 };
1401 let ty = decl.ty.to_string();
1402 format!("{name}: {ty}")
1403 })
1404 .collect();
1405 let ret_ty = self.tcx.fn_sig(def_id).skip_binder().output().skip_binder();
1406 let ret_ty = if ret_ty.is_unit() {
1407 None
1408 } else {
1409 Some(ret_ty.to_string())
1410 };
1411 (args, ret_ty)
1412 }
1413}
1414
1415use crate::helpers::name::short_fn_name;
1416
1417fn property_field_indices(property: &crate::verify::contract::Property<'_>) -> Vec<usize> {
1422 use crate::verify::contract::{ContractExpr, PropertyArg};
1423 let mut indices = Vec::new();
1424 for arg in property.args() {
1425 let place = match arg {
1426 PropertyArg::Expr(ContractExpr::Place(p)) => Some(p),
1427 _ => None,
1428 };
1429 if let Some(place) = place {
1430 for proj in &place.projections {
1431 match proj {
1432 crate::verify::contract::ContractProjection::Field { index, .. } => {
1433 let idx = *index;
1434 if !indices.contains(&idx) {
1435 indices.push(idx);
1436 }
1437 }
1438 crate::verify::contract::ContractProjection::Downcast { .. } => {}
1439 crate::verify::contract::ContractProjection::ForEach => {}
1440 }
1441 }
1442 }
1443 }
1444 indices
1445}
1446
1447fn targets_return_value(property: &Property<'_>) -> bool {
1452 property
1453 .target_place()
1454 .is_some_and(|cp| matches!(cp.base, PlaceBase::Return))
1455}
1456
1457fn remap_constructor_contract<'tcx>(
1458 property: crate::verify::contract::Property<'tcx>,
1459) -> crate::verify::contract::Property<'tcx> {
1460 use crate::verify::contract::{
1461 ContractExpr, ContractPlace, ContractProjection, PlaceBase, PropertyArg,
1462 };
1463
1464 fn remap_place_arg<'tcx>(arg: &PropertyArg<'tcx>) -> PropertyArg<'tcx> {
1465 let place = match arg {
1466 PropertyArg::Expr(ContractExpr::Place(p)) => p,
1467 _ => return arg.clone(),
1468 };
1469 let PlaceBase::Arg(field_idx) = place.base else {
1470 return arg.clone();
1471 };
1472 let projection = ContractProjection::Field {
1473 index: field_idx,
1474 ty: None,
1475 };
1476 let mut new_place = ContractPlace {
1477 base: PlaceBase::Arg(0),
1478 projections: vec![projection],
1479 };
1480 new_place
1481 .projections
1482 .extend(place.projections.iter().cloned());
1483 PropertyArg::Expr(ContractExpr::Place(new_place))
1484 }
1485
1486 let new_args: Vec<PropertyArg<'tcx>> = property
1487 .args()
1488 .iter()
1489 .map(|arg| remap_place_arg(arg))
1490 .collect();
1491
1492 match property {
1493 crate::verify::contract::Property::Atom(mut atom) => {
1494 atom.args = new_args;
1495 crate::verify::contract::Property::Atom(atom)
1496 }
1497 crate::verify::contract::Property::And(and) => crate::verify::contract::Property::And(and),
1498 crate::verify::contract::Property::Or(or) => crate::verify::contract::Property::Or(or),
1499 }
1500}