use rustc_hir::def_id::DefId;
use super::contract::Property;
use crate::verify::checkpoint::CheckpointLocation;
pub(crate) const PATH_LIMIT_DESCRIPTION: &str = "[not enumerated: path limit reached]";
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum UnknownReason {
SmtUnknown(String),
InlineDepthExceeded,
UnsupportedMir,
IncompleteInlineBranch,
UnmodeledIntrinsic,
UnresolvedUnsafeCall,
MissingContract,
UnsupportedProperty,
Unimplemented,
Multiple(Vec<UnknownReason>),
}
impl UnknownReason {
pub(crate) fn from_solver(solver: &z3::Solver<'_>) -> Self {
Self::SmtUnknown(
solver
.get_reason_unknown()
.unwrap_or_else(|| "reason unavailable".to_string()),
)
}
fn description(&self) -> String {
match self {
Self::SmtUnknown(reason) if reason == "timeout" => "timeout".to_string(),
Self::SmtUnknown(reason) => format!("SMT: {reason}"),
Self::InlineDepthExceeded => "inline depth".to_string(),
Self::UnsupportedMir => "unsupported MIR".to_string(),
Self::IncompleteInlineBranch => "incomplete inline branch".to_string(),
Self::UnmodeledIntrinsic => "unmodeled intrinsic".to_string(),
Self::UnresolvedUnsafeCall => "unresolved unsafe call".to_string(),
Self::MissingContract => "missing safety contract".to_string(),
Self::UnsupportedProperty => "unsupported property".to_string(),
Self::Unimplemented => "unresolved value or unsupported shape".to_string(),
Self::Multiple(reasons) => reasons
.iter()
.map(Self::description)
.collect::<Vec<_>>()
.join("; "),
}
}
pub(crate) fn merge(self, other: UnknownReason) -> UnknownReason {
let mut reasons = match self {
Self::Multiple(reasons) => reasons,
reason => vec![reason],
};
let others = match other {
Self::Multiple(reasons) => reasons,
reason => vec![reason],
};
for reason in others {
if !reasons.contains(&reason) {
reasons.push(reason);
}
}
if reasons.len() == 1 {
reasons.pop().unwrap()
} else {
Self::Multiple(reasons)
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum CheckResult {
ProvedByRule,
ProvedBySmt,
Failed,
Unknown(UnknownReason),
}
impl CheckResult {
pub(crate) fn is_proved(&self) -> bool {
matches!(self, CheckResult::ProvedByRule | CheckResult::ProvedBySmt)
}
pub(crate) fn label(&self) -> String {
match self {
CheckResult::ProvedByRule | CheckResult::ProvedBySmt => "Proved".to_string(),
CheckResult::Failed => "Failed".to_string(),
CheckResult::Unknown(reason) => format!("Unknown ({})", reason.description()),
}
}
pub(crate) fn and(self, other: CheckResult) -> CheckResult {
match (self, other) {
(CheckResult::Failed, _) | (_, CheckResult::Failed) => CheckResult::Failed,
(CheckResult::Unknown(a), CheckResult::Unknown(b)) => {
CheckResult::Unknown(a.merge(b))
}
(CheckResult::Unknown(a), _) | (_, CheckResult::Unknown(a)) => CheckResult::Unknown(a),
(CheckResult::ProvedByRule, CheckResult::ProvedByRule) => CheckResult::ProvedByRule,
_ => CheckResult::ProvedBySmt,
}
}
pub(crate) fn or(self, other: CheckResult) -> CheckResult {
match (self, other) {
(CheckResult::ProvedByRule, _) | (_, CheckResult::ProvedByRule) => {
CheckResult::ProvedByRule
}
(CheckResult::ProvedBySmt, _) | (_, CheckResult::ProvedBySmt) => {
CheckResult::ProvedBySmt
}
(CheckResult::Failed, CheckResult::Failed) => CheckResult::Failed,
(CheckResult::Unknown(a), CheckResult::Unknown(b)) => {
CheckResult::Unknown(a.merge(b))
}
(CheckResult::Unknown(a), _) | (_, CheckResult::Unknown(a)) => CheckResult::Unknown(a),
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct PropertyCheckResult<'tcx> {
pub checkpoint: CheckpointLocation,
pub checkpoint_index: usize,
pub path_index: usize,
pub property_index: usize,
pub property: Property<'tcx>,
pub result: CheckResult,
pub diagnostics: Option<String>,
pub path_description: String,
pub callee_name: String,
}
#[derive(Clone, Debug)]
pub(crate) struct VerificationReport<'tcx> {
pub function: DefId,
pub results: Vec<PropertyCheckResult<'tcx>>,
}
impl<'tcx> VerificationReport<'tcx> {
pub(crate) fn new(function: DefId) -> Self {
Self {
function,
results: Vec::new(),
}
}
pub(crate) fn push(&mut self, result: PropertyCheckResult<'tcx>) {
self.results.push(result);
}
pub(crate) fn describe(&self) -> String {
let mut out = String::new();
out.push_str(&format!(
"[rapx::verify::diagnostics] function {:?}: {} check item(s)\n",
self.function,
self.results.len()
));
for (index, result) in self.results.iter().enumerate() {
out.push_str(&format!(
" check #{index}: checkpoint #{}, bb{}, path #{}, property #{} {:?}, result {}\n",
result.checkpoint_index,
result.checkpoint.block.as_usize(),
result.path_index,
result.property_index,
result.property.kind(),
result.result.label()
));
if let Some(diagnostics) = &result.diagnostics {
out.push_str(diagnostics);
if !diagnostics.ends_with('\n') {
out.push('\n');
}
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::{CheckResult, UnknownReason};
#[test]
fn unknown_labels_preserve_solver_reason_and_multiple_causes() {
let timeout = CheckResult::Unknown(UnknownReason::SmtUnknown("timeout".into()));
assert_eq!(timeout.label(), "Unknown (timeout)");
let combined = UnknownReason::UnsupportedMir
.merge(UnknownReason::SmtUnknown("incomplete".into()));
assert_eq!(
CheckResult::Unknown(combined).label(),
"Unknown (unsupported MIR; SMT: incomplete)"
);
}
}