Skip to main content

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}