1use rustc_hir::def_id::DefId;
2use rustc_middle::ty::{self, TyCtxt};
3use rustc_middle::ty::ClauseKind;
4
5use crate::compat::FxHashMap;
6use crate::helpers::fn_info::get_cons;
7use indexmap::IndexMap;
8
9use crate::helpers::mir_scan::CheckpointLocation;
10use super::report::PropertyCheckResult;
11use crate::verify::contract::render::display_expr_user_friendly;
12
13pub fn fmt_fn_with_params(path: &str, arg_names: &[String], ret_ty: Option<&str>) -> String {
14 let args = arg_names.join(", ");
15 match ret_ty {
16 Some(ret) => format!("fn {path}({args}) -> {ret}"),
17 None if args.is_empty() => format!("fn {path}"),
18 None => format!("fn {path}({args})"),
19 }
20}
21
22pub fn fmt_fn_path_with_generics(
23 tcx: rustc_middle::ty::TyCtxt<'_>,
24 def_id: rustc_hir::def_id::DefId,
25) -> String {
26 let path = tcx.def_path_str(def_id);
27 let generics = tcx.generics_of(def_id);
28 let params: Vec<_> = generics
29 .own_params
30 .iter()
31 .map(|p| p.name.to_string())
32 .collect();
33 if params.is_empty() {
34 path
35 } else {
36 format!("{}::<{}>", path, params.join(", "))
37 }
38}
39
40pub fn fmt_fn_path_with_bounds(
41 tcx: TyCtxt<'_>,
42 def_id: DefId,
43) -> String {
44 let path = tcx.def_path_str(def_id);
45 let predicates = crate::compat::predicates_of(tcx, def_id);
46
47 let mut param_bounds: FxHashMap<String, Vec<String>> = FxHashMap::default();
48
49 macro_rules! collect_bounds {
50 ($iter:expr) => {
51 for (predicate, _span) in $iter {
52 if let ClauseKind::Trait(trait_ref) = predicate.kind().skip_binder() {
53 let self_ty = trait_ref.self_ty();
54 if let ty::TyKind::Param(param_ty) = self_ty.kind() {
55 let param_name = param_ty.name.to_string();
56 let trait_name = tcx.item_name(trait_ref.def_id()).to_string();
57 if trait_name != "Sized" {
58 param_bounds.entry(param_name).or_default().push(trait_name);
59 }
60 }
61 }
62 }
63 };
64 }
65
66 #[cfg(not(rapx_ge_100))]
67 {
68 collect_bounds!(predicates.predicates.iter());
69 if let Some(parent_def_id) = predicates.parent {
70 let parent_preds = crate::compat::predicates_of(tcx, parent_def_id);
71 collect_bounds!(parent_preds.predicates.iter());
72 }
73 }
74 #[cfg(rapx_ge_100)]
75 {
76 collect_bounds!(predicates.clauses.iter());
77 if let Some(parent_def_id) = predicates.parent {
78 let parent_preds = crate::compat::predicates_of(tcx, parent_def_id);
79 collect_bounds!(parent_preds.clauses.iter());
80 }
81 }
82
83 if param_bounds.is_empty() {
84 return path;
85 }
86
87 insert_bounds_into_path(&path, ¶m_bounds)
88}
89
90fn insert_bounds_into_path(path: &str, param_bounds: &FxHashMap<String, Vec<String>>) -> String {
91 let mut result = String::new();
92 let mut remaining = path;
93
94 while let Some(pos) = remaining.find("::<") {
95 result.push_str(&remaining[..pos + 3]);
96 remaining = &remaining[pos + 3..];
97
98 let Some(end) = remaining.find('>') else {
99 result.push_str(remaining);
100 return result;
101 };
102
103 let params_str = &remaining[..end];
104 let params: Vec<&str> = params_str.split(',').map(|s| s.trim()).collect();
105 let mut new_params = Vec::new();
106 let mut has_bounds = false;
107 for p in ¶ms {
108 if let Some(bounds) = param_bounds.get(*p) {
109 new_params.push(format!("{}: {}", p, bounds.join(" + ")));
110 has_bounds = true;
111 } else {
112 new_params.push(p.to_string());
113 }
114 }
115
116 if has_bounds {
117 result.push_str(&new_params.join(", "));
118 } else {
119 result.push_str(params_str);
120 }
121 result.push('>');
122 remaining = &remaining[end + 1..];
123 }
124
125 result.push_str(remaining);
126 result
127}
128
129pub fn fmt_contract_expanded<'tcx>(
130 tcx: rustc_middle::ty::TyCtxt<'tcx>,
131 property: &crate::verify::contract::Property<'tcx>,
132 struct_def_id: Option<rustc_hir::def_id::DefId>,
133 fn_def_id: Option<rustc_hir::def_id::DefId>,
134) -> (String, String) {
135 use crate::verify::contract::PropertyKind;
136 if let Some(name) = property.origin_name() {
140 let args = property.origin_args().map(|a| a.join(", ")).unwrap_or_default();
141 let meaning = property.origin_meaning().unwrap_or("");
142 return (format!("{name}({args})"), meaning.to_string());
143 }
144 if property.is_or() {
145 let group_count = property.groups().len();
146 let mut call_parts = Vec::new();
147 let mut meaning = format!("any of {group_count} alternative group(s):\n");
148 for (gi, group) in property.groups().iter().enumerate() {
149 let is_last = gi + 1 == group_count;
150 let branch = if is_last { "`-" } else { "|-" };
151 let group_calls: Vec<String> = group
152 .iter()
153 .map(|prop| {
154 let (call, _) =
155 fmt_contract_expanded(tcx, prop, struct_def_id, fn_def_id);
156 call
157 })
158 .collect();
159 call_parts.push(group_calls.join(" && "));
160 let meanings: Vec<String> = group
161 .iter()
162 .map(|prop| {
163 let (_, m) =
164 fmt_contract_expanded(tcx, prop, struct_def_id, fn_def_id);
165 m
166 })
167 .collect();
168 meaning.push_str(&format!("{branch} {}\n", meanings.join(" && ")));
169 }
170 return (
171 format!("Or({})", call_parts.join(", ")),
172 meaning.trim_end().to_string(),
173 );
174 }
175 let kind = property.kind().expect("leaf property");
176 let args: Vec<String> = property
177 .args()
178 .iter()
179 .map(|a| a.display_for_report(tcx, struct_def_id, fn_def_id))
180 .collect();
181 let tag = property
182 .origin_name()
183 .map(String::from)
184 .unwrap_or_else(|| format!("{:?}", kind));
185 let tag = if property.contract_kind() == crate::verify::contract::ContractKind::Hazard {
186 format!("[hazard] {tag}")
187 } else if property.contract_kind() == crate::verify::contract::ContractKind::Option_ {
188 format!("[option] {tag}")
189 } else {
190 tag
191 };
192 let call = if matches!(kind, PropertyKind::SplitTransmute) {
193 let wrapped: Vec<String> = args.iter().map(|a| format!("[{a}]")).collect();
194 format!("{tag}({})", wrapped.join(", "))
195 } else if matches!(kind, PropertyKind::InBound)
196 && matches!(
197 property.args().first(),
198 Some(crate::verify::contract::PropertyArg::Expr(
199 crate::verify::contract::ContractExpr::IndexAccess { .. }
200 ))
201 )
202 {
203 use crate::verify::contract::{ContractExpr, PropertyArg};
204 if let Some(PropertyArg::Expr(ContractExpr::IndexAccess { slice, index })) =
205 property.args().first()
206 {
207 let mut s = display_expr_user_friendly(slice, tcx, struct_def_id, fn_def_id);
208 s = s.strip_prefix("&mut ").unwrap_or(&s).to_string();
209 s = s.strip_prefix("&").unwrap_or(&s).to_string();
210 let i = display_expr_user_friendly(index, tcx, struct_def_id, fn_def_id);
211 format!("{tag}({s}, {i})")
212 } else {
213 unreachable!()
214 }
215 } else {
216 if matches!(kind, PropertyKind::Alive) && args.len() >= 2 {
217 format!("{tag}({}, '{})", args[0], args[1])
218 } else {
219 format!("{tag}({})", args.join(", "))
220 }
221 };
222 let call = if matches!(kind, PropertyKind::ValidNum)
223 && let Some(crate::verify::contract::PropertyArg::Predicates(preds)) = property.args().first()
224 {
225 let inner = preds
226 .iter()
227 .map(|p| p.display_user_friendly(tcx, struct_def_id, fn_def_id))
228 .collect::<Vec<_>>()
229 .join(", ");
230 format!("{tag}({inner})")
231 } else {
232 call
233 };
234 let meaning = match kind {
235 PropertyKind::InBound => {
236 use crate::verify::contract::{ContractExpr, PropertyArg};
237 let placeholder = format!("InBound({})", args.join(", "));
238 match property.args().first() {
239 Some(PropertyArg::Expr(ContractExpr::IndexAccess { slice, index })) => {
240 let mut s = display_expr_user_friendly(slice, tcx, struct_def_id, fn_def_id);
241 s = s.strip_prefix("&mut ").unwrap_or(&s).to_string();
242 s = s.strip_prefix("&").unwrap_or(&s).to_string();
243 let i = display_expr_user_friendly(index, tcx, struct_def_id, fn_def_id);
244 format!("0 <= {i} < {s}.len()")
245 }
246 Some(PropertyArg::Expr(ContractExpr::Place(place))) => {
247 let ptr = place.display_user_friendly(tcx, struct_def_id, fn_def_id);
248 let ty = property
249 .args()
250 .get(1)
251 .and_then(|a| match a {
252 PropertyArg::Ty(ty) => Some(ty.to_string()),
253 _ => None,
254 })
255 .unwrap_or_else(|| "?".to_string());
256 let cnt = property
257 .args()
258 .get(2)
259 .map(|a| a.display_for_report(tcx, struct_def_id, fn_def_id))
260 .unwrap_or_else(|| "?".to_string());
261 format!("same_alloc([{ptr}, {ptr} + sizeof({ty})*{cnt}])")
262 }
263 _ => placeholder,
264 }
265 }
266 PropertyKind::Size => {
267 let ty = args.first().map(|s| s.as_str()).unwrap_or("T");
268 let sz = args.get(1).map(|s| s.as_str()).unwrap_or("1");
269 match sz {
270 "sized" => format!("{ty} is Sized (non-ZST)"),
271 "unsized" => format!("{ty} is !Sized"),
272 n => format!("sizeof({ty}) = {n}"),
273 }
274 }
275 PropertyKind::ValidNum => args.join(" && "),
276 PropertyKind::Alive => {
277 let ptr = args.first().map(|s| s.as_str()).unwrap_or("ptr");
278 if let Some(lt) = args.get(1) {
279 format!("*{ptr} outlives '{lt}")
280 } else {
281 format!("*{ptr} outlives return")
282 }
283 }
284 PropertyKind::Allocated => {
285 let ptr = args.first().map(|s| s.as_str()).unwrap_or("ptr");
286 if args.len() >= 3 {
287 format!(
288 "{ptr} points to a live allocation of size: size_of({}) * {}",
289 args[1], args[2]
290 )
291 } else {
292 format!("{ptr} points to a live allocation")
293 }
294 }
295 PropertyKind::NonOverlap => {
296 format!("[{}] are pairwise disjoint memory ranges", args.join(", "))
297 }
298 PropertyKind::Alias => {
299 let p1 = args.first().map(|s| s.as_str()).unwrap_or("p1");
300 let p2 = args.get(1).map(|s| s.as_str()).unwrap_or("p2");
301 format!("{p1} and {p2} alias each other (hazard)")
302 }
303 _ => fmt_meaning_template(
304 crate::verify::contract::spec::kind_meaning(kind),
305 &args,
306 ),
307 };
308 (call, meaning)
309}
310
311fn fmt_meaning_template(template: &str, args: &[String]) -> String {
314 let mut out = template.to_string();
315 for i in 0..3 {
316 let value = args.get(i).map(|s| s.as_str()).unwrap_or("_");
317 out = out.replace(&format!("{{{i}}}"), value);
318 }
319 out
320}
321
322pub(crate) fn dedup_compound_props<'a, 'tcx>(
326 props: impl Iterator<Item = &'a crate::verify::contract::Property<'tcx>>,
327) -> Vec<&'a crate::verify::contract::Property<'tcx>> {
328 let mut out = Vec::new();
329 let mut prev: Option<(String, Vec<String>)> = None;
330 for p in props {
331 if let (Some(name), Some(args)) = (p.origin_name(), p.origin_args()) {
332 let key = (name.to_string(), args.to_vec());
333 if prev.as_ref() == Some(&key) {
334 continue;
335 }
336 prev = Some(key);
337 } else {
338 prev = None;
339 }
340 out.push(p);
341 }
342 out
343}
344
345pub fn emit_results_counts_and_checkpoints<'tcx>(
346 tcx: TyCtxt<'tcx>,
347 all_results: &[PropertyCheckResult<'tcx>],
348) -> (usize, usize) {
349
350 use crate::verify::contract::ContractKind;
351 use super::report::CheckResult;
352
353 let unproved = all_results
354 .iter()
355 .filter(|r| {
356 r.property.contract_kind() != ContractKind::Hazard
357 && r.property.contract_kind() != ContractKind::Option_
358 && !matches!(r.result, CheckResult::Proved)
359 })
360 .count();
361 let hazard_failed = all_results
362 .iter()
363 .filter(|r| {
364 r.property.contract_kind() == ContractKind::Hazard
365 && !matches!(r.result, CheckResult::Proved)
366 })
367 .count();
368
369 let mut groups: IndexMap<(CheckpointLocation, String), Vec<&PropertyCheckResult<'_>>> =
370 IndexMap::new();
371 for r in all_results {
372 groups
373 .entry((r.checkpoint, r.callee_name.clone()))
374 .or_default()
375 .push(r);
376 }
377
378 let checkpoint_groups: Vec<_> = groups
379 .iter()
380 .filter(|((_, name), _)| !name.starts_with("struct-invariant"))
381 .collect();
382 let invariant_groups: Vec<_> = groups
383 .iter()
384 .filter(|((_, name), _)| name.starts_with("struct-invariant"))
385 .collect();
386
387 if !checkpoint_groups.is_empty() {
388 rap_info!(" --- unsafe checkpoints ---");
389 for ((checkpoint, callee_name), results) in &checkpoint_groups {
390 rap_info!(
391 " unsafe checkpoint: bb{} -> {callee_name}",
392 checkpoint.block.as_usize(),
393 );
394 emit_property_rows(tcx, results);
395 }
396 }
397
398 if !invariant_groups.is_empty() {
399 rap_info!(" --- struct invariants ---");
400 for ((checkpoint, _), results) in &invariant_groups {
401 rap_info!(" checkpoint bb{}:", checkpoint.block.as_usize());
402 emit_property_rows(tcx, results);
403 }
404 }
405
406 (unproved, hazard_failed)
407}
408
409pub fn emit_verify_summary<'tcx>(
410 tcx: TyCtxt<'tcx>,
411 target_path: &str,
412 def_id: rustc_hir::def_id::DefId,
413 all_results: &[PropertyCheckResult<'tcx>],
414 skip_invariant: bool,
415) {
416 rap_info!("============================================================");
417 rap_info!("[rapx::verify] function: {target_path}");
418 rap_info!("============================================================");
419
420 if skip_invariant {
421 let cons = get_cons(tcx, def_id);
422 for con in &cons {
423 rap_info!(" + constructor: {}", tcx.def_path_str(*con));
424 }
425 }
426
427 emit_results_and_verdict(tcx, all_results);
428 rap_info!("");
429}
430
431pub fn emit_results_and_verdict<'tcx>(
432 tcx: TyCtxt<'tcx>,
433 all_results: &[PropertyCheckResult<'tcx>],
434) {
435 let (unproved, hazard_failed) = emit_results_counts_and_checkpoints(tcx, all_results);
436
437 if unproved == 0 && hazard_failed == 0 {
438 rap_info!(green, " result: SOUND");
439 } else {
440 rap_warn!(" result: UNSOUND ({unproved} unproved, {hazard_failed} hazard)");
441 }
442}
443
444
445
446pub fn emit_property_rows<'tcx>(
447 _tcx: TyCtxt<'tcx>,
448 results: &[&PropertyCheckResult<'tcx>],
449) {
450 let path_groups: Vec<(&str, Vec<_>)> = {
451 let mut map: FxHashMap<&str, Vec<_>> = FxHashMap::default();
452 for r in results.iter() {
453 map.entry(r.path_description.as_str())
454 .or_default()
455 .push(r);
456 }
457 let mut entries: Vec<_> = map.into_iter().collect();
458 entries.sort_by_key(|(desc, _)| desc.matches(',').count());
459 entries
460 };
461 for (path_desc, props) in &path_groups {
462 rap_info!(" path {path_desc}:");
463 let mut counts: Vec<(
465 Option<crate::verify::contract::PropertyKind>,
466 Option<String>,
467 bool,
468 bool,
469 super::report::CheckResult,
470 usize,
471 )> = Vec::new();
472 for r in props.iter() {
473 let result = r.result.clone();
474 if let Some(on) = r.property.origin_name() {
475 if let Some(entry) =
478 counts.iter_mut().find(|(_, o, _, _, _, _)| o.as_deref() == Some(on))
479 {
480 entry.4 = entry.4.clone().and(result);
481 } else {
482 counts.push((None, Some(on.to_string()), false, false, result, 1usize));
483 }
484 } else {
485 let is_hazard =
486 r.property.contract_kind() == crate::verify::contract::ContractKind::Hazard;
487 let is_option =
488 r.property.contract_kind() == crate::verify::contract::ContractKind::Option_;
489 if let Some(entry) = counts.iter_mut().find(|(k, o, h, opt, res, _)| {
490 *k == r.property.kind()
491 && o.is_none()
492 && *h == is_hazard
493 && *opt == is_option
494 && *res == result
495 }) {
496 entry.5 += 1;
497 } else {
498 counts.push((
499 r.property.kind(),
500 None,
501 is_hazard,
502 is_option,
503 result,
504 1usize,
505 ));
506 }
507 }
508 }
509 let n = counts.len();
510 for (i, (kind, origin, is_hazard, is_option, result, count)) in counts.iter().enumerate() {
511 let is_last = i + 1 == n;
512 let conn = if n > 1 {
513 if is_last { "└── " } else { "├── " }
514 } else {
515 ""
516 };
517 let name = origin.clone().unwrap_or_else(|| match kind {
518 Some(k) => format!("{k:?}"),
519 None => "Or".to_string(),
520 });
521 let tag = if *is_hazard {
522 format!("[hazard] {name}")
523 } else if *is_option {
524 format!("[option] {name}")
525 } else {
526 name
527 };
528 let mut line = format!(" {conn}{tag} | {:?}", result);
529 if *count > 1 {
530 line.push_str(&format!(" (x{count})"));
531 }
532 if matches!(result, super::report::CheckResult::Proved) {
533 rap_info!(green, "{line}");
534 } else {
535 rap_warn!("{line}");
536 }
537 }
538 }
539}