Skip to content

Commit

Permalink
Fixing several issues with malformed triggers (#827)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcoeilers authored Feb 3, 2025
1 parent cd64175 commit 75fe55c
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 10 deletions.
4 changes: 2 additions & 2 deletions src/main/scala/viper/silver/ast/Expression.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ case class GtCmp(left: Exp, right: Exp)(val pos: Position = NoPosition, val info
case class GeCmp(left: Exp, right: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends DomainBinExp(GeOp) with ForbiddenInTrigger

// Equality and non-equality (defined for all types)
case class EqCmp(left: Exp, right: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends EqualityCmp("==") {
case class EqCmp(left: Exp, right: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends EqualityCmp("==") with ForbiddenInTrigger {
override lazy val check : Seq[ConsistencyError] =
Seq(left, right).flatMap(Consistency.checkPure) ++
(if(left.typ != right.typ) Seq(ConsistencyError(s"expected the same type, but got ${left.typ} and ${right.typ}", left.pos)) else Seq())
}
case class NeCmp(left: Exp, right: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends EqualityCmp("!=") {
case class NeCmp(left: Exp, right: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends EqualityCmp("!=") with ForbiddenInTrigger {
override lazy val check : Seq[ConsistencyError] =
Seq(left, right).flatMap(Consistency.checkPure) ++
(if(left.typ != right.typ) Seq(ConsistencyError(s"expected the same type, but got ${left.typ} and ${right.typ}", left.pos)) else Seq())
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/viper/silver/ast/utility/Consistency.scala
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,8 @@ object Consistency {
/** checks that all quantified variables appear in all triggers */
def checkAllVarsMentionedInTriggers(variables: Seq[LocalVarDecl], triggers: Seq[Trigger]) : Seq[ConsistencyError] = {
var s = Seq.empty[ConsistencyError]
val varsInTriggers : Seq[Seq[LocalVar]] = triggers map(t=>{
t.deepCollect({case lv: LocalVar => lv})
val varsInTriggers : Seq[Set[LocalVar]] = triggers map(t=>{
Expressions.getContainedVariablesExcludingLet(t)
})
variables.foreach(v=>{
varsInTriggers.foreach(varList=>{
Expand Down
17 changes: 17 additions & 0 deletions src/main/scala/viper/silver/ast/utility/Expressions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,23 @@ object Expressions {
}.flatten.toSet
}

/** Collects all variables that are actually contained in the given node, filtering out let-variables
* as well as variables used in expressions bound to let-variables which are not used in the let body. */
def getContainedVariablesExcludingLet(e: Node): Set[LocalVar] = {
Visitor.deepCollect[Node, Set[LocalVar]](Seq(e), {
case _: Let => Seq()
case n => Nodes.subnodes(n)
}) {
case lv: LocalVar => Set(lv)
case Let(v, e, body) =>
val bodyVars = getContainedVariablesExcludingLet(body)
if (bodyVars.contains(v.localVar))
bodyVars - v.localVar ++ getContainedVariablesExcludingLet(e)
else
bodyVars - v.localVar
}.flatten.toSet
}

/** In an expression, rename a list (domain) of variables with given (range) variables. */
def renameVariables[E <: Exp]
(exp: E, domain: Seq[AbstractLocalVar], range: Seq[AbstractLocalVar])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package viper.silver.ast.utility

import java.util.concurrent.atomic.AtomicInteger
import reflect.ClassTag
import scala.annotation.unused

object GenericTriggerGenerator {
case class TriggerSet[E](exps: Seq[E])
Expand Down Expand Up @@ -193,7 +194,7 @@ abstract class GenericTriggerGenerator[Node <: AnyRef,
})

/* Collect all the sought (vs) variables in the function application */
processedArgs foreach (arg => visit(arg) {
processedArgs foreach (arg => getVarsInExp(arg) foreach {
case v: Var =>
if (nestedBoundVars.contains(v)) containsNestedBoundVars = true
if (allRelevantVars.contains(v)) containedVars +:= v
Expand All @@ -210,17 +211,25 @@ abstract class GenericTriggerGenerator[Node <: AnyRef,
})
}

def getVarsInExp(e: Exp): Set[Var] = {
var result: Set[Var] = Set()
visit(e) {
case v: Var => result += v
}
result
}

/*
* Hook for clients to add more cases to getFunctionAppsContaining to modify the found possible triggers.
* Used e.g. to wrap trigger expressions inferred from inside old-expression into old()
*/
def modifyPossibleTriggers(relevantVars: Seq[Var]): PartialFunction[Node, Seq[Seq[(PossibleTrigger, Seq[Var], Seq[Var])]] =>
def modifyPossibleTriggers(@unused relevantVars: Seq[Var]): PartialFunction[Node, Seq[Seq[(PossibleTrigger, Seq[Var], Seq[Var])]] =>
Seq[(PossibleTrigger, Seq[Var], Seq[Var])]] = PartialFunction.empty

/*
* Hook for clients to identify additional variables which can be covered by triggers.
*/
def additionalRelevantVariables(relevantVars: Seq[Var], varsToAvoid: Seq[Var]): PartialFunction[Node, Seq[Var]] = PartialFunction.empty
def additionalRelevantVariables(@unused relevantVars: Seq[Var], @unused varsToAvoid: Seq[Var]): PartialFunction[Node, Seq[Var]] = PartialFunction.empty

/* Precondition: if vars is non-empty then every (f,vs) pair in functs
* satisfies the property that vars and vs are not disjoint.
Expand Down
11 changes: 8 additions & 3 deletions src/main/scala/viper/silver/ast/utility/Triggers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ object Triggers {
case _ => sys.error(s"Unexpected expression $e")
}

override def getVarsInExp(e: Exp): Set[LocalVar] = {
Expressions.getContainedVariablesExcludingLet(e)
}

override def modifyPossibleTriggers(relevantVars: Seq[LocalVar]) = {
case ast.Old(_) => results =>
results.flatten.map(t => {
Expand All @@ -69,16 +73,17 @@ object Triggers {
val exp = t._1
(ast.LabelledOld(exp, l)(exp.pos, exp.info, exp.errT), t._2, t._3)
})
case ast.Let(v, e, _) => results =>
case ast.Let(v, e, body) => results =>
results.flatten.map(t => {
val exp = t._1
val coveredVars = t._2.filter(cv => cv != v.localVar) ++ relevantVars.filter(rv => e.contains(rv))
val coveredVars = t._2.filter(cv => cv != v.localVar) ++
(if (body.contains(v.localVar)) relevantVars.filter(rv => e.contains(rv)) else Seq())
(exp.replace(v.localVar, e), coveredVars, t._3)
})
}

override def additionalRelevantVariables(relevantVars: Seq[LocalVar], varsToAvoid: Seq[LocalVar]): PartialFunction[Node, Seq[LocalVar]] = {
case ast.Let(v, e, _) if relevantVars.exists(v => e.contains(v)) && varsToAvoid.forall(v => !e.contains(v)) =>
case ast.Let(v, e, body) if body.contains(v.localVar) && relevantVars.exists(v => e.contains(v)) && varsToAvoid.forall(v => !e.contains(v)) =>
Seq(v.localVar)
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/test/resources/all/issues/carbon/0406.vpr
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/

field f: Ref

method m1() {
Expand Down
3 changes: 3 additions & 0 deletions src/test/resources/all/issues/carbon/0422.vpr
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/

method m1() {
while (true) {
goto bubble
Expand Down
13 changes: 13 additions & 0 deletions src/test/resources/all/issues/carbon/0489.vpr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/

method test1()
ensures (forall q: Bool :: id(q == q))
{}

function id(a : Bool): Bool { a }


method test3()
ensures (forall q: Int :: id(let x == (q) in (true)))
{}
5 changes: 5 additions & 0 deletions src/test/scala/ConsistencyTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ class ConsistencyTests extends AnyFunSuite with Matchers {
val f = Function("f", Seq(LocalVarDecl("i", Int)()), Int, Seq(), Seq(), None)()
val forallNoVar = Forall(Seq(), Seq(), TrueLit()())()
val forallUnusedVar = Forall(Seq(LocalVarDecl("i", Int)(), LocalVarDecl("j", Int)()), Seq(Trigger(Seq(FuncApp(f, Seq(LocalVar("j", Int)()))()))()), TrueLit()())()
val forallUnusedVarLet = Forall(Seq(LocalVarDecl("i", Int)()), Seq(Trigger(Seq(FuncApp(f, Seq(Let(LocalVarDecl("j", Int)(), LocalVar("i", Int)(), TrueLit()())()))()))()), TrueLit()())()
val forallNoVarTrigger = Forall(Seq(LocalVarDecl("i", Int)()), Seq(Trigger(Seq(FuncApp(f, Seq(LocalVar("i", Int)()))(), FuncApp(f, Seq(IntLit(0)()))()))()), TrueLit()())()

forallNoVar.checkTransitively shouldBe Seq(
Expand All @@ -273,6 +274,10 @@ class ConsistencyTests extends AnyFunSuite with Matchers {
ConsistencyError("Variable i is not mentioned in one or more triggers.", NoPosition)
)

forallUnusedVarLet.checkTransitively shouldBe Seq(
ConsistencyError("Variable i is not mentioned in one or more triggers.", NoPosition)
)

forallNoVarTrigger.checkTransitively shouldBe Seq(
ConsistencyError("Trigger expression f(0) does not contain any quantified variable.", NoPosition)
)
Expand Down

0 comments on commit 75fe55c

Please sign in to comment.