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