Skip to content

Commit

Permalink
More misplaced impr. checks; move QR desugaring to a separate module
Browse files Browse the repository at this point in the history
  • Loading branch information
vfukala committed Jul 14, 2023
1 parent 4314482 commit 133a75f
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 104 deletions.
3 changes: 2 additions & 1 deletion prusti-common/src/vir/fixes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

//! Fix the potentially broken encoding.

pub use self::ghost_vars::fix_ghost_vars;
pub use self::{ghost_vars::fix_ghost_vars, quantified_resources::desugar_quantified_resources};

mod ghost_vars;
mod quantified_resources;
197 changes: 197 additions & 0 deletions prusti-common/src/vir/fixes/quantified_resources.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// © 2023, ETH Zurich
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! Desugar quantified permissions syntax.

use crate::vir::polymorphic_vir::{
ast,
ast::{ExprFolder, StmtFolder},
Program,
};

/// During the translation step from the parse AST to real Viper AST,
/// Silver desugars the quantified permissions syntax so that all
/// quantified permissions quantifiers have the form forall xs ::
/// c(xs) ==> acc(...) for some condition c and access acc(...).
/// Backend verifiers rely on this and crash otherwise. Since we
/// directly generate Viper AST and in Silver, this desugaring step
/// is built into the Translator (so we can't call it in Silver as a
/// separate pass), we do the desugaring ourselves here.

pub fn desugar_quantified_resources(program: Program) -> Program {
// add other parts of the Viper program to desugar if needed
let Program {
functions, methods, ..
} = program;

let desugared_functions = functions
.into_iter()
.map(|f| {
let ast::Function { body, .. } = f;
ast::Function {
body: body.map(|expr| ExprFolder::fold(&mut Desugarer {}, expr)),
..f
}
})
.collect::<Vec<_>>();

let desugared_methods = methods
.into_iter()
.map(|m| m.patch_statements(|stmt| Ok(StmtFolder::fold(&mut Desugarer {}, stmt))))
.collect::<Result<Vec<_>, ()>>()
.unwrap();

Program {
functions: desugared_functions,
methods: desugared_methods,
..program
}
}

struct Desugarer;

impl Desugarer {
fn desugar(e: ast::Expr) -> ast::Expr {
match e {
// transform (forall A) into (forall true ==> A) if A is not an implication
ast::Expr::ForAll(ast::ForAll {
variables,
triggers,
body: box body,
position,
}) if !body.is_pure()
&& !matches!(
body,
ast::Expr::BinOp(ast::BinOp {
op_kind: ast::BinaryOpKind::Implies,
..
})
) =>
{
Self::desugar(
ast::Expr::forall(
variables,
triggers,
ast::Expr::implies(true.into(), body.clone()).set_pos(body.pos()),
)
.set_pos(position),
)
}
// transform (forall A ==> B ==> C) into (forall (A && B) ==> C)
ast::Expr::ForAll(ast::ForAll {
variables,
triggers,
body:
box ast::Expr::BinOp(ast::BinOp {
op_kind: ast::BinaryOpKind::Implies,
left: box c0,
right:
box ast::Expr::BinOp(ast::BinOp {
op_kind: ast::BinaryOpKind::Implies,
left: box c1,
right: box ex,
..
}),
position: pos_body,
}),
position: pos_quant,
}) if !ex.is_pure() => Self::desugar(
ast::Expr::forall(
variables,
triggers,
ast::Expr::implies(ast::Expr::and(c0, c1).set_pos(pos_body), ex)
.set_pos(pos_body),
)
.set_pos(pos_quant),
),
// transform (forall A ==> (B ? C : D)) into (forall A ==> ((B ==> C) && (!B ==> D)))
ast::Expr::ForAll(ast::ForAll {
variables,
triggers,
body:
box ast::Expr::BinOp(ast::BinOp {
op_kind: ast::BinaryOpKind::Implies,
left: box filter,
right:
box ast::Expr::Cond(ast::Cond {
guard: box guard,
then_expr: box then_expr,
else_expr: box else_expr,
position: pos_cond,
}),
position: pos_body,
}),
position: pos_quant,
}) if !then_expr.is_pure() || !else_expr.is_pure() => Self::desugar(
ast::Expr::forall(
variables,
triggers,
ast::Expr::implies(
filter,
ast::Expr::and(
ast::Expr::implies(guard.clone(), then_expr).set_pos(pos_cond),
ast::Expr::implies(ast::Expr::not(guard).set_pos(pos_cond), else_expr),
)
.set_pos(pos_cond),
)
.set_pos(pos_body),
)
.set_pos(pos_quant),
),
// transform (forall A ==> (B && C)) into ((forall A ==> B) && (forall A ==> C))
ast::Expr::ForAll(ast::ForAll {
variables,
triggers,
body:
box ast::Expr::BinOp(ast::BinOp {
op_kind: ast::BinaryOpKind::Implies,
left: box cond,
right:
box ast::Expr::BinOp(ast::BinOp {
op_kind: ast::BinaryOpKind::And,
left: box part0,
right: box part1,
..
}),
position: pos_body,
}),
position: pos_quant,
}) if !part0.is_pure() || !part1.is_pure() => ast::Expr::and(
Self::desugar(
ast::Expr::forall(
variables.clone(),
triggers.clone(),
ast::Expr::implies(cond.clone(), part0).set_pos(pos_body),
)
.set_pos(pos_quant),
),
Self::desugar(
ast::Expr::forall(
variables,
triggers,
ast::Expr::implies(cond, part1).set_pos(pos_body),
)
.set_pos(pos_quant),
),
)
.set_pos(pos_quant),
r => r,
}
}
}

impl ExprFolder for Desugarer {
fn fold(&mut self, e: ast::Expr) -> ast::Expr {
let folded_expr = ast::default_fold_expr(self, e);
Self::desugar(folded_expr)
}
}

impl StmtFolder for Desugarer {
fn fold_expr(&mut self, e: ast::Expr) -> ast::Expr {
ExprFolder::fold(self, e)
}
}
90 changes: 0 additions & 90 deletions prusti-common/src/vir/optimizations/functions/simplifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,96 +207,6 @@ impl ExprSimplifier {
right: Box::new(Self::apply_rules(op2)),
position: pos,
}),
// the following transformations of foralls are necessary because the verifier backends rely on them (they are normally done by Silver when traslating the parse AST into real Viper AST);
// alternatively, consider calling the Silver functions that do this
// transform (forall A ==> B ==> C) into (forall (A && B) ==> C)
ast::Expr::ForAll(ast::ForAll {
variables,
triggers,
body:
box ast::Expr::BinOp(ast::BinOp {
op_kind: ast::BinaryOpKind::Implies,
left: box c0,
right:
box ast::Expr::BinOp(ast::BinOp {
op_kind: ast::BinaryOpKind::Implies,
left: box c1,
right: box ex,
..
}),
position: pos_body,
}),
position: pos_quant,
}) if !ex.is_pure() => Self::apply_rules(
ast::Expr::forall(
variables,
triggers,
ast::Expr::implies(ast::Expr::and(c0, c1).set_pos(pos_body), ex)
.set_pos(pos_body),
)
.set_pos(pos_quant),
),
// transform (forall A) into (forall true ==> A) if A is not an implication
ast::Expr::ForAll(ast::ForAll {
variables,
triggers,
body: box body,
position,
}) if !body.is_pure()
&& !matches!(
body,
ast::Expr::BinOp(ast::BinOp {
op_kind: ast::BinaryOpKind::Implies,
..
})
) =>
{
Self::apply_rules(
ast::Expr::forall(
variables,
triggers,
ast::Expr::implies(true.into(), body.clone()).set_pos(body.pos()),
)
.set_pos(position),
)
}
// transform (forall A ==> (B && C)) into ((forall A ==> B) && (forall A ==> C))
ast::Expr::ForAll(ast::ForAll {
variables,
triggers,
body:
box ast::Expr::BinOp(ast::BinOp {
op_kind: ast::BinaryOpKind::Implies,
left: box cond,
right:
box ast::Expr::BinOp(ast::BinOp {
op_kind: ast::BinaryOpKind::And,
left: box part0,
right: box part1,
..
}),
position: pos_body,
}),
position: pos_quant,
}) if !part0.is_pure() || !part1.is_pure() => ast::Expr::and(
Self::apply_rules(
ast::Expr::forall(
variables.clone(),
triggers.clone(),
ast::Expr::implies(cond.clone(), part0).set_pos(pos_body),
)
.set_pos(pos_quant),
),
Self::apply_rules(
ast::Expr::forall(
variables,
triggers,
ast::Expr::implies(cond, part1).set_pos(pos_body),
)
.set_pos(pos_quant),
),
)
.set_pos(pos_quant),
r => r,
}
}
Expand Down
31 changes: 30 additions & 1 deletion prusti-tests/tests/verify/fail/obligations/misplaced_impure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ obligation! {
fn alloced(amount: usize, loc: usize);
}

#[pure]
fn f(flag: bool) -> bool {
true
}

resource! {
fn rsrc(amount: usize, flag: bool);
}

#[ensures((result > 0) | alloced(1, 1))] //~ ERROR resource access is illegal inside disjunctions
fn f1() -> i32 { 0 }

Expand All @@ -22,6 +31,26 @@ fn f5() -> i32 { 0 }
#[ensures((result > 0) != alloced(1, 1))] //~ ERROR resource access is illegal inside equality comparisons
fn f6() -> i32 { 0 }

// TODO add obligations in conditionals to this test
#[ensures(!alloced(1, 1))] //~ ERROR resource access is illegal inside negations
fn f7() -> i32 { 0 }

#[ensures(f(alloced(1, 1)))] //~ ERROR resource access is illegal inside function arguments
fn f8() -> i32 { 0 }

#[ensures(rsrc(1, alloced(1, 1)))] //~ ERROR resource access is illegal inside resource access arguments
fn f9() -> i32 { 0 }

#[ensures(exists(|loc: usize| alloced(1, loc)))] //~ ERROR resource access is illegal inside existential quantifiers
fn f10() -> i32 { 0 }

// FIXME the following tests report confusing places for where the impurity is

// TODO add test for resource access in the guard of a conditional when the error messages there are made to work properly in future

#[ensures(alloced(1, 1) || result > 0)] //~ ERROR resource access is illegal inside
fn f11() -> i32 { 0 }

#[ensures(alloced(1, 1) && result > 0)] //~ ERROR resource access is illegal inside
fn f12() -> i32 { 0 }

fn main() {}
19 changes: 19 additions & 0 deletions prusti-tests/tests/verify/pass/obligations/impure_placement.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use prusti_contracts::*;

obligation! {
fn alloced(amount: usize, loc: usize);
}

#[requires(a > 0 || alloced(1, 1))]
#[ensures(a > 0 || alloced(1, 1))]
fn f1(a: i32) {}

#[ensures(a > 0 && alloced(1, 1))]
#[requires(a > 0 && alloced(1, 1))]
fn f2(a: i32) -> i32 { 0 }

#[ensures(if a > 0 { alloced(1, 1) } else { alloced(33, 42) })]
#[requires(if a > 0 { alloced(1, 1) } else { alloced(33, 42) })]
fn f3(a: i32) -> i32 { 0 }

fn main() {}
Loading

0 comments on commit 133a75f

Please sign in to comment.