Skip to content

Commit f96ac01

Browse files
authored
Checking only read permissions when asserting function preconditions (#816)
1 parent f7c9468 commit f96ac01

27 files changed

Lines changed: 585 additions & 82 deletions

src/main/scala/viper/silver/ast/Expression.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,16 +285,18 @@ object AccessPredicate {
285285
}
286286

287287
/** An accessibility predicate for a field location. */
288-
case class FieldAccessPredicate(loc: FieldAccess, perm: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends AccessPredicate {
288+
case class FieldAccessPredicate(loc: FieldAccess, permExp: Option[Exp])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends AccessPredicate {
289+
val perm = permExp.getOrElse(FullPerm()(pos, NoInfo, NoTrafos))
289290
override lazy val check : Seq[ConsistencyError] =
290-
(if(!(perm isSubtype Perm)) Seq(ConsistencyError(s"Permission amount parameter of access predicate must be of Perm type, but found ${perm.typ}", perm.pos)) else Seq()) ++ Consistency.checkWildcardUsage(perm)
291+
(if(!(perm isSubtype Perm)) Seq(ConsistencyError(s"Permission amount parameter of access predicate must be of Perm type, but found ${perm.typ}", perm.pos)) else Seq())
291292
val typ: Bool.type = Bool
292293
}
293294

294295
/** An accessibility predicate for a predicate location. */
295-
case class PredicateAccessPredicate(loc: PredicateAccess, perm: Exp)(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends AccessPredicate {
296+
case class PredicateAccessPredicate(loc: PredicateAccess, permExp: Option[Exp])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends AccessPredicate {
297+
val perm = permExp.getOrElse(FullPerm()(pos, NoInfo, NoTrafos))
296298
override lazy val check : Seq[ConsistencyError] =
297-
(if(!(perm isSubtype Perm)) Seq(ConsistencyError(s"Permission amount parameter of access predicate must be of Perm type, but found ${perm.typ}", perm.pos)) else Seq()) ++ Consistency.checkWildcardUsage(perm)
299+
(if(!(perm isSubtype Perm)) Seq(ConsistencyError(s"Permission amount parameter of access predicate must be of Perm type, but found ${perm.typ}", perm.pos)) else Seq())
298300
val typ: Bool.type = Bool
299301
}
300302

src/main/scala/viper/silver/ast/Program.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ case class Field(name: String, typ: Type)(val pos: Position = NoPosition, val in
361361
/** A predicate declaration. */
362362
case class Predicate(name: String, formalArgs: Seq[LocalVarDecl], body: Option[Exp])(val pos: Position = NoPosition, val info: Info = NoInfo, val errT: ErrorTrafo = NoTrafos) extends Location {
363363
override lazy val check : Seq[ConsistencyError] =
364-
(if (body.isDefined) Consistency.checkNonPostContract(body.get) else Seq()) ++
364+
(if (body.isDefined) Consistency.checkNonPostContract(body.get) ++ Consistency.checkWildcardUsage(body.get, false) else Seq()) ++
365365
(if (body.isDefined && !Consistency.noOld(body.get))
366366
Seq(ConsistencyError("Predicates must not contain old expressions.",body.get.pos))
367367
else Seq()) ++
@@ -426,7 +426,8 @@ case class Method(name: String, formalArgs: Seq[LocalVarDecl], formalReturns: Se
426426
body.fold(Seq.empty[ConsistencyError])(Consistency.checkNoArgsReassigned(formalArgs, _)) ++
427427
(if (!((formalArgs ++ formalReturns) forall (_.typ.isConcrete))) Seq(ConsistencyError("Formal args and returns must have concrete types.", pos)) else Seq()) ++
428428
(pres ++ posts).flatMap(Consistency.checkNoPermForpermExceptInhaleExhale) ++
429-
checkReturnsNotUsedInPreconditions
429+
checkReturnsNotUsedInPreconditions ++
430+
(pres ++ posts ++ body.toSeq).flatMap(Consistency.checkWildcardUsage(_, false))
430431

431432
lazy val checkReturnsNotUsedInPreconditions: Seq[ConsistencyError] = {
432433
val varsInPreconditions: Seq[LocalVar] = pres flatMap {_.deepCollect {case l: LocalVar => l}}
@@ -454,6 +455,7 @@ case class Function(name: String, formalArgs: Seq[LocalVarDecl], typ: Type, pres
454455
posts.flatMap(p=>{ if(!Consistency.noOld(p))
455456
Seq(ConsistencyError("Function post-conditions must not have old expressions.", p.pos)) else Seq()}) ++
456457
(pres ++ posts).flatMap(Consistency.checkNoPermForpermExceptInhaleExhale) ++
458+
(pres ++ posts ++ body.toSeq).flatMap(Consistency.checkWildcardUsage(_, true)) ++
457459
(if(!(body forall (_ isSubtype typ))) Seq(ConsistencyError("Type of function body must match function type.", pos)) else Seq() ) ++
458460
(posts flatMap (p => if (!Consistency.noPerm(p) || !Consistency.noForPerm(p)) Seq(ConsistencyError("perm and forperm expressions are not allowed in function postconditions", p.pos)) else Seq() )) ++
459461
pres.flatMap(Consistency.checkPre) ++

src/main/scala/viper/silver/ast/utility/Consistency.scala

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import viper.silver.{FastMessage, FastMessaging}
1515
/** An utility object for consistency checking. */
1616
object Consistency {
1717
var messages: FastMessaging.Messages = Nil
18+
19+
// Set to enable legacy mode where permission amounts in function preconditions have their usual meaning instead
20+
// of just just being treated as a kind of wildcard.
21+
private var respectFunctionPrePermAmounts: Boolean = false
1822
def recordIfNot(suspect: Positioned, property: Boolean, message: String): Unit = {
1923
if (!property) {
2024
val pos = suspect.pos
@@ -23,6 +27,14 @@ object Consistency {
2327
}
2428
}
2529

30+
/** Use this method to enable consistency checks suitable for the legacy mode where permission amounts in function
31+
* preconditions have their standard meaning, instead of always meaning a kind of wildcard.
32+
* In other words, this should be set iff the command line flag "--respectFunctionPrePermAmounts" is set.
33+
* */
34+
def setFunctionPreconditionLegacyMode(enableLegacyMode: Boolean) = {
35+
respectFunctionPrePermAmounts = enableLegacyMode
36+
}
37+
2638
def resetMessages(): Unit = { this.messages = Nil }
2739
@inline
2840
def recordIf(suspect: Positioned, property: Boolean, message: String): Unit =
@@ -196,18 +208,28 @@ object Consistency {
196208
(if(!noLabelledOld(e)) Seq(ConsistencyError("Labelled-old expressions are not allowed in postconditions.", e.pos)) else Seq())
197209
}
198210

199-
def checkWildcardUsage(e: Exp): Seq[ConsistencyError] = {
200-
val containedWildcards = e.shallowCollect{
201-
case w: WildcardPerm => w
202-
}
203-
if (containedWildcards.nonEmpty) {
204-
e match {
205-
case _: WildcardPerm => Seq()
206-
case _ => Seq(ConsistencyError("Wildcard occurs inside compound expression (should only occur directly in an accessibility predicate).", e.pos))
211+
def checkWildcardUsage(n: Node, inFunction: Boolean): Seq[ConsistencyError] = {
212+
if (!respectFunctionPrePermAmounts && inFunction)
213+
return Seq()
214+
215+
def checkValidUse(e: Exp): Seq[ConsistencyError] = {
216+
val containedWildcards = e.shallowCollect {
217+
case w: WildcardPerm => w
218+
}
219+
if (containedWildcards.nonEmpty) {
220+
e match {
221+
case _: WildcardPerm => Seq()
222+
case _ => Seq(ConsistencyError("Wildcard occurs inside compound expression (should only occur directly in an accessibility predicate).", e.pos))
223+
}
224+
} else {
225+
Seq()
207226
}
208-
} else {
209-
Seq()
210227
}
228+
229+
n.collect{
230+
case FieldAccessPredicate(_, Some(prm)) => checkValidUse(prm)
231+
case PredicateAccessPredicate(_, Some(prm)) => checkValidUse(prm)
232+
}.flatten.toSeq
211233
}
212234

213235
/** checks that all quantified variables appear in all triggers */

src/main/scala/viper/silver/ast/utility/Expressions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ object Expressions {
193193
}
194194
// Conditions for the current node.
195195
val conds: Seq[Exp] = n match {
196-
case f@FieldAccess(rcv, _) => List(NeCmp(rcv, NullLit()(p))(p), FieldAccessPredicate(f, WildcardPerm()(p))(p))
196+
case f@FieldAccess(rcv, _) => List(NeCmp(rcv, NullLit()(p))(p), FieldAccessPredicate(f, Some(WildcardPerm()(p)))(p))
197197
case f: FuncApp => prog.findFunction(f.funcname).pres
198198
case Div(_, q) => List(NeCmp(q, IntLit(0)(p))(p))
199199
case Mod(_, q) => List(NeCmp(q, IntLit(0)(p))(p))

src/main/scala/viper/silver/ast/utility/InverseFunctions.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ object InverseFunctions {
3737
val axiom1 = Forall(qvars, forall.triggers, Implies(cond, equalities)(pos, info, errT))(pos, info, errT)
3838
var condReplaced = cond
3939
var rcvReplaced = fap.loc.rcv
40-
var permReplaced = fap.perm
40+
var permReplaced = fap.permExp
4141
for (i <- 0 until qvars.length){
4242
condReplaced = condReplaced.replace(qvars(i).localVar, invsOfR(i))
4343
rcvReplaced = rcvReplaced.replace(qvars(i).localVar, invsOfR(i))
44-
permReplaced = permReplaced.replace(qvars(i).localVar, invsOfR(i))
44+
permReplaced = permReplaced.map(_.replace(qvars(i).localVar, invsOfR(i)))
4545
}
4646
val axiom2 = Forall(Seq(r), Seq(Trigger(invsOfR)(pos, info, errT)), Implies(condReplaced, EqCmp(rcvReplaced, r.localVar)(pos, info, errT))(pos, info, errT))(pos, info, errT)
4747
val acc1 = FieldAccessPredicate(FieldAccess(r.localVar, fap.loc.field)(), permReplaced)()
@@ -65,7 +65,7 @@ object InverseFunctions {
6565
val axiom2 = Forall(formalArgs, Seq(Trigger(invsOfFormalArgs)(pos, info, errT)), Implies(cond.replace((qvars map (_.localVar) zip invsOfFormalArgs).toMap), invArgsConj)(pos, info, errT))(pos, info, errT)
6666

6767
val cond1 = cond.replace((qvars map (_.localVar) zip invsOfFormalArgs).toMap)
68-
val acc1 = PredicateAccessPredicate(PredicateAccess(formalArgs map (_.localVar), pred.name)(pos, info, errT), pap.perm.replace((qvars map (_.localVar) zip invsOfFormalArgs).toMap))(pos, info, errT)
68+
val acc1 = PredicateAccessPredicate(PredicateAccess(formalArgs map (_.localVar), pred.name)(pos, info, errT), pap.permExp.map(_.replace((qvars map (_.localVar) zip invsOfFormalArgs).toMap)))(pos, info, errT)
6969
val forall1 = Forall(formalArgs, Seq(Trigger(invsOfFormalArgs)(pos, info, errT)), Implies(cond1, acc1)(pos, info, errT))(pos, info, errT)
7070

7171
val domain = Domain(domName, invs, Seq())(pos, info, errT)

src/main/scala/viper/silver/ast/utility/Nodes.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ object Nodes {
8080
case _: AbstractLocalVar => Nil
8181
case FieldAccess(rcv, _) => Seq(rcv)
8282
case PredicateAccess(params, _) => params
83-
case PredicateAccessPredicate(pred_acc, perm) => Seq(pred_acc, perm)
83+
case PredicateAccessPredicate(pred_acc, perm) => Seq(pred_acc) ++ perm.toSeq
8484
case Unfolding(acc, body) => Seq(acc, body)
8585
case Applying(wand, body) => Seq(wand, body)
8686
case Asserting(ass, body) => Seq(ass, body)

src/main/scala/viper/silver/ast/utility/Permissions.scala

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,21 @@ object Permissions {
3131
"Internal error: attempted to permission-scale expression " + e.toString +
3232
" by non-permission-typed expression " + permFactor.toString)
3333

34-
if(permFactor.isInstanceOf[FullPerm])
34+
def multiplyPermOpt(op: Option[Exp]): Option[Exp] = op match {
35+
case Some(p) => Some(PermMul(p, permFactor)(p.pos, p.info))
36+
case None => Some(permFactor)
37+
}
38+
39+
if (permFactor.isInstanceOf[FullPerm]) {
3540
e
36-
else
37-
e.transform({
38-
case fa@FieldAccessPredicate(loc,p) => FieldAccessPredicate(loc,PermMul(p,permFactor)(p.pos,p.info))(fa.pos,fa.info)
39-
case pa@PredicateAccessPredicate(loc,p) => PredicateAccessPredicate(loc,PermMul(p,permFactor)(p.pos,p.info))(pa.pos,pa.info)
41+
} else {
42+
e.transform{
43+
case fa@FieldAccessPredicate(loc, p) =>
44+
FieldAccessPredicate(loc, multiplyPermOpt(p))(fa.pos, fa.info)
45+
case pa@PredicateAccessPredicate(loc, p) =>
46+
PredicateAccessPredicate(loc, multiplyPermOpt(p))(pa.pos, pa.info)
4047
case _: MagicWand => sys.error("Cannot yet permission-scale magic wands")
41-
})}
48+
}
49+
}
50+
}
4251
}

src/main/scala/viper/silver/cfg/CfgTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ object CfgTest {
2323

2424
val parsed = parse(string, file).get
2525
val resolver = Resolver(parsed)
26-
val resolved = resolver.run.get
26+
val resolved = resolver.run(false).get
2727
val translator = Translator(resolved)
2828
val program = translator.translate.get
2929

src/main/scala/viper/silver/frontend/SilFrontEndConfig.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,13 @@ abstract class SilFrontendConfig(args: Seq[String], private var projectName: Str
139139
hidden = false
140140
)
141141

142+
val respectFunctionPrePermAmounts = opt[Boolean]("respectFunctionPrePermAmounts",
143+
descr = "Respects precise permission amounts in function preconditions instead of only checking read access.",
144+
default = Some(false),
145+
noshort = true,
146+
hidden = false
147+
)
148+
142149
val submitForEvaluation = opt[Boolean](name = "submitForEvaluation",
143150
descr = "Whether to allow storing the current program for future evaluation.",
144151
default = Some(false),

src/main/scala/viper/silver/frontend/SilFrontend.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ trait SilFrontend extends DefaultFrontend {
333333
val r = Resolver(inputPlugin)
334334
FrontendStateCache.resolver = r
335335
FrontendStateCache.pprogram = inputPlugin
336-
val analysisResult = r.run
336+
val analysisResult = r.run(if (config == null) true else !config.respectFunctionPrePermAmounts())
337337
val warnings = for (m <- FastMessaging.sortmessages(r.messages) if !m.error) yield {
338338
TypecheckerWarning(m.label, m.pos)
339339
}
@@ -374,12 +374,16 @@ trait SilFrontend extends DefaultFrontend {
374374
}
375375

376376
def doConsistencyCheck(input: Program): Result[Program] = {
377+
if (config != null) {
378+
Consistency.setFunctionPreconditionLegacyMode(config.respectFunctionPrePermAmounts())
379+
}
377380
var errors = input.checkTransitively
378381
if (backendTypeFormat.isDefined)
379382
errors = errors ++ Consistency.checkBackendTypes(input, backendTypeFormat.get)
380383
if (errors.isEmpty) {
381384
Succ(input)
382-
} else
385+
} else {
383386
Fail(errors)
387+
}
384388
}
385389
}

0 commit comments

Comments
 (0)