Skip to content

Commit f94e56a

Browse files
authored
Merge pull request #650 from viperproject/meilers_function_preconditions
Sound trigger-independent function encoding
2 parents c2f00b6 + e7697eb commit f94e56a

8 files changed

Lines changed: 136 additions & 19 deletions

File tree

src/main/scala/rules/Consumer.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,14 @@ object consumer extends ConsumptionRules {
474474

475475
executionFlowController.tryOrFail0(s1, v)((s2, v1, QS) => {
476476
eval(s2, e, pve, v1)((s3, t, v2) => {
477-
v2.decider.assert(t) {
477+
val termToAssert = t match {
478+
case Quantification(q, vars, body, trgs, name, isGlob) =>
479+
val transformed = FunctionPreconditionTransformer.transform(body, s3.program)
480+
v2.decider.assume(Quantification(q, vars, transformed, trgs, name+"_precondition", isGlob))
481+
Quantification(q, vars, Implies(transformed, body), trgs, name, isGlob)
482+
case _ => t
483+
}
484+
v2.decider.assert(termToAssert) {
478485
case true =>
479486
v2.decider.assume(t)
480487
QS(s3, v2)

src/main/scala/rules/Evaluator.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,10 @@ object evaluator extends EvaluationRules {
728728
smDomainNeeded = true)
729729
consumes(s3, pres, _ => pvePre, v2)((s4, snap, v3) => {
730730
val snap1 = snap.convert(sorts.Snap)
731+
if (func.pres.nonEmpty) {
732+
val preFApp = App(functionSupporter.preconditionVersion(v3.symbolConverter.toFunction(func)), snap1 :: tArgs)
733+
v3.decider.assume(preFApp)
734+
}
731735
val tFApp = App(v3.symbolConverter.toFunction(func), snap1 :: tArgs)
732736
val fr5 =
733737
s4.functionRecorder.changeDepthBy(-1)

src/main/scala/rules/FunctionSupporter.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,9 @@ object functionSupporter {
1919
val id = function.id.withSuffix("%", "stateless")
2020
Fun(id, function.argSorts.tail, terms.sorts.Bool)
2121
}
22+
23+
def preconditionVersion(function: HeapDepFun): HeapDepFun = {
24+
val id = function.id.withSuffix("%", "precondition")
25+
HeapDepFun(id, function.argSorts, terms.sorts.Bool)
26+
}
2227
}

src/main/scala/rules/Joiner.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import viper.silicon.interfaces.{Success, VerificationResult}
1111
import viper.silicon.logger.SymbExLogger
1212
import viper.silicon.logger.records.structural.JoiningRecord
1313
import viper.silicon.state.State
14+
import viper.silicon.state.terms.{And, Or, Term}
1415
import viper.silicon.verifier.Verifier
1516

1617
case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathConditions)
@@ -64,11 +65,16 @@ object joiner extends JoiningRules {
6465
} else {
6566
val (sJoined, dataJoined) = merge(entries)
6667

68+
var feasibleBranches: List[Term] = Nil
69+
6770
entries foreach (entry => {
6871
val pcs = entry.pathConditions.conditionalized
6972
v.decider.prover.comment("Joined path conditions")
7073
v.decider.assume(pcs)
74+
feasibleBranches = And(entry.pathConditions.branchConditions) :: feasibleBranches
7175
})
76+
// Assume we are in a feasible branch
77+
v.decider.assume(Or(feasibleBranches))
7278

7379
Q(sJoined, dataJoined, v)
7480
}

src/main/scala/rules/QuantifiedChunkSupport.scala

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules {
142142
perms: Term,
143143
arguments: Seq[Term],
144144
triggers: Seq[Trigger],
145-
qidPrefix: String)
145+
qidPrefix: String,
146+
program: ast.Program)
146147
: Quantification
147148

148149
def createSingletonQuantifiedChunk(codomainQVars: Seq[Var],
@@ -847,8 +848,10 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport {
847848
vars = effectiveTriggersQVars,
848849
triggers = effectiveTriggers)))
849850

851+
val nonNegImplication = Implies(tCond, perms.IsNonNegative(tPerm))
852+
val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil)
850853
// TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative
851-
v.decider.assert(Forall(qvars, Implies(tCond, perms.IsNonNegative(tPerm)), Nil)) {
854+
v.decider.assert(nonNegTerm) {
852855
case true =>
853856

854857
/* TODO: Can we omit/simplify the injectivity check in certain situations? */
@@ -861,18 +864,21 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport {
861864
perms = tPerm,
862865
arguments = tArgs,
863866
triggers = Nil,
864-
qidPrefix = qid)
867+
qidPrefix = qid,
868+
program = s.program)
865869
} else {
866870
True()
867871
}
868872
v.decider.prover.comment("Check receiver injectivity")
873+
v.decider.assume(FunctionPreconditionTransformer.transform(receiverInjectivityCheck, s.program))
869874
v.decider.assert(receiverInjectivityCheck) {
870875
case true =>
871876
val ax = inverseFunctions.axiomInversesOfInvertibles
872877
val inv = inverseFunctions.copy(axiomInversesOfInvertibles = Forall(ax.vars, ax.body, effectiveTriggers))
873878

874879
v.decider.prover.comment("Definitional axioms for inverse functions")
875880
val definitionalAxiomMark = v.decider.setPathConditionMark()
881+
v.decider.assume(inv.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)))
876882
v.decider.assume(inv.definitionalAxioms)
877883
val conservedPcs =
878884
if (s.recordPcs) (s.conservedPcs.head :+ v.decider.pcs.after(definitionalAxiomMark)) +: s.conservedPcs.tail
@@ -1046,8 +1052,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport {
10461052
/* Explicit triggers were provided. */
10471053
v.decider.assume(auxNonGlobals)
10481054
}
1055+
1056+
val nonNegImplication = Implies(tCond, perms.IsNonNegative(tPerm))
1057+
val nonNegTerm = Forall(qvars, Implies(FunctionPreconditionTransformer.transform(nonNegImplication, s.program), nonNegImplication), Nil)
10491058
// TODO: Replace by QP-analogue of permissionSupporter.assertNotNegative
1050-
v.decider.assert(Forall(qvars, Implies(tCond, perms.IsNonNegative(tPerm)), Nil)) {
1059+
v.decider.assert(nonNegTerm) {
10511060
case true =>
10521061
val hints = quantifiedChunkSupporter.extractHints(Some(tCond), tArgs)
10531062
val chunkOrderHeuristics =
@@ -1077,7 +1086,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport {
10771086
perms = tPerm,
10781087
arguments = tArgs,
10791088
triggers = Nil,
1080-
qidPrefix = qid)
1089+
qidPrefix = qid,
1090+
program = s.program)
10811091
v.decider.prover.comment("Check receiver injectivity")
10821092
v.decider.assert(receiverInjectivityCheck) {
10831093
case true =>
@@ -1086,6 +1096,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport {
10861096
val lossOfInvOfLoc = loss.replace(qvarsToInvOfLoc)
10871097

10881098
v.decider.prover.comment("Definitional axioms for inverse functions")
1099+
v.decider.assume(inverseFunctions.definitionalAxioms.map(a => FunctionPreconditionTransformer.transform(a, s.program)))
10891100
v.decider.assume(inverseFunctions.definitionalAxioms)
10901101

10911102
if (s.heapDependentTriggers.contains(resourceIdentifier)){
@@ -1510,7 +1521,8 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport {
15101521
perms: Term,
15111522
arguments: Seq[Term],
15121523
triggers: Seq[Trigger],
1513-
qidPrefix: String)
1524+
qidPrefix: String,
1525+
program: ast.Program)
15141526
: Quantification = {
15151527

15161528
val qvars1 = qvars.map(x => x.copy(id = x.id.rename(id => s"${id}1")))
@@ -1546,9 +1558,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport {
15461558
argsEqual),
15471559
varsEqual)
15481560

1561+
val functionPres = FunctionPreconditionTransformer.transform(implies, program)
1562+
15491563
Forall(
15501564
qvars1 ++ qvars2,
1551-
implies,
1565+
Implies(functionPres, implies),
15521566
triggers,
15531567
s"$qidPrefix-rcvrInj")
15541568
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package viper.silicon.state
2+
3+
import viper.silicon.rules.functionSupporter
4+
import viper.silicon.state.terms.{And, App, HeapDepFun, Implies, Ite, Let, Literal, Not, Or, Quantification, Term, True}
5+
import viper.silver.ast
6+
7+
8+
/**
9+
* Given a term t, returns a new term tr(t) that expresses that all applications of heap-dependent functions are
10+
* well-defined (in particular, that the abstract functions that represent their preconditions hold).
11+
* In general, after checking that t is well-defined, it is sound to assume tr(t).
12+
*
13+
* I.e., given
14+
* fun(e1, ..., en)
15+
* where fun is a heap-dependent function with a precondition, it returns
16+
* tr(e1) && ... && tr(en) && fun%precondition(e1, ..., en)
17+
* where the tr(ei) terms express that all function applications in the argument terms are well-defined.
18+
*
19+
* For &&, ||, and ==>, the transformation takes into account short-circuit semantics:
20+
* If A && B is well-defined, that means that A is well-defined, and if A is true, then B is well-defined as well.
21+
* Thus, the transformation of A && B is tr(A) && (A ==> tr(B)).
22+
*/
23+
object FunctionPreconditionTransformer {
24+
def transform(t: Term, p: ast.Program): Term = {
25+
val res = t match {
26+
case _:Literal => True()
27+
case And(ts) => And(transform(ts.head, p), Implies(ts.head, transform(And(ts.tail), p)))
28+
case Or(ts) => And(transform(ts.head, p), Implies(Not(ts.head), transform(Or(ts.tail), p)))
29+
case Implies(t0, t1) => And(transform(t0, p), Implies(t0, transform(t1, p)))
30+
case Ite(t, t1, t2) => And(transform(t, p), Ite(t, transform(t1, p), transform(t2, p)))
31+
case Let(bindings, body) => Let(bindings, transform(body, p))
32+
case Quantification(q, vars, body, triggers, name, isGlobal) =>
33+
val tBody = transform(body, p)
34+
if (tBody == True())
35+
tBody
36+
else
37+
Quantification(q, vars, tBody, triggers, name, isGlobal)
38+
case App(hdf@HeapDepFun(id, _, _), args) =>
39+
val funcName = id match {
40+
case SuffixedIdentifier(prefix, _, _) => prefix.name
41+
case _ => id.name
42+
}
43+
if (p.findFunction(funcName).pres.nonEmpty)
44+
And(args.map(transform(_, p)) :+ App(functionSupporter.preconditionVersion(hdf), args))
45+
else
46+
And(args.map(transform(_, p)))
47+
case other => And(other.subterms.map(transform(_, p)))
48+
}
49+
res
50+
}
51+
52+
}

src/main/scala/supporters/functions/FunctionData.scala

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package viper.silicon.supporters.functions
88

99
import scala.annotation.unused
1010
import com.typesafe.scalalogging.LazyLogging
11+
import viper.silicon.state.FunctionPreconditionTransformer
1112
import viper.silver.ast
1213
import viper.silver.ast.utility.Functions
1314
import viper.silicon.common.collections.immutable.InsertionOrderedSet
@@ -51,6 +52,7 @@ class FunctionData(val programFunction: ast.Function,
5152
val function: HeapDepFun = symbolConverter.toFunction(programFunction)
5253
val limitedFunction = functionSupporter.limitedVersion(function)
5354
val statelessFunction = functionSupporter.statelessVersion(function)
55+
val preconditionFunction = functionSupporter.preconditionVersion(function)
5456

5557
val formalArgs: Map[ast.AbstractLocalVar, Var] = toMap(
5658
for (arg <- programFunction.formalArgs;
@@ -67,6 +69,7 @@ class FunctionData(val programFunction: ast.Function,
6769
val functionApplication = App(function, `?s` +: formalArgs.values.toSeq)
6870
val limitedFunctionApplication = App(limitedFunction, `?s` +: formalArgs.values.toSeq)
6971
val triggerFunctionApplication = App(statelessFunction, formalArgs.values.toSeq)
72+
val preconditionFunctionApplication = App(preconditionFunction, `?s` +: formalArgs.values.toSeq)
7073

7174
val limitedAxiom =
7275
Forall(arguments,
@@ -175,15 +178,21 @@ class FunctionData(val programFunction: ast.Function,
175178
expressionTranslator.translatePrecondition(program, programFunction.pres, this)
176179
}
177180

181+
lazy val translatedPosts = {
182+
assert(phase == 1, s"Postcondition axiom must be generated in phase 1, current phase is $phase")
183+
if (programFunction.posts.nonEmpty) {
184+
expressionTranslator.translatePostcondition(program, programFunction.posts, this)
185+
}else{
186+
Seq()
187+
}
188+
}
189+
178190
lazy val postAxiom: Option[Term] = {
179191
assert(phase == 1, s"Postcondition axiom must be generated in phase 1, current phase is $phase")
180192

181193
if (programFunction.posts.nonEmpty) {
182-
val posts =
183-
expressionTranslator.translatePostcondition(program, programFunction.posts, this)
184-
185-
val pre = And(translatedPres)
186-
val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(posts))))
194+
val pre = if (programFunction.pres.nonEmpty) preconditionFunctionApplication else True()
195+
val innermostBody = And(generateNestedDefinitionalAxioms ++ List(Implies(pre, And(translatedPosts))))
187196
val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication)
188197
val body = Let(toMap(bodyBindings), innermostBody)
189198

@@ -237,13 +246,17 @@ class FunctionData(val programFunction: ast.Function,
237246
}))
238247
}
239248

240-
lazy val definitionalAxiom: Option[Term] = {
249+
lazy val optBody: Option[Term] = {
241250
assert(phase == 2, s"Definitional axiom must be generated in phase 2, current phase is $phase")
242251

243-
val optBody = expressionTranslator.translate(program, programFunction, this)
252+
expressionTranslator.translate(program, programFunction, this)
253+
}
254+
255+
lazy val definitionalAxiom: Option[Term] = {
256+
assert(phase == 2, s"Definitional axiom must be generated in phase 2, current phase is $phase")
244257

245258
optBody.map(translatedBody => {
246-
val pre = And(translatedPres)
259+
val pre = if (programFunction.pres.nonEmpty) preconditionFunctionApplication else True()
247260
val nestedDefinitionalAxioms = generateNestedDefinitionalAxioms
248261
val body = And(nestedDefinitionalAxioms ++ List(Implies(pre, And(functionApplication === translatedBody))))
249262
val allTriggers = (
@@ -252,4 +265,18 @@ class FunctionData(val programFunction: ast.Function,
252265

253266
Forall(arguments, body, allTriggers)})
254267
}
268+
269+
lazy val preconditionPropagationAxiom: Seq[Term] = {
270+
val pre = if (programFunction.pres.nonEmpty) preconditionFunctionApplication else True()
271+
val bodyPreconditions = if (programFunction.body.isDefined) optBody.map(translatedBody => {
272+
val body = Implies(pre, FunctionPreconditionTransformer.transform(translatedBody, program))
273+
Forall(arguments, body, Seq(Trigger(functionApplication)))
274+
}) else None
275+
val postPreconditions = if (programFunction.posts.nonEmpty) {
276+
val bodyBindings: Map[Var, Term] = Map(formalResult -> limitedFunctionApplication)
277+
val bodies = translatedPosts.map(tPost => Let(bodyBindings, Implies(pre, FunctionPreconditionTransformer.transform(tPost, program))))
278+
bodies.map(b => Forall(arguments, b, Seq(Trigger(limitedFunctionApplication))))
279+
} else Seq()
280+
bodyPreconditions.toSeq ++ postPreconditions
281+
}
255282
}

src/main/scala/supporters/functions/FunctionVerificationUnit.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver
111111
private def generateFunctionSymbolsAfterAnalysis: Iterable[Either[String, Decl]] = (
112112
Seq(Left("Declaring symbols related to program functions (from program analysis)"))
113113
++ functionData.values.flatMap(data =>
114-
Seq(data.function, data.limitedFunction, data.statelessFunction).map(FunctionDecl)
114+
Seq(data.function, data.limitedFunction, data.statelessFunction, data.preconditionFunction).map(FunctionDecl)
115115
).map(Right(_))
116116
)
117117

@@ -175,9 +175,10 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver
175175
emitAndRecordFunctionAxioms(data.postAxiom.toSeq: _*)
176176
this.postConditionAxioms = this.postConditionAxioms ++ data.postAxiom.toSeq
177177

178-
if (function.body.isEmpty)
178+
if (function.body.isEmpty) {
179+
emitAndRecordFunctionAxioms(data.preconditionPropagationAxiom.toSeq: _*)
179180
result1
180-
else {
181+
} else {
181182
/* Phase 2: Verify the function's postcondition */
182183
val result2 = verify(function, phase1data)
183184

@@ -186,6 +187,7 @@ trait DefaultFunctionVerificationUnitProvider extends VerifierComponent { v: Ver
186187
data.verificationFailures = data.verificationFailures :+ fatalResult
187188
case _ =>
188189
emitAndRecordFunctionAxioms(data.definitionalAxiom.toSeq: _*)
190+
emitAndRecordFunctionAxioms(data.preconditionPropagationAxiom.toSeq: _*)
189191
}
190192

191193
result1 && result2

0 commit comments

Comments
 (0)