@@ -18,7 +18,7 @@ abstract class UState<Type, Field, Method, Statement>(
18
18
open val pathConstraints : UPathConstraints <Type >,
19
19
open val memory : UMemoryBase <Field , Type , Method >,
20
20
open var models : List <UModelBase <Field , Type >>,
21
- open var path : PersistentList <Statement >
21
+ open var path : PersistentList <Statement >,
22
22
) {
23
23
/* *
24
24
* Deterministic state id.
@@ -40,67 +40,71 @@ abstract class UState<Type, Field, Method, Statement>(
40
40
get() = path.lastOrNull()
41
41
}
42
42
43
- class ForkResult <T >(
43
+ data class ForkResult <T >(
44
44
val positiveState : T ? ,
45
45
val negativeState : T ? ,
46
- ) {
47
- operator fun component1 (): T ? = positiveState
48
- operator fun component2 (): T ? = negativeState
49
- }
46
+ )
47
+
48
+ private typealias StateToCheck = Boolean
49
+
50
+ private const val ForkedState = true
51
+ private const val OriginalState = false
50
52
51
53
/* *
52
- * Checks if [conditionToCheck] is satisfiable within path constraints of [state ].
53
- * If it does, clones [state] and returns it with enriched constraints :
54
- * - if [forkToSatisfied], then adds constraint [satisfiedCondition] ;
55
- * - if ![forkToSatisfied], then adds constraint [conditionToCheck].
56
- * Otherwise, returns null.
57
- * If [conditionToCheck] is not unsatisfiable (i.e., solver returns sat or unknown),
58
- * mutates [ state] by adding new path constraint c:
59
- * - if [forkToSatisfied], then c = [conditionToCheck]
60
- * - if ![forkToSatisfied], then c = [satisfiedCondition]
54
+ * Checks [newConstraintToOriginalState] or [newConstraintToForkedState], depending on the value of [stateToCheck ].
55
+ * Depending on the result of checking this condition, do the following :
56
+ * - On [UUnsatResult] - returns `null` ;
57
+ * - On [UUnknownResult] - adds [newConstraintToOriginalState] to the path constraints of the [state],
58
+ * iff [addConstraintOnUnknown] is `true`, and returns null;
59
+ * - On [USatResult] - clones the original state and adds the [newConstraintToForkedState] to it, adds [newConstraintToOriginalState]
60
+ * to the original state, sets the satisfiable model to the corresponding state depending on the [stateToCheck], and returns the
61
+ * forked state.
62
+ *
61
63
*/
62
64
private fun <T : UState <Type , Field , * , * >, Type , Field > forkIfSat (
63
65
state : T ,
64
- satisfiedCondition : UBoolExpr ,
65
- conditionToCheck : UBoolExpr ,
66
- forkToSatisfied : Boolean ,
66
+ newConstraintToOriginalState : UBoolExpr ,
67
+ newConstraintToForkedState : UBoolExpr ,
68
+ stateToCheck : StateToCheck ,
69
+ addConstraintOnUnknown : Boolean = true,
67
70
): T ? {
68
- val pathConstraints = state.pathConstraints.clone()
69
- pathConstraints + = conditionToCheck
71
+ val constraintsToCheck = state.pathConstraints.clone()
70
72
71
- if (pathConstraints.isFalse) {
72
- return null
73
+ constraintsToCheck + = if (stateToCheck) {
74
+ newConstraintToForkedState
75
+ } else {
76
+ newConstraintToOriginalState
73
77
}
74
-
75
- val solver = satisfiedCondition.uctx.solver<Field , Type , Any ?>()
76
- val satResult = solver.check(pathConstraints, useSoftConstraints = true )
78
+ val solver = newConstraintToForkedState.uctx.solver<Field , Type , Any ?>()
79
+ val satResult = solver.check(constraintsToCheck, useSoftConstraints = true )
77
80
78
81
return when (satResult) {
82
+ is UUnsatResult -> null
83
+
79
84
is USatResult -> {
80
- if (forkToSatisfied) {
85
+ // Note that we cannot extract common code here due to
86
+ // heavy plusAssign operator in path constraints.
87
+ // Therefore, it is better to reuse already constructed [constraintToCheck].
88
+ if (stateToCheck) {
81
89
@Suppress(" UNCHECKED_CAST" )
82
- val forkedState = state.clone() as T
83
- // TODO: implement path condition setter (don't forget to reset UMemoryBase:types!)
84
- state.pathConstraints + = conditionToCheck
85
- state.models = listOf (satResult.model)
86
- forkedState.pathConstraints + = satisfiedCondition
90
+ val forkedState = state.clone(constraintsToCheck) as T
91
+ state.pathConstraints + = newConstraintToOriginalState
92
+ forkedState.models = listOf (satResult.model)
87
93
forkedState
88
94
} else {
89
95
@Suppress(" UNCHECKED_CAST" )
90
- val forkedState = state.clone(pathConstraints) as T
91
- state.pathConstraints + = satisfiedCondition
92
- forkedState.models = listOf (satResult.model)
96
+ val forkedState = state.clone() as T
97
+ state.pathConstraints + = newConstraintToOriginalState
98
+ state.models = listOf (satResult.model)
99
+ // TODO: implement path condition setter (don't forget to reset UMemoryBase:types!)
100
+ forkedState.pathConstraints + = newConstraintToForkedState
93
101
forkedState
94
102
}
95
103
}
96
104
97
- is UUnsatResult -> null
98
-
99
105
is UUnknownResult -> {
100
- if (forkToSatisfied) {
101
- state.pathConstraints + = conditionToCheck
102
- } else {
103
- state.pathConstraints + = satisfiedCondition
106
+ if (addConstraintOnUnknown) {
107
+ state.pathConstraints + = newConstraintToOriginalState
104
108
}
105
109
null
106
110
}
@@ -148,13 +152,18 @@ fun <T : UState<Type, Field, *, *>, Type, Field> fork(
148
152
149
153
trueModels.isNotEmpty() -> state to forkIfSat(
150
154
state,
151
- condition,
152
- notCondition,
153
- forkToSatisfied = false
155
+ newConstraintToOriginalState = condition,
156
+ newConstraintToForkedState = notCondition,
157
+ stateToCheck = ForkedState
154
158
)
155
159
156
160
falseModels.isNotEmpty() -> {
157
- val forkedState = forkIfSat(state, notCondition, condition, forkToSatisfied = true )
161
+ val forkedState = forkIfSat(
162
+ state,
163
+ newConstraintToOriginalState = condition,
164
+ newConstraintToForkedState = notCondition,
165
+ stateToCheck = OriginalState
166
+ )
158
167
159
168
if (forkedState != null ) {
160
169
state to forkedState
@@ -169,3 +178,55 @@ fun <T : UState<Type, Field, *, *>, Type, Field> fork(
169
178
@Suppress(" UNCHECKED_CAST" )
170
179
return ForkResult (posState, negState as T ? )
171
180
}
181
+
182
+ /* *
183
+ * Implements symbolic branching on few disjoint conditions.
184
+ *
185
+ * @return a list of states for each condition - `null` state
186
+ * means [UUnknownResult] or [UUnsatResult] of checking condition.
187
+ */
188
+ fun <T : UState <Type , Field , * , * >, Type , Field > forkMulti (
189
+ state : T ,
190
+ conditions : Iterable <UBoolExpr >,
191
+ ): List <T ?> {
192
+ var curState = state
193
+ val result = mutableListOf<T ?>()
194
+ for (condition in conditions) {
195
+ val (trueModels, _) = curState.models.partition { model ->
196
+ val holdsInModel = model.eval(condition)
197
+ check(holdsInModel is KInterpretedValue <UBoolSort >) {
198
+ " Evaluation in $model on condition $condition : expected true or false, but got $holdsInModel "
199
+ }
200
+ holdsInModel.isTrue
201
+ }
202
+
203
+ val nextRoot = if (trueModels.isNotEmpty()) {
204
+ @Suppress(" UNCHECKED_CAST" )
205
+ val root = curState.clone() as T
206
+
207
+ curState.models = trueModels
208
+ curState.pathConstraints + = condition
209
+
210
+ root
211
+ } else {
212
+ val root = forkIfSat(
213
+ curState,
214
+ newConstraintToOriginalState = condition,
215
+ newConstraintToForkedState = condition.ctx.trueExpr,
216
+ stateToCheck = OriginalState ,
217
+ addConstraintOnUnknown = false
218
+ )
219
+
220
+ root
221
+ }
222
+
223
+ if (nextRoot != null ) {
224
+ result + = curState
225
+ curState = nextRoot
226
+ } else {
227
+ result + = null
228
+ }
229
+ }
230
+
231
+ return result
232
+ }
0 commit comments