Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/driver/commands/refinement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::{
quant_proof::{lower_quant_prove_task, QuantVcProveTask},
smt_proof::run_smt_prove_task,
},
proof_rules::calculus::ProcSoundness,
resource_limits::{LimitError, LimitsRef},
servers::SharedServer,
};
Expand Down Expand Up @@ -163,6 +164,7 @@ async fn verify_entailment(
server,
slice_vars,
vc_is_valid,
&ProcSoundness::default(),
)?;

// Handle reasons to stop the verifier.
Expand Down
15 changes: 14 additions & 1 deletion src/driver/commands/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::{
quant_proof::lower_quant_prove_task,
smt_proof::{run_smt_prove_task, set_global_z3_params},
},
proof_rules::calculus::get_soundness_map,
resource_limits::{await_with_resource_limits, LimitError, LimitsRef},
servers::{Server, SharedServer},
};
Expand Down Expand Up @@ -186,6 +187,8 @@ fn verify_files_main(
// based on the provided calculus annotations
module.check_calculus_rules(&mut tcx)?;

let soundness_map = get_soundness_map(&mut module, &mut tcx);

// Desugar encodings from source units.
module.apply_encodings(&mut tcx, server)?;

Expand Down Expand Up @@ -215,7 +218,14 @@ fn verify_files_main(
.items
.into_iter()
.flat_map(|item| {
item.flat_map(|unit| CoreVerifyTask::from_source_unit(unit, &mut depgraph))
let soundness_blame = soundness_map.get(item.name()).cloned().unwrap_or_default();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this map work? Is it really ever not a bug if an item has no associated SoundnessBlame?

item.flat_map(|unit| {
let core_verify_task = CoreVerifyTask::from_source_unit(unit, &mut depgraph);
core_verify_task.map(|mut task| {
task.proc_soundness = soundness_blame.clone();
task
})
})
})
.collect();

Expand Down Expand Up @@ -254,6 +264,8 @@ fn verify_files_main(
// (depending on options).
let vc_is_valid = lower_quant_prove_task(options, &limits_ref, &mut tcx, name, vc_expr)?;

let soundness_blame = &verify_unit.proc_soundness;

// Running the SMT prove task: translating to Z3, running the solver.
let result = run_smt_prove_task(
options,
Expand All @@ -264,6 +276,7 @@ fn verify_files_main(
server,
slice_vars,
vc_is_valid,
soundness_blame,
)?;

// Handle reasons to stop the verifier.
Expand Down
4 changes: 4 additions & 0 deletions src/driver/core_verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::{
proc_verify::{encode_proc_verify, to_direction_lower_bounds},
SpecCall,
},
proof_rules::calculus::ProcSoundness,
resource_limits::LimitsRef,
servers::Server,
slicing::{
Expand Down Expand Up @@ -70,6 +71,7 @@ pub struct CoreVerifyTask {
pub deps: Dependencies,
pub direction: Direction,
pub block: Block,
pub proc_soundness: ProcSoundness,
}

impl CoreVerifyTask {
Expand All @@ -96,6 +98,7 @@ impl CoreVerifyTask {
deps,
direction,
block,
proc_soundness: ProcSoundness::default(),
})
}
DeclKind::DomainDecl(_domain_decl) => None, // TODO: check that the axioms are not contradictions
Expand All @@ -107,6 +110,7 @@ impl CoreVerifyTask {
deps,
direction: Direction::Down,
block,
proc_soundness: ProcSoundness::default(),
}),
}
}
Expand Down
32 changes: 30 additions & 2 deletions src/driver/smt_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::driver::commands::verify::VerifyCommand;
use crate::driver::error::CaesarError;
use crate::driver::item::SourceUnitName;
use crate::driver::quant_proof::{BoolVcProveTask, QuantVcProveTask};
use crate::proof_rules::calculus::ProcSoundness;
use crate::slicing::transform::SliceStmts;
use crate::smt::funcs::axiomatic::{AxiomInstantiation, AxiomaticFunctionEncoder, PartialEncoding};
use crate::smt::funcs::fuel::literals::LiteralExprCollector;
Expand Down Expand Up @@ -141,6 +142,7 @@ pub fn run_smt_prove_task(
server: &mut dyn Server,
slice_vars: SliceStmts,
vc_is_valid: BoolVcProveTask,
proc_soundness: &ProcSoundness,
) -> Result<ProveResult, CaesarError> {
let ctx = Context::new(&z3::Config::default());
let function_encoder = mk_function_encoder(tcx, depgraph, options)?;
Expand All @@ -161,7 +163,7 @@ pub fn run_smt_prove_task(
vc_is_valid.run_solver(options, limits_ref, name, &ctx, &mut translate, &slice_vars)?;

server
.handle_vc_check_result(name, &mut result, &mut translate)
.handle_vc_check_result(name, &mut result, &mut translate, proc_soundness)
.map_err(CaesarError::ServerError)?;

Ok(result.prove_result)
Expand Down Expand Up @@ -558,6 +560,7 @@ impl<'ctx> SmtVcProveResult<'ctx> {
span: Span,
server: &mut dyn Server,
translate: &mut TranslateExprs<'smt, 'ctx>,
proc_soundness: &ProcSoundness,
) -> Result<(), CaesarError> {
// TODO: batch all those messages

Expand All @@ -567,8 +570,25 @@ impl<'ctx> SmtVcProveResult<'ctx> {
}
}

let approximation_labels = proc_soundness.diagnostic_labels();
let approx_note = proc_soundness.diagnostic_note();

match &mut self.prove_result {
ProveResult::Proof => {}
ProveResult::Proof => {
if !proc_soundness.sound_proofs() {
let msg = if let Some(note) = approx_note {
format!("The verification result might be unsound because {}", note)
} else {
"The verification result might be unsound.".to_string()
};

let diag = Diagnostic::new(ReportKind::Warning, span)
.with_message(msg)
.with_labels(approximation_labels);

server.add_diagnostic(diag)?;
}
}
ProveResult::Counterexample => {
let model = self.model.as_ref().unwrap();
let mut labels = vec![];
Expand Down Expand Up @@ -618,6 +638,14 @@ impl<'ctx> SmtVcProveResult<'ctx> {
let diagnostic = Diagnostic::new(ReportKind::Error, span)
.with_message(text)
.with_labels(labels);

let diagnostic = if proc_soundness.sound_refutations() {
diagnostic
} else {
diagnostic
.with_note("This counter-example might be spurious and might not apply to the real program.")
.with_labels(approximation_labels)
};
server.add_diagnostic(diagnostic)?;
}
ProveResult::Unknown(reason) => {
Expand Down
42 changes: 24 additions & 18 deletions src/intrinsic/annotations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ use crate::{
resolve::{Resolve, ResolveError},
tycheck::{Tycheck, TycheckError},
},
proof_rules::calculus::RecursiveProcBlame,
proof_rules::Encoding,
proof_rules::{calculus::RecursiveProcBlame, Encoding, FixpointSemanticsKind},
slicing::selection::SliceAnnotation,
tyctx::TyCtx,
};
Expand Down Expand Up @@ -54,12 +53,6 @@ pub enum AnnotationUnsoundnessError {
span: Span,
enc_name: Ident,
},
CalculusEncodingMismatch {
direction: Direction,
span: Span,
calculus_name: Ident,
enc_name: Ident,
},
CalculusCallMismatch {
span: Span,
context_calculus: Ident,
Expand Down Expand Up @@ -125,16 +118,6 @@ impl AnnotationUnsoundnessError {
"There must not be any statements after this annotated statement (and the annotated statement must not be nested in a block).",
))
}
AnnotationUnsoundnessError::CalculusEncodingMismatch{direction, span, calculus_name, enc_name } => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still have a corresponding test now for this case?

Diagnostic::new(ReportKind::Error, span)
.with_message(format!(
"In {}s, the `{}` calculus does not support the `{}` proof rule.",
direction.prefix("proc"), calculus_name.name, enc_name.name
))
.with_label(Label::new(span).with_message(
"The calculus, proof rule, and direction are incompatible.",
))
}
AnnotationUnsoundnessError::CalculusCallMismatch{span,context_calculus,call_calculus} => {
Diagnostic::new(ReportKind::Error, span)
.with_message(format!(
Expand Down Expand Up @@ -175,13 +158,29 @@ pub struct Calculus {
pub calculus_type: CalculusType,
}

impl std::fmt::Display for Calculus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CalculusType {
Wp,
Wlp,
Ert,
}

impl std::fmt::Display for CalculusType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CalculusType::Wp => write!(f, "wp"),
CalculusType::Wlp => write!(f, "wlp"),
CalculusType::Ert => write!(f, "ert"),
}
}
}

impl CalculusType {
pub fn is_induction_allowed(&self, direction: Direction) -> bool {
matches!(
Expand All @@ -191,6 +190,13 @@ impl CalculusType {
| (CalculusType::Ert, Direction::Up)
)
}

pub fn to_fixed_point_semantics_kind(self) -> FixpointSemanticsKind {
match self {
CalculusType::Wp | CalculusType::Ert => FixpointSemanticsKind::LeastFixedPoint,
CalculusType::Wlp => FixpointSemanticsKind::GreatestFixedPoint,
}
}
}

#[derive(Debug, Clone)]
Expand Down
51 changes: 1 addition & 50 deletions src/proof_rules/calculus/calculus_checker.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use std::{collections::HashMap, ops::DerefMut};

use crate::{
ast::{
visit::{walk_stmt, VisitorMut},
DeclKind, DeclRef, Diagnostic, Expr, ExprKind, Ident, ProcDecl, Stmt, StmtKind,
},
ast::{visit::VisitorMut, DeclKind, DeclRef, Diagnostic, Expr, ExprKind, Ident, ProcDecl},
intrinsic::annotations::{
AnnotationError, AnnotationKind, AnnotationUnsoundnessError, Calculus,
},
Expand Down Expand Up @@ -143,50 +140,4 @@ impl<'tcx> VisitorMut for CalculusVisitor<'tcx> {

res
}

fn visit_stmt(&mut self, s: &mut Stmt) -> Result<(), Self::Err> {
match &mut s.node {
// If the statement is an annotation, transform it
StmtKind::Annotation(_, ident, _, inner_stmt) => {
// First visit the statement that is annotated and handle inner annotations
self.visit_stmt(inner_stmt)?;

if let DeclKind::AnnotationDecl(AnnotationKind::Encoding(anno_ref)) =
self.tcx.get(*ident).unwrap().as_ref()
{
// Try to get the current procedure context
let proc_context = self
.proc_context
.as_ref()
// If there is no procedure context, throw an error
.ok_or(CalculusVisitorError::AnnotationError(
AnnotationError::NotInProcedure {
span: s.span,
annotation_name: *ident,
},
))?;

// Unpack the current procedure context
let direction = proc_context.direction;

// Check if the calculus annotation is compatible with the encoding annotation
if let Some(calculus) = proc_context.calculus {
// If calculus is not allowed, return an error
if !anno_ref.is_calculus_allowed(calculus, direction) {
return Err(CalculusVisitorError::UnsoundnessError(
AnnotationUnsoundnessError::CalculusEncodingMismatch {
direction,
span: s.span,
calculus_name: calculus.name,
enc_name: anno_ref.name(),
},
));
};
}
}
}
_ => walk_stmt(self, s)?,
}
Ok(())
}
}
2 changes: 2 additions & 0 deletions src/proof_rules/calculus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ mod calculus_checker;
pub use calculus_checker::*;
mod recursion_checker;
pub use recursion_checker::*;
mod soundness_checker;
pub use soundness_checker::*;

#[cfg(test)]
mod tests;
Loading
Loading