-
Notifications
You must be signed in to change notification settings - Fork 37
/
Copy pathJoiner.scala
124 lines (109 loc) · 5.57 KB
/
Joiner.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2011-2019 ETH Zurich.
package viper.silicon.rules
import viper.silicon.decider.RecordedPathConditions
import viper.silicon.interfaces.{Success, VerificationResult}
import viper.silicon.logger.records.structural.JoiningRecord
import viper.silicon.state.State
import viper.silicon.state.terms.{And, Or, Term}
import viper.silicon.verifier.Verifier
case class JoinDataEntry[D](s: State, data: D, pathConditions: RecordedPathConditions) {
// Instead of merging states by calling State.merge,
// we can directly merge JoinDataEntries to obtain new States,
// and the join data entries themselves provide information about the path conditions to State.merge.
def pathConditionAwareMerge(other: JoinDataEntry[D], v: Verifier): State = {
val res = State.merge(this.s, this.pathConditions, other.s, other.pathConditions)
v.stateConsolidator(s).consolidate(res, v)
}
def pathConditionAwareMergeWithoutConsolidation(other: JoinDataEntry[D], v: Verifier): State = {
State.merge(this.s, this.pathConditions, other.s, other.pathConditions)
}
}
trait JoiningRules extends SymbolicExecutionRules {
def join[D, JD](s: State, v: Verifier, resetState: Boolean = true)
(block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult)
(merge: Seq[JoinDataEntry[D]] => (State, JD))
(Q: (State, JD, Verifier) => VerificationResult)
: VerificationResult
}
object joiner extends JoiningRules {
/**
* This method is a higher-order function that handles the joining of different execution
* paths in a symbolic execution engine. It records the data collected from each execution path
* and merges it into a single data structure. The final data structure is then passed to a
* function that performs some final verification step.
*
* @param s The current state of the symbolic execution.
* @param v The current verifier.
* @param resetState A flag indicating whether the state should be reset after each execution path.
* @param block A function that executes a block of code symbolically and collects data
* from each feasible execution path.
* @param merge A function that merges the data collected from each feasible execution path.
* @param Q A function that is called after all execution paths have been joined and the data has been merged.
* It is expected to perform some final verification step.
* @tparam D The generic type parameter for the data collected from each execution path.
* @tparam JD The generic type parameter for the merged data.
* @return The final result of the join method, which is the result of the Q function.
*/
def join[D, JD](s: State, v: Verifier, resetState: Boolean = true)
(block: (State, Verifier, (State, D, Verifier) => VerificationResult) => VerificationResult)
(merge: Seq[JoinDataEntry[D]] => (State, JD))
(Q: (State, JD, Verifier) => VerificationResult)
: VerificationResult = {
var entries: Seq[JoinDataEntry[D]] = Vector.empty
val joiningRecord = new JoiningRecord(s, v.decider.pcs)
val uidJoin = v.symbExLog.openScope(joiningRecord)
executionFlowController.locally(s, v)((s1, v1) => {
val preMark = v1.decider.setPathConditionMark()
val s2 = s1.copy(underJoin = true)
block(s2, v1, (s3, data, v2) => {
val s4 =
if (resetState) {
/* In order to prevent mismatches between different final states of the evaluation
* paths that are to be joined, we reset certain state properties that may have been
* affected by the evaluation - such as the store (by let-bindings) or the heap (by
* state consolidations) to their initial values.
*/
s3.copy(g = s1.g,
h = s1.h,
oldHeaps = s1.oldHeaps,
underJoin = s1.underJoin,
// TODO: Evaluation should not affect partiallyConsumedHeap, probably
ssCache = s1.ssCache,
partiallyConsumedHeap = s1.partiallyConsumedHeap,
invariantContexts = s1.invariantContexts,
retrying = s1.retrying)
} else {
// For more joins, state shouldn't be reset.
s3
}
entries :+= JoinDataEntry(s4, data, v2.decider.pcs.after(preMark))
Success()
})
}) combine {
v.symbExLog.closeScope(uidJoin)
if (entries.isEmpty) {
/* No block data was collected, which we interpret as all branches through
* the block being infeasible. In turn, we assume that the overall verification path
* is infeasible. Instead of calling Q, we therefore terminate this path.
*/
Success()
} else {
val (sJoined, dataJoined) = merge(entries)
var feasibleBranches: List[Term] = Nil
entries foreach (entry => {
val pcs = entry.pathConditions.conditionalized
v.decider.prover.comment("Joined path conditions")
v.decider.assume(pcs)
feasibleBranches = And(entry.pathConditions.branchConditions) :: feasibleBranches
})
// Assume we are in a feasible branch
v.decider.assume(Or(feasibleBranches))
Q(sJoined, dataJoined, v)
}
}
}
}