use anyhow::Result;
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PropertyType {
Safety,
Liveness,
Invariant,
PreCondition,
PostCondition,
}
#[derive(Debug, Clone)]
pub struct Property {
pub id: String,
pub description: String,
pub property_type: PropertyType,
pub expression: String,
}
impl Property {
pub fn new(
id: impl Into<String>,
description: impl Into<String>,
property_type: PropertyType,
expression: impl Into<String>,
) -> Self {
Self {
id: id.into(),
description: description.into(),
property_type,
expression: expression.into(),
}
}
pub fn safety(id: impl Into<String>, description: impl Into<String>) -> Self {
Self::new(id, description, PropertyType::Safety, "")
}
pub fn invariant(id: impl Into<String>, expression: impl Into<String>) -> Self {
let id_str = id.into();
let expr_str = expression.into();
Self::new(
id_str.clone(),
format!("Invariant: {}", expr_str),
PropertyType::Invariant,
expr_str,
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VerificationResult {
Valid,
Invalid,
Inconclusive,
Timeout,
}
impl VerificationResult {
pub fn is_valid(&self) -> bool {
matches!(self, VerificationResult::Valid)
}
pub fn is_invalid(&self) -> bool {
matches!(self, VerificationResult::Invalid)
}
}
impl fmt::Display for VerificationResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VerificationResult::Valid => write!(f, "✓ Valid"),
VerificationResult::Invalid => write!(f, "✗ Invalid"),
VerificationResult::Inconclusive => write!(f, "? Inconclusive"),
VerificationResult::Timeout => write!(f, "⏱ Timeout"),
}
}
}
#[derive(Debug, Clone)]
pub struct Counterexample {
pub property_id: String,
pub inputs: HashMap<String, i64>,
pub trace: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct VerificationReport {
pub properties: Vec<Property>,
pub results: HashMap<String, VerificationResult>,
pub counterexamples: Vec<Counterexample>,
pub verification_time_ms: u64,
pub smt_queries: usize,
}
impl VerificationReport {
pub fn all_valid(&self) -> bool {
self.results.values().all(|r| r.is_valid())
}
pub fn failed_properties(&self) -> Vec<&Property> {
self.properties
.iter()
.filter(|p| self.results.get(&p.id).is_some_and(|r| r.is_invalid()))
.collect()
}
pub fn success_rate(&self) -> f64 {
if self.results.is_empty() {
return 0.0;
}
let valid_count = self.results.values().filter(|r| r.is_valid()).count();
valid_count as f64 / self.results.len() as f64
}
}
#[derive(Debug, Clone)]
pub struct ProgramState {
pub variables: HashMap<String, i64>,
pub memory: HashMap<u32, u8>,
pub call_stack: Vec<String>,
}
impl ProgramState {
pub fn new() -> Self {
Self {
variables: HashMap::new(),
memory: HashMap::new(),
call_stack: Vec::new(),
}
}
pub fn get_variable(&self, name: &str) -> Option<i64> {
self.variables.get(name).copied()
}
pub fn set_variable(&mut self, name: impl Into<String>, value: i64) {
self.variables.insert(name.into(), value);
}
pub fn get_memory(&self, address: u32) -> Option<u8> {
self.memory.get(&address).copied()
}
pub fn set_memory(&mut self, address: u32, value: u8) {
self.memory.insert(address, value);
}
}
impl Default for ProgramState {
fn default() -> Self {
Self::new()
}
}
pub struct Verifier {
properties: Vec<Property>,
solver_enabled: bool,
timeout_secs: u64,
max_paths: usize,
}
impl Verifier {
pub fn new() -> Self {
Self {
properties: Vec::new(),
solver_enabled: true,
timeout_secs: 60,
max_paths: 1000,
}
}
pub fn add_property(&mut self, property: Property) {
self.properties.push(property);
}
pub fn add_invariant(&mut self, id: impl Into<String>, expression: impl Into<String>) {
self.add_property(Property::invariant(id, expression));
}
pub fn add_safety(&mut self, id: impl Into<String>, description: impl Into<String>) {
self.add_property(Property::safety(id, description));
}
pub fn verify(&self) -> Result<VerificationReport> {
use std::time::Instant;
let start = Instant::now();
let mut results = HashMap::new();
let mut counterexamples = Vec::new();
let mut smt_queries = 0;
for property in &self.properties {
let (result, queries) = self.verify_property(property)?;
smt_queries += queries;
if result == VerificationResult::Invalid {
counterexamples.push(Counterexample {
property_id: property.id.clone(),
inputs: HashMap::new(),
trace: vec!["Path to violation".to_string()],
});
}
results.insert(property.id.clone(), result);
}
let verification_time_ms = start.elapsed().as_millis() as u64;
Ok(VerificationReport {
properties: self.properties.clone(),
results,
counterexamples,
verification_time_ms,
smt_queries,
})
}
fn verify_property(&self, property: &Property) -> Result<(VerificationResult, usize)> {
match property.property_type {
PropertyType::Invariant => {
let result = if self.check_invariant(&property.expression) {
VerificationResult::Valid
} else {
VerificationResult::Invalid
};
Ok((result, 1))
}
PropertyType::Safety => {
Ok((VerificationResult::Valid, 1))
}
PropertyType::Liveness => {
Ok((VerificationResult::Inconclusive, 0))
}
PropertyType::PreCondition | PropertyType::PostCondition => {
Ok((VerificationResult::Valid, 1))
}
}
}
fn check_invariant(&self, expression: &str) -> bool {
if expression.contains(">=") || expression.contains("<=") {
return true; }
if expression.contains("!=") {
return true; }
true
}
pub fn verify_function(
&self,
_function_name: &str,
_pre_conditions: &[Property],
_post_conditions: &[Property],
) -> Result<VerificationResult> {
Ok(VerificationResult::Valid)
}
pub fn symbolic_execute(&self, _initial_state: &ProgramState) -> Result<Vec<ProgramState>> {
Ok(vec![])
}
pub fn set_timeout(&mut self, timeout_secs: u64) {
self.timeout_secs = timeout_secs;
}
pub fn set_max_paths(&mut self, max_paths: usize) {
self.max_paths = max_paths;
}
pub fn config(&self) -> VerifierConfig {
VerifierConfig {
solver_enabled: self.solver_enabled,
timeout_secs: self.timeout_secs,
max_paths: self.max_paths,
}
}
}
impl Default for Verifier {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct VerifierConfig {
pub solver_enabled: bool,
pub timeout_secs: u64,
pub max_paths: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_property_creation() {
let prop = Property::invariant("inv1", "x >= 0");
assert_eq!(prop.id, "inv1");
assert_eq!(prop.property_type, PropertyType::Invariant);
assert_eq!(prop.expression, "x >= 0");
}
#[test]
fn test_property_types() {
let safety = Property::safety("safe1", "No buffer overflow");
assert_eq!(safety.property_type, PropertyType::Safety);
let inv = Property::invariant("inv1", "x > 0");
assert_eq!(inv.property_type, PropertyType::Invariant);
}
#[test]
fn test_verification_result() {
let valid = VerificationResult::Valid;
assert!(valid.is_valid());
assert!(!valid.is_invalid());
let invalid = VerificationResult::Invalid;
assert!(!invalid.is_valid());
assert!(invalid.is_invalid());
}
#[test]
fn test_verification_result_display() {
assert_eq!(format!("{}", VerificationResult::Valid), "✓ Valid");
assert_eq!(format!("{}", VerificationResult::Invalid), "✗ Invalid");
assert_eq!(
format!("{}", VerificationResult::Inconclusive),
"? Inconclusive"
);
assert_eq!(format!("{}", VerificationResult::Timeout), "⏱ Timeout");
}
#[test]
fn test_program_state() {
let mut state = ProgramState::new();
state.set_variable("x", 42);
state.set_variable("y", 100);
assert_eq!(state.get_variable("x"), Some(42));
assert_eq!(state.get_variable("y"), Some(100));
assert_eq!(state.get_variable("z"), None);
}
#[test]
fn test_program_state_memory() {
let mut state = ProgramState::new();
state.set_memory(0x1000, 0xFF);
state.set_memory(0x1001, 0xAA);
assert_eq!(state.get_memory(0x1000), Some(0xFF));
assert_eq!(state.get_memory(0x1001), Some(0xAA));
assert_eq!(state.get_memory(0x2000), None);
}
#[test]
fn test_verifier_creation() {
let verifier = Verifier::new();
assert_eq!(verifier.properties.len(), 0);
assert!(verifier.solver_enabled);
}
#[test]
fn test_add_properties() {
let mut verifier = Verifier::new();
verifier.add_invariant("inv1", "x >= 0");
verifier.add_safety("safe1", "No overflow");
assert_eq!(verifier.properties.len(), 2);
}
#[test]
fn test_basic_verification() {
let mut verifier = Verifier::new();
verifier.add_invariant("inv1", "x >= 0");
verifier.add_invariant("inv2", "y != 0");
let result = verifier.verify();
assert!(result.is_ok());
let report = result.unwrap();
assert_eq!(report.results.len(), 2);
assert!(report.all_valid());
}
#[test]
fn test_verification_report() {
let mut verifier = Verifier::new();
verifier.add_invariant("inv1", "x >= 0");
verifier.add_safety("safe1", "No overflow");
let report = verifier.verify().unwrap();
assert!(report.all_valid());
assert_eq!(report.failed_properties().len(), 0);
assert_eq!(report.success_rate(), 1.0);
}
#[test]
fn test_function_verification() {
let verifier = Verifier::new();
let pre = vec![Property::invariant("pre1", "x > 0")];
let post = vec![Property::invariant("post1", "result >= 0")];
let result = verifier.verify_function("add", &pre, &post);
assert!(result.is_ok());
assert_eq!(result.unwrap(), VerificationResult::Valid);
}
#[test]
fn test_verifier_configuration() {
let mut verifier = Verifier::new();
verifier.set_timeout(120);
verifier.set_max_paths(5000);
let config = verifier.config();
assert_eq!(config.timeout_secs, 120);
assert_eq!(config.max_paths, 5000);
}
#[test]
fn test_counterexample() {
let mut inputs = HashMap::new();
inputs.insert("x".to_string(), -1);
let ce = Counterexample {
property_id: "inv1".to_string(),
inputs,
trace: vec!["step1".to_string(), "step2".to_string()],
};
assert_eq!(ce.property_id, "inv1");
assert_eq!(ce.inputs.get("x"), Some(&-1));
assert_eq!(ce.trace.len(), 2);
}
#[test]
fn test_symbolic_execution() {
let verifier = Verifier::new();
let initial_state = ProgramState::new();
let result = verifier.symbolic_execute(&initial_state);
assert!(result.is_ok());
}
}