Skip to content

Commit 88604a2

Browse files
authored
Checking only read permissions when asserting function preconditions (#877)
1 parent efaf769 commit 88604a2

14 files changed

+126
-53
lines changed

silver

src/main/scala/extensions/ConditionalPermissionRewriter.scala

+15-10
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import scala.collection.mutable
2020
*
2121
*/
2222
class ConditionalPermissionRewriter {
23-
private def rewriter(implicit p: Program, alreadySeen: mutable.HashSet[Exp]) = ViperStrategy.Context[Condition]({
23+
private def rewriter(implicit p: Program, isFunction: Boolean, alreadySeen: mutable.HashSet[Exp]) = ViperStrategy.Context[Condition]({
2424
// Does NOT rewrite ternary expressions; those have to be transformed to implications in advance
2525
// using the ternaryRewriter below.
2626
//
@@ -32,8 +32,8 @@ class ConditionalPermissionRewriter {
3232
// Transformation causes issues if the permission involve a wildcard, so we avoid that case.
3333
// Also, we cannot push perm and forperm expressions further in, since their value may be different at different
3434
// places in the same assertion.
35-
val res = if (!acc.perm.contains[WildcardPerm] && !Expressions.containsPermissionIntrospection(cond))
36-
(conditionalize(acc, cc.c &*& cond), cc) // Won't recurse into acc's children (see recurseFunc below)
35+
val res = if ((isFunction || !acc.perm.contains[WildcardPerm]) && !Expressions.containsPermissionIntrospection(cond))
36+
(conditionalize(acc, cc.c &*& cond, isFunction), cc) // Won't recurse into acc's children (see recurseFunc below)
3737
else
3838
(Implies(And(cc.c.exp, cond)(), acc)(i.pos, i.info, i.errT), cc)
3939
alreadySeen.add(res._1)
@@ -61,8 +61,8 @@ class ConditionalPermissionRewriter {
6161
case (acc: AccessPredicate, cc) if cc.c.optExp.nonEmpty =>
6262
// Found an accessibility predicate nested under some conditionals
6363
// Wildcards may cause issues, see above.
64-
val res = if (!acc.perm.contains[WildcardPerm])
65-
(conditionalize(acc, cc.c), cc) // Won't recurse into acc's children
64+
val res = if (isFunction || !acc.perm.contains[WildcardPerm])
65+
(conditionalize(acc, cc.c, isFunction), cc) // Won't recurse into acc's children
6666
else
6767
(Implies(cc.c.exp, acc)(acc.pos, acc.info, acc.errT), cc)
6868
alreadySeen.add(res._1)
@@ -103,7 +103,11 @@ class ConditionalPermissionRewriter {
103103
*/
104104
def rewrite(root: Program): Program = {
105105
val noTernaryProgram: Program = ternaryRewriter.execute(root)
106-
val res: Program = rewriter(root, new mutable.HashSet[Exp]()).execute(noTernaryProgram)
106+
val functionRewriter = rewriter(root, true, new mutable.HashSet[Exp]())
107+
val nonFunctionRewriter = rewriter(root, false, new mutable.HashSet[Exp]())
108+
val res = noTernaryProgram.copy(functions = noTernaryProgram.functions.map(functionRewriter.execute[Function](_)),
109+
predicates = noTernaryProgram.predicates.map(nonFunctionRewriter.execute[Predicate](_)),
110+
methods = noTernaryProgram.methods.map(nonFunctionRewriter.execute[Method](_)))(noTernaryProgram.pos, noTernaryProgram.info, noTernaryProgram.errT)
107111
res
108112
}
109113

@@ -114,26 +118,27 @@ class ConditionalPermissionRewriter {
114118

115119
/** Makes `acc`'s permissions conditional w.r.t. `cond`.
116120
*/
117-
private def conditionalize(acc: AccessPredicate, cond: Condition)(implicit p: Program): Exp = {
121+
private def conditionalize(acc: AccessPredicate, cond: Condition, isFunction: Boolean)(implicit p: Program): Exp = {
118122
// We have to be careful not to introduce well-definedness issues when conditionalizing.
119123
// For example, if we transform
120124
// i >= 0 && i < |s| ==> acc(s[i].f)
121125
// to
122126
// acc(s[i].f, i >= 0 && i < |s| ? write : none)
123127
// then backends may complain that s[i].f is not well-defined. Thus, we only perform the
124128
// transformation if receiver/argument expressions are always well-defined.
129+
val defaultPerm = if (isFunction) WildcardPerm()() else FullPerm()()
125130
acc match {
126131
case FieldAccessPredicate(loc, perm) =>
127132
if (Expressions.proofObligations(loc.rcv)(p).isEmpty) {
128-
FieldAccessPredicate(loc, makeCondExp(cond.exp, perm))(acc.pos, acc.info, acc.errT)
133+
FieldAccessPredicate(loc, Some(makeCondExp(cond.exp, perm.getOrElse(defaultPerm))))(acc.pos, acc.info, acc.errT)
129134
} else {
130135
// Hack: use a conditional null as the receiver, that's always well-defined.
131136
val fieldAccess = loc.copy(rcv = makeCondExp(cond.exp, loc.rcv, NullLit()()))(loc.pos, loc.info, loc.errT)
132-
FieldAccessPredicate(fieldAccess, makeCondExp(cond.exp, perm))(acc.pos, acc.info, acc.errT)
137+
FieldAccessPredicate(fieldAccess, Some(makeCondExp(cond.exp, perm.getOrElse(defaultPerm))))(acc.pos, acc.info, acc.errT)
133138
}
134139
case PredicateAccessPredicate(loc, perm) =>
135140
if (!loc.args.exists(a => Expressions.proofObligations(a)(p).nonEmpty))
136-
PredicateAccessPredicate(loc, makeCondExp(cond.exp, perm))(acc.pos, acc.info, acc.errT)
141+
PredicateAccessPredicate(loc, Some(makeCondExp(cond.exp, perm.getOrElse(defaultPerm))))(acc.pos, acc.info, acc.errT)
137142
else
138143
Implies(cond.exp, acc)(acc.pos, acc.info, acc.errT)
139144
case wand: MagicWand =>

src/main/scala/resources/ResourceDescriptions.scala

+22-8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
package viper.silicon.resources
88

9+
import scala.annotation.unused
10+
911
/**
1012
* A resource description contains the assumptions that are added at several points during verificaton.
1113
* <ul>
@@ -14,16 +16,18 @@ package viper.silicon.resources
1416
* <li>Static properties are also assumed when a new chunk is added to the heap. They descripe properties of the whole heap
1517
* and are not allowed to contain the <code>This()</code> literal.</li>
1618
* <li>Delayed properties are static properties that are only assumed after a state consolidation.</li>
19+
* <li>The flag withPermUpperBounds determines if properties that set upper bounds for permission amounts should
20+
* be included.</li>
1721
* </ul>
1822
*/
1923
trait ResourceDescription {
20-
val instanceProperties: Seq[Property]
21-
val delayedProperties: Seq[Property]
24+
def instanceProperties(withPermUpperBounds: Boolean): Seq[Property]
25+
def delayedProperties(withPermUpperBounds: Boolean): Seq[Property]
2226
}
2327

2428
abstract class BasicDescription extends ResourceDescription {
25-
override val instanceProperties = Seq(permAtLeastZero)
26-
override val delayedProperties = Seq(valNeqImpliesLocNeq)
29+
override def instanceProperties(@unused withPermUpperBounds: Boolean) = Seq(permAtLeastZero)
30+
override def delayedProperties(@unused withPermUpperBounds: Boolean) = Seq(valNeqImpliesLocNeq)
2731

2832
def permAtLeastZero: Property = {
2933
val description = "Permissions are non-negative"
@@ -47,8 +51,18 @@ class PredicateDescription extends BasicDescription {
4751
}
4852

4953
class FieldDescription extends BasicDescription {
50-
override val instanceProperties = Seq(permAtLeastZero, permAtMostOne, permImpliesNonNull)
51-
override val delayedProperties = Seq(permUpperBoundDiseq, valNeqImpliesLocNeq)
54+
override def instanceProperties(withPermUpperBounds: Boolean) = {
55+
if (withPermUpperBounds)
56+
Seq(permAtLeastZero, permAtMostOne, permImpliesNonNull)
57+
else
58+
Seq(permAtLeastZero, permImpliesNonNull)
59+
}
60+
override def delayedProperties(withPermUpperBounds: Boolean) = {
61+
if (withPermUpperBounds)
62+
Seq(permUpperBoundDiseq, valNeqImpliesLocNeq)
63+
else
64+
Seq(valNeqImpliesLocNeq)
65+
}
5266

5367
def permAtMostOne: Property = {
5468
val description = "Field permissions are at most one"
@@ -75,8 +89,8 @@ class FieldDescription extends BasicDescription {
7589
}
7690

7791
class MagicWandDescription extends ResourceDescription {
78-
override val instanceProperties = Seq(permAtLeastZero)
79-
override val delayedProperties = Seq[Property]()
92+
override def instanceProperties(withPermUpperBounds: Boolean) = Seq(permAtLeastZero)
93+
override def delayedProperties(withPermUpperBounds: Boolean) = Seq[Property]()
8094

8195
def permAtLeastZero: Property = {
8296
val description = "Permissons are non-negative"

src/main/scala/rules/ChunkSupporter.scala

+8-2
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,19 @@ object chunkSupporter extends ChunkSupportRules {
163163
def assumeProperties(chunk: NonQuantifiedChunk, heap: Heap): Unit = {
164164
val interpreter = new NonQuantifiedPropertyInterpreter(heap.values, v)
165165
val resource = Resources.resourceDescriptions(chunk.resourceID)
166-
val pathCond = interpreter.buildPathConditionsForChunk(chunk, resource.instanceProperties)
166+
val pathCond = interpreter.buildPathConditionsForChunk(chunk, resource.instanceProperties(s.mayAssumeUpperBounds))
167167
pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2))))
168168
}
169169

170170
findChunk[NonQuantifiedChunk](h.values, id, args, v) match {
171171
case Some(ch) =>
172-
if (consumeExact) {
172+
if (s.assertReadAccessOnly) {
173+
if (v.decider.check(Implies(IsPositive(perms), IsPositive(ch.perm)), Verifier.config.assertTimeout.getOrElse(0))) {
174+
(Complete(), s, h, Some(ch))
175+
} else {
176+
(Incomplete(perms, permsExp), s, h, None)
177+
}
178+
} else if (consumeExact) {
173179
val toTake = PermMin(ch.perm, perms)
174180
val toTakeExp = permsExp.map(pe => buildMinExp(Seq(ch.permExp.get, pe), ast.Perm))
175181
val newPermExp = permsExp.map(pe => ast.PermSub(ch.permExp.get, toTakeExp.get)(pe.pos, pe.info, pe.errT))

src/main/scala/rules/Evaluator.scala

+7-5
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,9 @@ object evaluator extends EvaluationRules {
924924
* incomplete).
925925
*/
926926
smDomainNeeded = true,
927-
moreJoins = JoinMode.Off)
927+
moreJoins = JoinMode.Off,
928+
assertReadAccessOnly = if (Verifier.config.respectFunctionPrePermAmounts())
929+
s2.assertReadAccessOnly /* should currently always be false */ else true)
928930
consumes(s3, pres, true, _ => pvePre, v2)((s4, snap, v3) => {
929931
val snap1 = snap.get.convert(sorts.Snap)
930932
val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs)
@@ -950,8 +952,8 @@ object evaluator extends EvaluationRules {
950952
recordVisited = s2.recordVisited,
951953
functionRecorder = fr5,
952954
smDomainNeeded = s2.smDomainNeeded,
953-
hackIssue387DisablePermissionConsumption = s.hackIssue387DisablePermissionConsumption,
954-
moreJoins = s2.moreJoins)
955+
moreJoins = s2.moreJoins,
956+
assertReadAccessOnly = s2.assertReadAccessOnly)
955957
val funcAppNew = eArgsNew.map(args => ast.FuncApp(funcName, args)(fapp.pos, fapp.info, fapp.typ, fapp.errT))
956958
QB(s5, (tFApp, funcAppNew), v3)})
957959
/* TODO: The join-function is heap-independent, and it is not obvious how a
@@ -968,7 +970,7 @@ object evaluator extends EvaluationRules {
968970
if (s.cycles(predicate) < Verifier.config.recursivePredicateUnfoldings()) {
969971
v.decider.startDebugSubExp()
970972
evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) =>
971-
eval(s1, ePerm, pve, v1)((s2, tPerm, ePermNew, v2) =>
973+
eval(s1, ePerm.getOrElse(ast.FullPerm()()), pve, v1)((s2, tPerm, ePermNew, v2) =>
972974
v2.decider.assert(IsPositive(tPerm)) { // TODO: Replace with permissionSupporter.assertNotNegative
973975
case true =>
974976
joiner.join[(Term, Option[ast.Exp]), (Term, Option[ast.Exp])](s2, v2)((s3, v3, QB) => {
@@ -1021,7 +1023,7 @@ object evaluator extends EvaluationRules {
10211023
Q(s12, r12._1, r12._2, v7)})
10221024
case false =>
10231025
v2.decider.finishDebugSubExp(s"unfolded(${predicate.name})")
1024-
createFailure(pve dueTo NonPositivePermission(ePerm), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT)))}))
1026+
createFailure(pve dueTo NonPositivePermission(ePerm.get), v2, s2, IsPositive(tPerm), ePermNew.map(p => ast.PermGtCmp(p, ast.NoPerm()())(p.pos, p.info, p.errT)))}))
10251027
} else {
10261028
val unknownValue = v.decider.appliedFresh("recunf", v.symbolConverter.toSort(eIn.typ), s.relevantQuantifiedVariables.map(_._1))
10271029
Q(s, unknownValue, Option.when(withExp)(ast.LocalVarWithVersion("unknownValue", eIn.typ)(eIn.pos, eIn.info, eIn.errT)), v)

src/main/scala/rules/Executor.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -601,8 +601,9 @@ object executor extends ExecutionRules {
601601
v3.symbExLog.closeScope(sepIdentifier)
602602
Q(s6, v3)})})})
603603

604-
case fold @ ast.Fold(ast.PredicateAccessPredicate(predAcc @ ast.PredicateAccess(eArgs, predicateName), ePerm)) =>
604+
case fold @ ast.Fold(pap @ ast.PredicateAccessPredicate(predAcc @ ast.PredicateAccess(eArgs, predicateName), _)) =>
605605
v.decider.startDebugSubExp()
606+
val ePerm = pap.perm
606607
val predicate = s.program.findPredicate(predicateName)
607608
val pve = FoldFailed(fold)
608609
evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) =>
@@ -615,8 +616,9 @@ object executor extends ExecutionRules {
615616
}
616617
)})))
617618

618-
case unfold @ ast.Unfold(ast.PredicateAccessPredicate(pa @ ast.PredicateAccess(eArgs, predicateName), ePerm)) =>
619+
case unfold @ ast.Unfold(pap @ ast.PredicateAccessPredicate(pa @ ast.PredicateAccess(eArgs, predicateName), _)) =>
619620
v.decider.startDebugSubExp()
621+
val ePerm = pap.perm
620622
val predicate = s.program.findPredicate(predicateName)
621623
val pve = UnfoldFailed(unfold)
622624
evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) =>

src/main/scala/rules/MoreCompleteExhaleSupporter.scala

+8-7
Original file line numberDiff line numberDiff line change
@@ -233,16 +233,16 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules {
233233
(Q: (State, Heap, Option[Term], Verifier) => VerificationResult)
234234
: VerificationResult = {
235235

236-
if (!s.hackIssue387DisablePermissionConsumption)
236+
if (!s.assertReadAccessOnly)
237237
actualConsumeComplete(s, h, resource, args, argsExp, perms, permsExp, returnSnap, ve, v)(Q)
238-
else {
239-
summariseHeapAndAssertReadAccess(s, h, resource, args, argsExp, returnSnap, ve, v)(Q)
240-
}
238+
else
239+
summariseHeapAndAssertReadAccess(s, h, resource, perms, args, argsExp, returnSnap, ve, v)(Q)
241240
}
242241

243242
private def summariseHeapAndAssertReadAccess(s: State,
244243
h: Heap,
245244
resource: ast.Resource,
245+
perm: Term,
246246
args: Seq[Term],
247247
argsExp: Option[Seq[ast.Exp]],
248248
returnSnap: Boolean,
@@ -253,17 +253,18 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules {
253253

254254
val id = ChunkIdentifier(resource, s.program)
255255
val relevantChunks = findChunksWithID[NonQuantifiedChunk](h.values, id).toSeq
256+
256257
if (returnSnap) {
257258
summarise(s, relevantChunks, resource, args, argsExp, None, v)((s1, snap, permSum, permSumExp, v1) =>
258-
v.decider.assert(IsPositive(permSum)) {
259+
v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum))) {
259260
case true =>
260261
Q(s1, h, Some(snap), v1)
261262
case false =>
262263
createFailure(ve, v, s1, IsPositive(permSum), permSumExp.map(IsPositive(_)()))
263264
})
264265
} else {
265266
val (s1, permSum, permSumExp) = permSummariseOnly(s, relevantChunks, resource, args, argsExp)
266-
v.decider.assert(IsPositive(permSum)) {
267+
v.decider.assert(Implies(IsPositive(perm), IsPositive(permSum))) {
267268
case true =>
268269
Q(s1, h, None, v)
269270
case false =>
@@ -374,7 +375,7 @@ object moreCompleteExhaleSupporter extends SymbolicExecutionRules {
374375
val interpreter = new NonQuantifiedPropertyInterpreter(allChunks, v)
375376
newChunks foreach { ch =>
376377
val resource = Resources.resourceDescriptions(ch.resourceID)
377-
val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties)
378+
val pathCond = interpreter.buildPathConditionsForChunk(ch, resource.instanceProperties(s.mayAssumeUpperBounds))
378379
pathCond.foreach(p => v.decider.assume(p._1, Option.when(withExp)(DebugExp.createInstance(p._2, p._2))))
379380
}
380381
val newHeap = Heap(allChunks)

src/main/scala/rules/Producer.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,8 @@ object producer extends ProductionRules {
317317
letSupporter.handle[ast.Exp](s, let, pve, v)((s1, g1, body, v1) =>
318318
produceR(s1.copy(g = s1.g + g1), sf, body, pve, v1)(Q))
319319

320-
case accPred@ast.FieldAccessPredicate(ast.FieldAccess(eRcvr, field), perm) =>
320+
case accPred@ast.FieldAccessPredicate(ast.FieldAccess(eRcvr, field), _) =>
321+
val perm = accPred.perm
321322
eval(s, eRcvr, pve, v)((s1, tRcvr, eRcvrNew, v1) =>
322323
eval(s1, perm, pve, v1)((s2, tPerm, ePermNew, v2) =>
323324
permissionSupporter.assertNotNegative(s2, tPerm, perm, ePermNew, pve, v2)((s3, v3) => {
@@ -341,8 +342,9 @@ object producer extends ProductionRules {
341342
Q(s6, v4)
342343
})}})))
343344

344-
case ast.PredicateAccessPredicate(ast.PredicateAccess(eArgs, predicateName), perm) =>
345+
case accPred @ ast.PredicateAccessPredicate(ast.PredicateAccess(eArgs, predicateName), _) =>
345346
val predicate = s.program.findPredicate(predicateName)
347+
val perm = accPred.perm
346348
evals(s, eArgs, _ => pve, v)((s1, tArgs, eArgsNew, v1) =>
347349
eval(s1, perm, pve, v1)((s1a, tPerm, ePermNew, v1a) =>
348350
permissionSupporter.assertNotNegative(s1a, tPerm, perm, ePermNew, pve, v1a)((s2, v2) => {

0 commit comments

Comments
 (0)