Skip to content

Commit e73c870

Browse files
authored
Fixing potential unsoundness in QP state consolidation (#910)
1 parent e8793a1 commit e73c870

3 files changed

Lines changed: 16 additions & 12 deletions

File tree

src/main/scala/rules/QuantifiedChunkSupport.scala

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -232,28 +232,31 @@ trait QuantifiedChunkSupport extends SymbolicExecutionRules {
232232
*
233233
* @param fr The functionRecorder to use when new snapshot maps are generated.
234234
* @param field The name of the field.
235+
* @param fqvars Arguments of the current function if we are currently verifying one, i.e., functionRecorderQVars.
235236
* @param t1 The first chunk's snapshot map.
236237
* @param t2 The second chunk's snapshot map.
237238
* @param p1 The first chunk's permission amount, should be constrained by the domain.
238239
* @param p2 The second chunk's permission amount, should be constrained by the domain.
239240
* @param v The verifier to use.
240241
* @return A tuple (fr, sm, def) of functionRecorder, a snapshot map sm and a term def constraining sm.
241242
*/
242-
def combineFieldSnapshotMaps(fr: FunctionRecorder, field: String, t1: Term, t2: Term, p1: Term, p2: Term, v: Verifier): (FunctionRecorder, Term, Term)
243+
def combineFieldSnapshotMaps(fr: FunctionRecorder, field: String, fqvars: Seq[Var],t1: Term, t2: Term, p1: Term, p2: Term, v: Verifier): (FunctionRecorder, Term, Term)
243244

244245
/** Merge the snapshots of two quantified heap chunks that denote the same predicate
245246
*
246247
* @param fr The functionRecorder to use when new snapshot maps are generated.
247248
* @param predicate The name of the predicate.
248249
* @param qVars The variables over which p1 and p2 are defined
250+
* @param fqvars Arguments of the current function if we are currently verifying one, i.e., functionRecorderQVars.
249251
* @param t1 The first chunk's snapshot map.
250252
* @param t2 The second chunk's snapshot map.
251253
* @param p1 The first chunk's permission amount, should be constrained by the domain.
252254
* @param p2 The second chunk's permission amount, should be constrained by the domain.
253255
* @param v The verifier to use.
254256
* @return A tuple (fr, sm, def) of functionRecorder, a snapshot map sm and a term def constraining sm.
255257
*/
256-
def combinePredicateSnapshotMaps(fr: FunctionRecorder, predicate: String, qVars: Seq[Var], t1: Term, t2: Term, p1: Term, p2: Term, v: Verifier): (FunctionRecorder, Term, Term)
258+
def combinePredicateSnapshotMaps(fr: FunctionRecorder, predicate: String, qVars: Seq[Var], fqvars: Seq[Var],
259+
t1: Term, t2: Term, p1: Term, p2: Term, v: Verifier): (FunctionRecorder, Term, Term)
257260
}
258261

259262
object quantifiedChunkSupporter extends QuantifiedChunkSupport {
@@ -2094,7 +2097,7 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport {
20942097
}
20952098

20962099
// Based on StateConsolidator#combineSnapshots
2097-
override def combineFieldSnapshotMaps(fr: FunctionRecorder, field: String, t1: Term, t2: Term, p1: Term, p2: Term, v: Verifier): (FunctionRecorder, Term, Term) = {
2100+
override def combineFieldSnapshotMaps(fr: FunctionRecorder, field: String, fqvars: Seq[Var], t1: Term, t2: Term, p1: Term, p2: Term, v: Verifier): (FunctionRecorder, Term, Term) = {
20982101
val lookupT1 = Lookup(field, t1, `?r`)
20992102
val lookupT2 = Lookup(field, t2, `?r`)
21002103
val (fr2, sm, smDef, triggers) = (IsPositive(p1), IsPositive(p2)) match {
@@ -2108,18 +2111,18 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport {
21082111
* we have to introduce a fresh snapshot. Note that it is not sound
21092112
* to use t1 or t2 and constrain it.
21102113
*/
2111-
val t3 = v.decider.fresh(t1.sort, Option.when(withExp)(PUnknown()))
2114+
val t3 = v.decider.appliedFresh("ms", t1.sort, fqvars)
21122115
val lookupT3 = Lookup(field, t3, `?r`)
21132116
val constraint = And(Implies(b1, lookupT3 === lookupT1), Implies(b2, lookupT3 === lookupT2))
21142117
val triggers = Seq(Trigger(lookupT1), Trigger(lookupT2), Trigger(lookupT3))
2115-
(fr.recordConstrainedVar(t3, Forall(`?r`, constraint, triggers)), t3, constraint, triggers)
2118+
(fr.recordPathSymbol(t3.applicable.asInstanceOf[Function]).recordConstraint(Forall(`?r`, constraint, triggers)), t3, constraint, triggers)
21162119
}
21172120

21182121
(fr2, sm, Forall(`?r`, smDef, triggers))
21192122
}
21202123

21212124
// Based on StateConsolidator#combineSnapshots
2122-
override def combinePredicateSnapshotMaps(fr: FunctionRecorder, predicate: String, qVars: Seq[Var], t1: Term, t2: Term, p1: Term, p2: Term, v: Verifier): (FunctionRecorder, Term, Term) = {
2125+
override def combinePredicateSnapshotMaps(fr: FunctionRecorder, predicate: String, qVars: Seq[Var], fqvars: Seq[Var], t1: Term, t2: Term, p1: Term, p2: Term, v: Verifier): (FunctionRecorder, Term, Term) = {
21232126
val lookupT1 = PredicateLookup(predicate, t1, qVars)
21242127
val lookupT2 = PredicateLookup(predicate, t2, qVars)
21252128
val (fr2, sm, smDef, triggers) = (IsPositive(p1), IsPositive(p2)) match {
@@ -2133,10 +2136,11 @@ object quantifiedChunkSupporter extends QuantifiedChunkSupport {
21332136
* we have to introduce a fresh snapshot. Note that it is not sound
21342137
* to use t1 or t2 and constrain it.
21352138
*/
2136-
val t3 = v.decider.fresh(t1.sort, Option.when(withExp)(PUnknown()))
2139+
val t3 = v.decider.appliedFresh("ms", t1.sort, fqvars)
21372140
val lookupT3 = PredicateLookup(predicate, t3, qVars)
2138-
(fr.recordConstrainedVar(t3, And(Implies(b1, lookupT3 === lookupT1), Implies(b2, lookupT3 === lookupT2))), t3,
2139-
And(Implies(b1, lookupT3 === lookupT1), Implies(b2, lookupT3 === lookupT2)), Seq(Trigger(lookupT1), Trigger(lookupT2), Trigger(lookupT3)))
2141+
val constraint = And(Implies(b1, lookupT3 === lookupT1), Implies(b2, lookupT3 === lookupT2))
2142+
val triggers = Seq(Trigger(lookupT1), Trigger(lookupT2), Trigger(lookupT3))
2143+
(fr.recordPathSymbol(t3.applicable.asInstanceOf[Function]).recordConstraint(Forall(qVars, constraint, triggers)), t3, constraint, triggers)
21402144
}
21412145

21422146
(fr2, sm, Forall(qVars, smDef, triggers))

src/main/scala/rules/StateConsolidator.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,14 +207,14 @@ class DefaultStateConsolidator(protected val config: Config) extends StateConsol
207207
assert(l.quantifiedVars == Seq(`?r`))
208208
assert(r.quantifiedVars == Seq(`?r`))
209209
// We need to use l.perm/r.perm here instead of perm1 and perm2 since the permission amount might be dependent on the condition/domain
210-
val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combineFieldSnapshotMaps(fr1, id1.name, fvf1, fvf2, l.perm, r.perm, v)
210+
val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combineFieldSnapshotMaps(fr1, id1.name, qvars, fvf1, fvf2, l.perm, r.perm, v)
211211
val permSum = PermPlus(perm1, perm2)
212212
val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1, perm2Exp.get)())
213213
val bestHints = if (hints1.nonEmpty) hints1 else hints2
214214
Some(fr2, QuantifiedFieldChunk(id1, combinedSnap, condition1, condition1Exp, permSum, permSumExp, invs1, singletonRcvr1, singletonRcvr1Exp, bestHints), snapEq)
215215
case (l@QuantifiedPredicateChunk(id1, qVars1, qVars1Exp, psf1, _, _, perm1, perm1Exp, _, _, _, _),
216216
r@QuantifiedPredicateChunk(_, qVars2, qVars2Exp, psf2, condition2, condition2Exp, perm2, perm2Exp, invs2, singletonArgs2, singletonArgs2Exp, hints2)) =>
217-
val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v)
217+
val (fr2, combinedSnap, snapEq) = quantifiedChunkSupporter.combinePredicateSnapshotMaps(fr1, id1.name, qVars2, qvars, psf1, psf2, l.perm.replace(qVars1, qVars2), r.perm, v)
218218

219219
val permSum = PermPlus(perm1.replace(qVars1, qVars2), perm2)
220220
val permSumExp = perm1Exp.map(p1 => ast.PermAdd(p1.replace(qVars1Exp.get.zip(qVars2Exp.get).toMap), perm2Exp.get)())

0 commit comments

Comments
 (0)