307 lines
9.1 KiB
Rust
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);
|
|
}
|
|
}
|