rapx/verify/report.rs
1//! Diagnostics and summaries for the staged verifier pipeline.
2//!
3//! The driver and later checking stages report their per-path property results
4//! through the types in this module. Keeping these types here leaves the driver
5//! focused on orchestration.
6
7use rustc_hir::def_id::DefId;
8
9use super::contract::Property;
10use crate::helpers::mir_scan::CheckpointLocation;
11
12/// Why a [`CheckResult::Unknown`] could not be discharged.
13///
14/// Carried by the `Unknown` variant so the report can distinguish a solver
15/// timeout (the common case under the fixed per-query budget) from a structural
16/// "no rule for this shape" gap.
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub(crate) enum UnknownReason {
19 /// The SMT solver returned `unknown` — with the fixed per-query timeout
20 /// this is effectively a timeout on a query it could not decide in time.
21 SmtTimeout,
22 /// The checker has no rule for this shape (unsupported property, unannotated
23 /// callee, unresolvable operand, undecidable generic, …).
24 Unimplemented,
25}
26
27impl UnknownReason {
28 /// Merge two reasons, keeping the more diagnostic one.
29 fn merge(self, other: UnknownReason) -> UnknownReason {
30 match (self, other) {
31 (UnknownReason::SmtTimeout, _) | (_, UnknownReason::SmtTimeout) => {
32 UnknownReason::SmtTimeout
33 }
34 _ => UnknownReason::Unimplemented,
35 }
36 }
37}
38
39/// Verification status for one required property on one path.
40#[derive(Clone, Debug, PartialEq)]
41pub(crate) enum CheckResult {
42 /// Proved by a *sound structural rule* (a fast-path that needs no solver
43 /// query): ZST guards, zero-element access, tracked invariants/flags,
44 /// type-level transparency, provenance-origin classification. These are
45 /// the rules that must be audited for soundness.
46 ProvedByRule,
47 /// Proved by an *SMT query*: the solver showed the negation is unsatisfiable
48 /// (numeric bounds, byte-range coverage, alignment modulo/linear checks).
49 ProvedBySmt,
50 /// The verifier found a possible violation for this path.
51 Failed,
52 /// The verifier has not implemented or completed the proof for this path;
53 /// the reason is carried in the payload.
54 Unknown(UnknownReason),
55}
56
57impl CheckResult {
58 /// Whether this result counts as "proved" (by rule or by SMT).
59 pub(crate) fn is_proved(&self) -> bool {
60 matches!(self, CheckResult::ProvedByRule | CheckResult::ProvedBySmt)
61 }
62
63 /// The user-facing label for this result. `ProvedByRule` and `ProvedBySmt`
64 /// both report as `"Proved"` — the rule/SMT distinction is an internal
65 /// audit signal, not part of the report. An `Unknown` result is suffixed
66 /// with its reason when it is not the generic unimplemented case.
67 pub(crate) fn label(&self) -> &'static str {
68 match self {
69 CheckResult::ProvedByRule | CheckResult::ProvedBySmt => "Proved",
70 CheckResult::Failed => "Failed",
71 CheckResult::Unknown(UnknownReason::SmtTimeout) => "Unknown (timeout)",
72 CheckResult::Unknown(UnknownReason::Unimplemented) => "Unknown",
73 }
74 }
75
76 /// AND-combine two results: any `Failed` → `Failed`; any `Unknown` →
77 /// `Unknown`; only all-proved → proved (`Rule` when every conjunct was a
78 /// rule, otherwise `Smt`).
79 pub(crate) fn and(self, other: CheckResult) -> CheckResult {
80 match (self, other) {
81 (CheckResult::Failed, _) | (_, CheckResult::Failed) => CheckResult::Failed,
82 (CheckResult::Unknown(a), CheckResult::Unknown(b)) => {
83 CheckResult::Unknown(a.merge(b))
84 }
85 (CheckResult::Unknown(a), _) | (_, CheckResult::Unknown(a)) => CheckResult::Unknown(a),
86 (CheckResult::ProvedByRule, CheckResult::ProvedByRule) => CheckResult::ProvedByRule,
87 _ => CheckResult::ProvedBySmt,
88 }
89 }
90
91 /// OR-combine two results: any proved → proved (a `Rule` proof dominates);
92 /// all `Failed` → `Failed`; otherwise `Unknown`.
93 pub(crate) fn or(self, other: CheckResult) -> CheckResult {
94 match (self, other) {
95 (CheckResult::ProvedByRule, _) | (_, CheckResult::ProvedByRule) => {
96 CheckResult::ProvedByRule
97 }
98 (CheckResult::ProvedBySmt, _) | (_, CheckResult::ProvedBySmt) => {
99 CheckResult::ProvedBySmt
100 }
101 (CheckResult::Failed, CheckResult::Failed) => CheckResult::Failed,
102 (CheckResult::Unknown(a), CheckResult::Unknown(b)) => {
103 CheckResult::Unknown(a.merge(b))
104 }
105 (CheckResult::Unknown(a), _) | (_, CheckResult::Unknown(a)) => CheckResult::Unknown(a),
106 }
107 }
108}
109
110/// Result for one required property along one path to a checkpoint.
111#[derive(Clone, Debug)]
112pub(crate) struct PropertyCheckResult<'tcx> {
113 /// Unsafe checkpoint being checked.
114 pub checkpoint: CheckpointLocation,
115 /// Index of the checkpoint in the function-level checkpoint list.
116 pub checkpoint_index: usize,
117 /// Index of the path in the checkpoint path set.
118 pub path_index: usize,
119 /// Index of the property in the checkpoint-level property list.
120 pub property_index: usize,
121 /// Required property checked on this path.
122 pub property: Property<'tcx>,
123 /// Current verification status.
124 pub result: CheckResult,
125 /// Optional path-local diagnostic message generated by the verifier.
126 pub diagnostics: Option<String>,
127 /// Human-readable path description.
128 pub path_description: String,
129 /// Callee name for this checkpoint.
130 pub callee_name: String,
131}
132
133/// Verification report for one function target.
134#[derive(Clone, Debug)]
135pub(crate) struct VerificationReport<'tcx> {
136 /// Function that was verified.
137 pub function: DefId,
138 /// Per-path property results emitted by the verifier.
139 pub results: Vec<PropertyCheckResult<'tcx>>,
140}
141
142impl<'tcx> VerificationReport<'tcx> {
143 /// Create an empty report for a function target.
144 pub(crate) fn new(function: DefId) -> Self {
145 Self {
146 function,
147 results: Vec::new(),
148 }
149 }
150
151 /// Add one path/property check result to this report.
152 pub(crate) fn push(&mut self, result: PropertyCheckResult<'tcx>) {
153 self.results.push(result);
154 }
155
156 /// Render the whole report as a readable multi-line diagnostic.
157 pub(crate) fn describe(&self) -> String {
158 let mut out = String::new();
159 out.push_str(&format!(
160 "[rapx::verify::diagnostics] function {:?}: {} check item(s)\n",
161 self.function,
162 self.results.len()
163 ));
164
165 for (index, result) in self.results.iter().enumerate() {
166 out.push_str(&format!(
167 " check #{index}: checkpoint #{}, bb{}, path #{}, property #{} {:?}, result {}\n",
168 result.checkpoint_index,
169 result.checkpoint.block.as_usize(),
170 result.path_index,
171 result.property_index,
172 result.property.kind(),
173 result.result.label()
174 ));
175
176 if let Some(diagnostics) = &result.diagnostics {
177 out.push_str(diagnostics);
178 if !diagnostics.ends_with('\n') {
179 out.push('\n');
180 }
181 }
182 }
183
184 out
185 }
186}