mod infer;
mod templates;
#[cfg(test)]
mod tests;
pub(crate) use infer::{cegis_refine, infer_postconditions_from_body, infer_preconditions};
pub(crate) use templates::match_templates;
#[cfg(test)]
pub(crate) use infer::{infer_postconditions_from_constraints, verify_candidate, weaken_candidate};
pub(crate) use crate::ast::*;
pub(crate) use crate::solve;
pub(crate) use crate::sym::{self, ConstraintSystem, SymValue};
use std::collections::BTreeMap;
#[derive(Clone, Debug)]
pub struct SynthesizedSpec {
pub function: String,
pub kind: SpecKind,
pub expression: String,
pub confidence: u8,
pub explanation: String,
}
impl SynthesizedSpec {
pub fn format(&self) -> String {
let kind_str = match &self.kind {
SpecKind::LoopInvariant { loop_var } => {
format!("loop invariant (over {})", loop_var)
}
SpecKind::Postcondition => "postcondition (#[ensures])".to_string(),
SpecKind::Precondition => "precondition (#[requires])".to_string(),
SpecKind::Assertion => "assertion".to_string(),
};
let confidence_str = if self.confidence >= 90 {
"high"
} else if self.confidence >= 60 {
"medium"
} else {
"low"
};
format!(
" [{}] {} {}: {}\n {}",
confidence_str, self.function, kind_str, self.expression, self.explanation,
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum SpecKind {
LoopInvariant { loop_var: String },
Postcondition,
Precondition,
Assertion,
}
#[derive(Clone, Debug)]
struct AccumulationPattern {
acc_var: String,
loop_var: String,
init_value: String,
op: AccumulationOp,
}
#[derive(Clone, Debug, PartialEq)]
enum AccumulationOp {
Additive,
Multiplicative,
Counting,
}
#[derive(Clone, Debug)]
struct MonotonicPattern {
var: String,
loop_var: String,
init_value: String,
}
pub fn synthesize_specs(file: &File) -> Vec<SynthesizedSpec> {
let mut specs = Vec::new();
for item in &file.items {
if let Item::Fn(func) = &item.node {
if func.body.is_some() {
let mut fn_specs = synthesize_for_function(func, file);
specs.append(&mut fn_specs);
}
}
}
specs
}
pub fn format_report(specs: &[SynthesizedSpec]) -> String {
if specs.is_empty() {
return "No specifications synthesized.\n".to_string();
}
let mut report = String::new();
report.push_str(&format!(
"Synthesized {} specification(s):\n\n",
specs.len()
));
for spec in specs {
report.push_str(&spec.format());
report.push('\n');
}
report
}
fn synthesize_for_function(func: &FnDef, file: &File) -> Vec<SynthesizedSpec> {
let fn_name = &func.name.node;
let mut specs = Vec::new();
let mut template_specs = match_templates(func);
specs.append(&mut template_specs);
let mut pre_specs = infer_preconditions(func);
specs.append(&mut pre_specs);
if let Some(ref body) = func.body {
let mut post_specs = infer_postconditions_from_body(func, &body.node);
specs.append(&mut post_specs);
}
let candidates: Vec<String> = specs.iter().map(|s| s.expression.clone()).collect();
if !candidates.is_empty() {
let refined = cegis_refine(func, file, &candidates);
for spec in &mut specs {
if refined.iter().any(|r| r.expression == spec.expression) {
spec.confidence = spec.confidence.max(90);
}
}
}
for spec in &mut specs {
spec.function = fn_name.clone();
}
specs
}
pub(crate) fn collect_mut_inits(block: &Block) -> Vec<(String, Expr)> {
let mut inits = Vec::new();
for stmt in &block.stmts {
if let Stmt::Let {
mutable: true,
pattern: Pattern::Name(name),
init,
..
} = &stmt.node
{
inits.push((name.node.clone(), init.node.clone()));
}
}
inits
}
pub(crate) fn place_is_var(place: &Place, name: &str) -> bool {
matches!(place, Place::Var(n) if n == name)
}
pub(crate) fn expr_is_var(expr: &Expr, name: &str) -> bool {
matches!(expr, Expr::Var(n) if n == name)
}
pub(crate) fn expr_to_string(expr: &Expr) -> String {
match expr {
Expr::Literal(Literal::Integer(n)) => n.to_string(),
Expr::Literal(Literal::Bool(b)) => b.to_string(),
Expr::Var(name) => name.clone(),
Expr::BinOp { op, lhs, rhs } => {
let l = expr_to_string(&lhs.node);
let r = expr_to_string(&rhs.node);
format!("({} {} {})", l, op.as_str(), r)
}
Expr::Call { path, args, .. } => {
let name = path.node.as_dotted();
let arg_strs: Vec<String> = args.iter().map(|a| expr_to_string(&a.node)).collect();
format!("{}({})", name, arg_strs.join(", "))
}
Expr::Index { expr, index } => {
format!(
"{}[{}]",
expr_to_string(&expr.node),
expr_to_string(&index.node)
)
}
Expr::FieldAccess { expr, field } => {
format!("{}.{}", expr_to_string(&expr.node), field.node)
}
Expr::Tuple(elems) => {
let parts: Vec<String> = elems.iter().map(|e| expr_to_string(&e.node)).collect();
format!("({})", parts.join(", "))
}
Expr::ArrayInit(elems) => {
let parts: Vec<String> = elems.iter().map(|e| expr_to_string(&e.node)).collect();
format!("[{}]", parts.join(", "))
}
Expr::StructInit { path, fields } => {
let name = path.node.as_dotted();
let field_strs: Vec<String> = fields
.iter()
.map(|(n, v)| format!("{}: {}", n.node, expr_to_string(&v.node)))
.collect();
format!("{} {{ {} }}", name, field_strs.join(", "))
}
}
}