//! 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, /// 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 { 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 { 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 { 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 { // 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); } }