synor/crates/synor-verifier/src/prover.rs

307 lines
9.1 KiB
Rust

//! Proof generation and verification.
//!
//! Integrates with SMT solvers to prove or disprove properties.
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
use crate::ast::Expression;
use crate::error::{VerifierError, VerifierResult};
use crate::smt::{SmtContext, SmtResult};
use crate::symbolic::SymbolicState;
/// Prover configuration.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProverConfig {
/// Timeout in milliseconds.
pub timeout_ms: u64,
/// Maximum symbolic execution depth.
pub max_depth: usize,
/// Maximum loop unrolling iterations.
pub max_loop_unroll: usize,
/// Enable counterexample generation.
pub generate_counterexamples: bool,
/// Enable proof caching.
pub enable_caching: bool,
}
impl Default for ProverConfig {
fn default() -> Self {
Self {
timeout_ms: 30000,
max_depth: 100,
max_loop_unroll: 10,
generate_counterexamples: true,
enable_caching: true,
}
}
}
/// Result of a proof attempt.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ProofResult {
/// Property is proven to hold.
Proven {
/// Time taken in milliseconds.
time_ms: u64,
},
/// Property is disproven with counterexample.
Disproven {
/// Counterexample assignments.
counterexample: Counterexample,
/// Time taken.
time_ms: u64,
},
/// Could not determine (timeout, resource limit).
Unknown {
/// Reason for unknown result.
reason: String,
/// Time taken.
time_ms: u64,
},
}
impl ProofResult {
/// Returns true if property is proven.
pub fn is_proven(&self) -> bool {
matches!(self, ProofResult::Proven { .. })
}
/// Returns the counterexample if disproven.
pub fn counterexample(&self) -> Option<&Counterexample> {
if let ProofResult::Disproven { counterexample, .. } = self {
Some(counterexample)
} else {
None
}
}
}
/// Counterexample showing how property can fail.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Counterexample {
/// Variable assignments that cause failure.
pub assignments: Vec<(String, String)>,
/// Execution trace leading to failure.
pub trace: Vec<String>,
/// The failing property.
pub property: String,
}
impl Counterexample {
/// Creates a new counterexample.
pub fn new(property: &str) -> Self {
Self {
assignments: Vec::new(),
trace: Vec::new(),
property: property.to_string(),
}
}
/// Adds a variable assignment.
pub fn add_assignment(&mut self, var: &str, value: &str) {
self.assignments.push((var.to_string(), value.to_string()));
}
/// Adds a trace step.
pub fn add_trace(&mut self, step: &str) {
self.trace.push(step.to_string());
}
/// Formats the counterexample for display.
pub fn format(&self) -> String {
let mut s = format!("Counterexample for '{}':\n", self.property);
s.push_str(" Assignments:\n");
for (var, val) in &self.assignments {
s.push_str(&format!(" {} = {}\n", var, val));
}
if !self.trace.is_empty() {
s.push_str(" Trace:\n");
for (i, step) in self.trace.iter().enumerate() {
s.push_str(&format!(" {}. {}\n", i + 1, step));
}
}
s
}
}
/// Main prover interface.
pub struct Prover {
config: ProverConfig,
smt: SmtContext,
}
impl Prover {
/// Creates a new prover with configuration.
pub fn new(config: ProverConfig) -> Self {
Self {
smt: SmtContext::new(config.timeout_ms),
config,
}
}
/// Proves or disproves an expression holds in all states.
pub fn prove(&self, expr: &Expression, state: &SymbolicState) -> VerifierResult<ProofResult> {
let start = Instant::now();
let _timeout = Duration::from_millis(self.config.timeout_ms);
// Convert expression and state to SMT constraints
let constraints = self.smt.encode_state(state)?;
let property = self.smt.encode_expression(expr)?;
// Check if negation is unsatisfiable (property always holds)
let negated = self.smt.negate(&property)?;
let result = self.smt.check_sat_with_constraints(&constraints, &negated)?;
let elapsed = start.elapsed().as_millis() as u64;
if elapsed > self.config.timeout_ms {
return Ok(ProofResult::Unknown {
reason: "Timeout".to_string(),
time_ms: elapsed,
});
}
match result {
SmtResult::Unsat => {
// Negation is unsat, so property always holds
Ok(ProofResult::Proven { time_ms: elapsed })
}
SmtResult::Sat(model) => {
// Found counterexample
let counterexample = if self.config.generate_counterexamples {
self.extract_counterexample(&model, expr)
} else {
Counterexample::new("(counterexample generation disabled)")
};
Ok(ProofResult::Disproven {
counterexample,
time_ms: elapsed,
})
}
SmtResult::Unknown(reason) => Ok(ProofResult::Unknown {
reason,
time_ms: elapsed,
}),
}
}
/// Proves an implication: precondition => postcondition.
pub fn prove_implication(
&self,
precondition: &Expression,
postcondition: &Expression,
state: &SymbolicState,
) -> VerifierResult<ProofResult> {
let start = Instant::now();
// Encode: precondition AND NOT postcondition
let pre = self.smt.encode_expression(precondition)?;
let post = self.smt.encode_expression(postcondition)?;
let neg_post = self.smt.negate(&post)?;
let constraints = self.smt.encode_state(state)?;
// If pre AND NOT post is unsat, then pre => post
let combined = self.smt.and(&pre, &neg_post)?;
let result = self.smt.check_sat_with_constraints(&constraints, &combined)?;
let elapsed = start.elapsed().as_millis() as u64;
match result {
SmtResult::Unsat => Ok(ProofResult::Proven { time_ms: elapsed }),
SmtResult::Sat(model) => {
let counterexample = self.extract_counterexample(&model, postcondition);
Ok(ProofResult::Disproven {
counterexample,
time_ms: elapsed,
})
}
SmtResult::Unknown(reason) => Ok(ProofResult::Unknown {
reason,
time_ms: elapsed,
}),
}
}
/// Extracts a counterexample from an SMT model.
fn extract_counterexample(
&self,
model: &[(String, String)],
expr: &Expression,
) -> Counterexample {
let mut ce = Counterexample::new(&format!("{:?}", expr));
for (var, val) in model {
ce.add_assignment(var, val);
}
ce
}
/// Checks if an expression is satisfiable.
pub fn check_sat(&self, expr: &Expression) -> VerifierResult<bool> {
let encoded = self.smt.encode_expression(expr)?;
let result = self.smt.check_sat(&encoded)?;
match result {
SmtResult::Sat(_) => Ok(true),
SmtResult::Unsat => Ok(false),
SmtResult::Unknown(reason) => Err(VerifierError::SmtError(reason)),
}
}
/// Simplifies an expression using the SMT solver.
pub fn simplify(&self, expr: &Expression) -> VerifierResult<Expression> {
// For now, just return the original expression
// Full implementation would use SMT solver's simplify
Ok(expr.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prover_config_default() {
let config = ProverConfig::default();
assert_eq!(config.timeout_ms, 30000);
assert_eq!(config.max_depth, 100);
assert!(config.generate_counterexamples);
}
#[test]
fn test_counterexample_format() {
let mut ce = Counterexample::new("x > 0");
ce.add_assignment("x", "-1");
ce.add_trace("Entered function foo");
ce.add_trace("x assigned -1");
let formatted = ce.format();
assert!(formatted.contains("x = -1"));
assert!(formatted.contains("Entered function foo"));
}
#[test]
fn test_proof_result() {
let proven = ProofResult::Proven { time_ms: 100 };
assert!(proven.is_proven());
assert!(proven.counterexample().is_none());
let ce = Counterexample::new("test");
let disproven = ProofResult::Disproven {
counterexample: ce,
time_ms: 50,
};
assert!(!disproven.is_proven());
assert!(disproven.counterexample().is_some());
}
#[test]
fn test_prover_creation() {
let prover = Prover::new(ProverConfig::default());
assert_eq!(prover.config.timeout_ms, 30000);
}
}