Skip to content

Commit 75fe55c

Browse files
authored
Fixing several issues with malformed triggers (#827)
1 parent cd64175 commit 75fe55c

File tree

9 files changed

+65
-10
lines changed

9 files changed

+65
-10
lines changed

Diff for: src/main/scala/viper/silver/ast/Expression.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,12 @@ case class GtCmp(left: Exp, right: Exp)(val pos: Position = NoPosition, val info
7373
case class GeCmp(left: Exp, right: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends DomainBinExp(GeOp) with ForbiddenInTrigger
7474

7575
// Equality and non-equality (defined for all types)
76-
case class EqCmp(left: Exp, right: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends EqualityCmp("==") {
76+
case class EqCmp(left: Exp, right: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends EqualityCmp("==") with ForbiddenInTrigger {
7777
override lazy val check : Seq[ConsistencyError] =
7878
Seq(left, right).flatMap(Consistency.checkPure) ++
7979
(if(left.typ != right.typ) Seq(ConsistencyError(s"expected the same type, but got ${left.typ} and ${right.typ}", left.pos)) else Seq())
8080
}
81-
case class NeCmp(left: Exp, right: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends EqualityCmp("!=") {
81+
case class NeCmp(left: Exp, right: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends EqualityCmp("!=") with ForbiddenInTrigger {
8282
override lazy val check : Seq[ConsistencyError] =
8383
Seq(left, right).flatMap(Consistency.checkPure) ++
8484
(if(left.typ != right.typ) Seq(ConsistencyError(s"expected the same type, but got ${left.typ} and ${right.typ}", left.pos)) else Seq())

Diff for: src/main/scala/viper/silver/ast/utility/Consistency.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,8 @@ object Consistency {
235235
/** checks that all quantified variables appear in all triggers */
236236
def checkAllVarsMentionedInTriggers(variables: Seq[LocalVarDecl], triggers: Seq[Trigger]) : Seq[ConsistencyError] = {
237237
var s = Seq.empty[ConsistencyError]
238-
val varsInTriggers : Seq[Seq[LocalVar]] = triggers map(t=>{
239-
t.deepCollect({case lv: LocalVar => lv})
238+
val varsInTriggers : Seq[Set[LocalVar]] = triggers map(t=>{
239+
Expressions.getContainedVariablesExcludingLet(t)
240240
})
241241
variables.foreach(v=>{
242242
varsInTriggers.foreach(varList=>{

Diff for: src/main/scala/viper/silver/ast/utility/Expressions.scala

+17
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,23 @@ object Expressions {
136136
}.flatten.toSet
137137
}
138138

139+
/** Collects all variables that are actually contained in the given node, filtering out let-variables
140+
* as well as variables used in expressions bound to let-variables which are not used in the let body. */
141+
def getContainedVariablesExcludingLet(e: Node): Set[LocalVar] = {
142+
Visitor.deepCollect[Node, Set[LocalVar]](Seq(e), {
143+
case _: Let => Seq()
144+
case n => Nodes.subnodes(n)
145+
}) {
146+
case lv: LocalVar => Set(lv)
147+
case Let(v, e, body) =>
148+
val bodyVars = getContainedVariablesExcludingLet(body)
149+
if (bodyVars.contains(v.localVar))
150+
bodyVars - v.localVar ++ getContainedVariablesExcludingLet(e)
151+
else
152+
bodyVars - v.localVar
153+
}.flatten.toSet
154+
}
155+
139156
/** In an expression, rename a list (domain) of variables with given (range) variables. */
140157
def renameVariables[E <: Exp]
141158
(exp: E, domain: Seq[AbstractLocalVar], range: Seq[AbstractLocalVar])

Diff for: src/main/scala/viper/silver/ast/utility/GenericTriggerGenerator.scala

+12-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package viper.silver.ast.utility
88

99
import java.util.concurrent.atomic.AtomicInteger
1010
import reflect.ClassTag
11+
import scala.annotation.unused
1112

1213
object GenericTriggerGenerator {
1314
case class TriggerSet[E](exps: Seq[E])
@@ -193,7 +194,7 @@ abstract class GenericTriggerGenerator[Node <: AnyRef,
193194
})
194195

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

214+
def getVarsInExp(e: Exp): Set[Var] = {
215+
var result: Set[Var] = Set()
216+
visit(e) {
217+
case v: Var => result += v
218+
}
219+
result
220+
}
221+
213222
/*
214223
* Hook for clients to add more cases to getFunctionAppsContaining to modify the found possible triggers.
215224
* Used e.g. to wrap trigger expressions inferred from inside old-expression into old()
216225
*/
217-
def modifyPossibleTriggers(relevantVars: Seq[Var]): PartialFunction[Node, Seq[Seq[(PossibleTrigger, Seq[Var], Seq[Var])]] =>
226+
def modifyPossibleTriggers(@unused relevantVars: Seq[Var]): PartialFunction[Node, Seq[Seq[(PossibleTrigger, Seq[Var], Seq[Var])]] =>
218227
Seq[(PossibleTrigger, Seq[Var], Seq[Var])]] = PartialFunction.empty
219228

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

225234
/* Precondition: if vars is non-empty then every (f,vs) pair in functs
226235
* satisfies the property that vars and vs are not disjoint.

Diff for: src/main/scala/viper/silver/ast/utility/Triggers.scala

+8-3
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ object Triggers {
5858
case _ => sys.error(s"Unexpected expression $e")
5959
}
6060

61+
override def getVarsInExp(e: Exp): Set[LocalVar] = {
62+
Expressions.getContainedVariablesExcludingLet(e)
63+
}
64+
6165
override def modifyPossibleTriggers(relevantVars: Seq[LocalVar]) = {
6266
case ast.Old(_) => results =>
6367
results.flatten.map(t => {
@@ -69,16 +73,17 @@ object Triggers {
6973
val exp = t._1
7074
(ast.LabelledOld(exp, l)(exp.pos, exp.info, exp.errT), t._2, t._3)
7175
})
72-
case ast.Let(v, e, _) => results =>
76+
case ast.Let(v, e, body) => results =>
7377
results.flatten.map(t => {
7478
val exp = t._1
75-
val coveredVars = t._2.filter(cv => cv != v.localVar) ++ relevantVars.filter(rv => e.contains(rv))
79+
val coveredVars = t._2.filter(cv => cv != v.localVar) ++
80+
(if (body.contains(v.localVar)) relevantVars.filter(rv => e.contains(rv)) else Seq())
7681
(exp.replace(v.localVar, e), coveredVars, t._3)
7782
})
7883
}
7984

8085
override def additionalRelevantVariables(relevantVars: Seq[LocalVar], varsToAvoid: Seq[LocalVar]): PartialFunction[Node, Seq[LocalVar]] = {
81-
case ast.Let(v, e, _) if relevantVars.exists(v => e.contains(v)) && varsToAvoid.forall(v => !e.contains(v)) =>
86+
case ast.Let(v, e, body) if body.contains(v.localVar) && relevantVars.exists(v => e.contains(v)) && varsToAvoid.forall(v => !e.contains(v)) =>
8287
Seq(v.localVar)
8388
}
8489
}

Diff for: src/test/resources/all/issues/carbon/0406.vpr

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Any copyright is dedicated to the Public Domain.
2+
// http://creativecommons.org/publicdomain/zero/1.0/
3+
14
field f: Ref
25

36
method m1() {

Diff for: src/test/resources/all/issues/carbon/0422.vpr

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Any copyright is dedicated to the Public Domain.
2+
// http://creativecommons.org/publicdomain/zero/1.0/
3+
14
method m1() {
25
while (true) {
36
goto bubble

Diff for: src/test/resources/all/issues/carbon/0489.vpr

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Any copyright is dedicated to the Public Domain.
2+
// http://creativecommons.org/publicdomain/zero/1.0/
3+
4+
method test1()
5+
ensures (forall q: Bool :: id(q == q))
6+
{}
7+
8+
function id(a : Bool): Bool { a }
9+
10+
11+
method test3()
12+
ensures (forall q: Int :: id(let x == (q) in (true)))
13+
{}

Diff for: src/test/scala/ConsistencyTests.scala

+5
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ class ConsistencyTests extends AnyFunSuite with Matchers {
263263
val f = Function("f", Seq(LocalVarDecl("i", Int)()), Int, Seq(), Seq(), None)()
264264
val forallNoVar = Forall(Seq(), Seq(), TrueLit()())()
265265
val forallUnusedVar = Forall(Seq(LocalVarDecl("i", Int)(), LocalVarDecl("j", Int)()), Seq(Trigger(Seq(FuncApp(f, Seq(LocalVar("j", Int)()))()))()), TrueLit()())()
266+
val forallUnusedVarLet = Forall(Seq(LocalVarDecl("i", Int)()), Seq(Trigger(Seq(FuncApp(f, Seq(Let(LocalVarDecl("j", Int)(), LocalVar("i", Int)(), TrueLit()())()))()))()), TrueLit()())()
266267
val forallNoVarTrigger = Forall(Seq(LocalVarDecl("i", Int)()), Seq(Trigger(Seq(FuncApp(f, Seq(LocalVar("i", Int)()))(), FuncApp(f, Seq(IntLit(0)()))()))()), TrueLit()())()
267268

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

277+
forallUnusedVarLet.checkTransitively shouldBe Seq(
278+
ConsistencyError("Variable i is not mentioned in one or more triggers.", NoPosition)
279+
)
280+
276281
forallNoVarTrigger.checkTransitively shouldBe Seq(
277282
ConsistencyError("Trigger expression f(0) does not contain any quantified variable.", NoPosition)
278283
)

0 commit comments

Comments
 (0)