Skip to content

Commit fcc4d81

Browse files
authored
Added simplification/rewriting for assumptions (#940)
1 parent 2d092c5 commit fcc4d81

File tree

8 files changed

+99
-36
lines changed

8 files changed

+99
-36
lines changed

utbot-framework-test/src/test/kotlin/org/utbot/engine/pc/QueryOptimizationsTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ fun afterAll() {
5151
class QueryOptimizationsTest {
5252

5353
private fun BaseQuery.check(vararg exprs: UtBoolExpression, checker: (BaseQuery) -> Unit = {}): BaseQuery =
54-
this.with(hard = exprs.toList(), soft = emptyList()).also {
54+
this.with(hard = exprs.toList(), soft = emptyList(), assumptions = emptyList()).also {
5555
checker(it)
5656
}
5757

utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ class BaseStreamExampleTest : UtValueTestCaseChecker(
8787
}
8888

8989
@Test
90-
@Disabled("Java 11 transition -- Yura looks at this. We have similar issue with Strings")
9190
fun testDistinctExample() {
9291
check(
9392
BaseStreamExample::distinctExample,

utbot-framework/src/main/kotlin/org/utbot/engine/DataClasses.kt

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ import org.utbot.engine.pc.UtIsExpression
88
import org.utbot.engine.pc.UtTrue
99
import org.utbot.engine.pc.mkAnd
1010
import org.utbot.engine.pc.mkOr
11-
import org.utbot.engine.symbolic.Assumption
12-
import org.utbot.engine.symbolic.HardConstraint
13-
import org.utbot.engine.symbolic.SoftConstraint
14-
import org.utbot.engine.symbolic.SymbolicStateUpdate
11+
import org.utbot.engine.symbolic.*
1512
import org.utbot.framework.plugin.api.FieldId
1613
import org.utbot.framework.plugin.api.UtInstrumentation
1714
import java.util.Objects
@@ -133,17 +130,17 @@ data class MethodResult(
133130

134131
constructor(
135132
symbolicResult: SymbolicResult,
136-
hardConstraints: HardConstraint = HardConstraint(),
137-
softConstraints: SoftConstraint = SoftConstraint(),
138-
assumption: Assumption = Assumption(),
133+
hardConstraints: HardConstraint = emptyHardConstraint(),
134+
softConstraints: SoftConstraint = emptySoftConstraint(),
135+
assumption: Assumption = emptyAssumption(),
139136
memoryUpdates: MemoryUpdate = MemoryUpdate()
140137
) : this(symbolicResult, SymbolicStateUpdate(hardConstraints, softConstraints, assumption, memoryUpdates))
141138

142139
constructor(
143140
value: SymbolicValue,
144-
hardConstraints: HardConstraint = HardConstraint(),
145-
softConstraints: SoftConstraint = SoftConstraint(),
146-
assumption: Assumption = Assumption(),
141+
hardConstraints: HardConstraint = emptyHardConstraint(),
142+
softConstraints: SoftConstraint = emptySoftConstraint(),
143+
assumption: Assumption = emptyAssumption(),
147144
memoryUpdates: MemoryUpdate = MemoryUpdate(),
148145
) : this(SymbolicSuccess(value), hardConstraints, softConstraints, assumption, memoryUpdates)
149146
}

utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ import org.utbot.engine.pc.store
6969
import org.utbot.engine.symbolic.HardConstraint
7070
import org.utbot.engine.symbolic.SoftConstraint
7171
import org.utbot.engine.symbolic.Assumption
72+
import org.utbot.engine.symbolic.emptyAssumption
73+
import org.utbot.engine.symbolic.emptyHardConstraint
74+
import org.utbot.engine.symbolic.emptySoftConstraint
7275
import org.utbot.engine.symbolic.SymbolicStateUpdate
7376
import org.utbot.engine.symbolic.asHardConstraint
7477
import org.utbot.engine.symbolic.asSoftConstraint
@@ -1076,8 +1079,8 @@ class Traverser(
10761079
}
10771080

10781081
// Depending on existance of assumeExpr we have to add corresponding hardConstraints and assumptions
1079-
val hardConstraints = if (!isAssumeExpr) negativeCasePathConstraint.asHardConstraint() else HardConstraint()
1080-
val assumption = if (isAssumeExpr) negativeCasePathConstraint.asAssumption() else Assumption()
1082+
val hardConstraints = if (!isAssumeExpr) negativeCasePathConstraint.asHardConstraint() else emptyHardConstraint()
1083+
val assumption = if (isAssumeExpr) negativeCasePathConstraint.asAssumption() else emptyAssumption()
10811084

10821085
val negativeCaseState = environment.state.updateQueued(
10831086
negativeCaseEdge,
@@ -2958,8 +2961,9 @@ class Traverser(
29582961
return when (expr) {
29592962
is UtInstanceOfExpression -> { // for now only this type of expression produces deferred updates
29602963
val onlyMemoryUpdates = expr.symbolicStateUpdate.copy(
2961-
hardConstraints = HardConstraint(),
2962-
softConstraints = SoftConstraint()
2964+
hardConstraints = emptyHardConstraint(),
2965+
softConstraints = emptySoftConstraint(),
2966+
assumptions = emptyAssumption()
29632967
)
29642968
SymbolicStateUpdateForResolvedCondition(onlyMemoryUpdates)
29652969
}

utbot-framework/src/main/kotlin/org/utbot/engine/pc/Query.kt

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import kotlinx.collections.immutable.toPersistentSet
3131
sealed class BaseQuery(
3232
open val hard: PersistentSet<UtBoolExpression>,
3333
open val soft: PersistentSet<UtBoolExpression>,
34+
open val assumptions: PersistentSet<UtBoolExpression>,
3435
open val status: UtSolverStatus,
3536
open val lastAdded: Collection<UtBoolExpression>
3637
) {
@@ -40,7 +41,11 @@ sealed class BaseQuery(
4041
* @param hard - new constraints that must be satisfied.
4142
* @param soft - new constraints that are suggested to be satisfied if possible.
4243
*/
43-
abstract fun with(hard: Collection<UtBoolExpression>, soft: Collection<UtBoolExpression>): BaseQuery
44+
abstract fun with(
45+
hard: Collection<UtBoolExpression>,
46+
soft: Collection<UtBoolExpression>,
47+
assumptions: Collection<UtBoolExpression>
48+
): BaseQuery
4449

4550
/**
4651
* Set [status] of the query.
@@ -55,11 +60,19 @@ sealed class BaseQuery(
5560
* UnsatQuery.[status] is [UtSolverStatusUNSAT].
5661
* UnsatQuery.[lastAdded] = [emptyList]
5762
*/
58-
class UnsatQuery(hard: PersistentSet<UtBoolExpression>) :
59-
BaseQuery(hard, persistentHashSetOf(), UtSolverStatusUNSAT(UtSolverStatusKind.UNSAT), emptyList()) {
63+
class UnsatQuery(hard: PersistentSet<UtBoolExpression>) : BaseQuery(
64+
hard,
65+
soft = persistentHashSetOf(),
66+
assumptions = persistentHashSetOf(),
67+
UtSolverStatusUNSAT(UtSolverStatusKind.UNSAT),
68+
lastAdded = emptyList()
69+
) {
6070

61-
override fun with(hard: Collection<UtBoolExpression>, soft: Collection<UtBoolExpression>): BaseQuery =
62-
error("State with UnsatQuery isn't eliminated. Adding constraints to $this isn't allowed.")
71+
override fun with(
72+
hard: Collection<UtBoolExpression>,
73+
soft: Collection<UtBoolExpression>,
74+
assumptions: Collection<UtBoolExpression>
75+
): BaseQuery = error("State with UnsatQuery isn't eliminated. Adding constraints to $this isn't allowed.")
6376

6477
override fun withStatus(newStatus: UtSolverStatus) = this
6578

@@ -78,12 +91,13 @@ class UnsatQuery(hard: PersistentSet<UtBoolExpression>) :
7891
data class Query(
7992
override val hard: PersistentSet<UtBoolExpression> = persistentHashSetOf(),
8093
override val soft: PersistentSet<UtBoolExpression> = persistentHashSetOf(),
94+
override val assumptions: PersistentSet<UtBoolExpression> = persistentHashSetOf(),
8195
override val status: UtSolverStatus = UtSolverStatusUNDEFINED,
8296
override val lastAdded: Collection<UtBoolExpression> = emptyList(),
8397
private val eqs: PersistentMap<UtExpression, UtExpression> = persistentHashMapOf(),
8498
private val lts: PersistentMap<UtExpression, Long> = persistentHashMapOf(),
8599
private val gts: PersistentMap<UtExpression, Long> = persistentHashMapOf()
86-
) : BaseQuery(hard, soft, status, lastAdded) {
100+
) : BaseQuery(hard, soft, assumptions, status, lastAdded) {
87101

88102
val rewriter: RewritingVisitor
89103
get() = RewritingVisitor(eqs, lts, gts)
@@ -179,6 +193,7 @@ data class Query(
179193
private fun addSimplified(
180194
hard: Collection<UtBoolExpression>,
181195
soft: Collection<UtBoolExpression>,
196+
assumptions: Collection<UtBoolExpression>
182197
): BaseQuery {
183198
val addedHard = hard.simplify().filterNot { it is UtTrue }
184199
if (addedHard.isEmpty() && soft.isEmpty()) {
@@ -266,20 +281,48 @@ data class Query(
266281
.filterNot { it is UtBoolLiteral }
267282
.toPersistentSet()
268283

284+
// Apply simplifications to assumptions in query.
285+
// We do not filter out UtFalse here because we need them to get UNSAT in corresponding cases and run concrete instead.
286+
val newAssumptions = this.assumptions
287+
.addAll(assumptions)
288+
.simplify(newEqs, newLts, newGts)
289+
.toPersistentSet()
290+
269291
val diffHard = newHard - this.hard
270-
return Query(newHard, newSoft, status.checkFastSatAndReturnStatus(diffHard), diffHard, newEqs, newLts, newGts)
292+
293+
return Query(
294+
newHard,
295+
newSoft,
296+
newAssumptions,
297+
status.checkFastSatAndReturnStatus(diffHard),
298+
lastAdded = diffHard,
299+
newEqs,
300+
newLts,
301+
newGts
302+
)
271303
}
272304

273305
/**
274306
* Add constraints to query and apply simplifications if [useExpressionSimplification] is true.
275307
* @param hard - set of constraints that must be satisfied.
276308
* @param soft - set of constraints that should be satisfied if possible.
277309
*/
278-
override fun with(hard: Collection<UtBoolExpression>, soft: Collection<UtBoolExpression>): BaseQuery {
310+
override fun with(
311+
hard: Collection<UtBoolExpression>,
312+
soft: Collection<UtBoolExpression>,
313+
assumptions: Collection<UtBoolExpression>
314+
): BaseQuery {
279315
return if (useExpressionSimplification) {
280-
addSimplified(hard, soft)
316+
addSimplified(hard, soft, assumptions)
281317
} else {
282-
Query(this.hard.addAll(hard), this.soft.addAll(soft), status.checkFastSatAndReturnStatus(hard), hard, this.eqs)
318+
Query(
319+
this.hard.addAll(hard),
320+
this.soft.addAll(soft),
321+
this.assumptions.addAll(assumptions),
322+
status.checkFastSatAndReturnStatus(hard),
323+
lastAdded = hard,
324+
this.eqs
325+
)
283326
}
284327
}
285328

utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSolver.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import com.microsoft.z3.Status.UNSATISFIABLE
3131
import kotlinx.collections.immutable.PersistentSet
3232
import kotlinx.collections.immutable.persistentHashSetOf
3333
import mu.KotlinLogging
34+
import org.utbot.engine.symbolic.asAssumption
35+
import org.utbot.engine.symbolic.emptyAssumption
3436
import soot.ByteType
3537
import soot.CharType
3638
import soot.IntType
@@ -131,7 +133,7 @@ data class UtSolver constructor(
131133
// Constraints that should not be added in the solver as hypothesis.
132134
// Instead, we use `check` to find out if they are satisfiable.
133135
// It is required to have unsat cores with them.
134-
var assumption: Assumption = Assumption(),
136+
var assumption: Assumption = emptyAssumption(),
135137

136138
//new constraints for solver (kind of incremental behavior)
137139
private var hardConstraintsNotYetAddedToZ3Solver: PersistentSet<UtBoolExpression> = persistentHashSetOf(),
@@ -175,9 +177,9 @@ data class UtSolver constructor(
175177

176178
var expectUndefined: Boolean = false
177179

178-
fun add(hard: HardConstraint, soft: SoftConstraint, assumption: Assumption = Assumption()): UtSolver {
180+
fun add(hard: HardConstraint, soft: SoftConstraint, assumption: Assumption): UtSolver {
179181
// status can implicitly change here to UNDEFINED or UNSAT
180-
val newConstraints = constraints.with(hard.constraints, soft.constraints)
182+
val newConstraints = constraints.with(hard.constraints, soft.constraints, assumption.constraints)
181183
val wantClone = (expectUndefined && newConstraints.status is UtSolverStatusUNDEFINED)
182184
|| (!expectUndefined && newConstraints.status !is UtSolverStatusUNSAT)
183185

@@ -204,7 +206,7 @@ data class UtSolver constructor(
204206
copy(
205207
constraints = constraintsWithStatus,
206208
hardConstraintsNotYetAddedToZ3Solver = newConstraints.hard,
207-
assumption = this.assumption + assumption,
209+
assumption = newConstraints.assumptions.asAssumption(),
208210
z3Solver = context.mkSolver().also { it.setParameters(params) },
209211
)
210212
}

utbot-framework/src/main/kotlin/org/utbot/engine/symbolic/SymbolicStateUpdate.kt

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@ sealed class Constraint<T : Constraint<T>>(constraints: Set<UtBoolExpression> =
2222
/**
2323
* Represents hard constraints.
2424
*/
25-
class HardConstraint(
25+
open class HardConstraint(
2626
constraints: Set<UtBoolExpression> = emptySet()
2727
) : Constraint<HardConstraint>(constraints) {
2828
override fun plus(other: HardConstraint): HardConstraint =
2929
HardConstraint(addConstraints(other.constraints))
30+
31+
companion object {
32+
internal val EMPTY: HardConstraint = HardConstraint()
33+
}
3034
}
3135

36+
fun emptyHardConstraint(): HardConstraint = HardConstraint.EMPTY
37+
3238
/**
3339
* Represents soft constraints.
3440
*/
@@ -37,8 +43,14 @@ class SoftConstraint(
3743
) : Constraint<SoftConstraint>(constraints) {
3844
override fun plus(other: SoftConstraint): SoftConstraint =
3945
SoftConstraint(addConstraints(other.constraints))
46+
47+
companion object {
48+
internal val EMPTY: SoftConstraint = SoftConstraint()
49+
}
4050
}
4151

52+
fun emptySoftConstraint(): SoftConstraint = SoftConstraint.EMPTY
53+
4254
/**
4355
* Represent constraints that must be satisfied for symbolic execution.
4456
* At the same time, if they don't, the state they belong to still
@@ -52,17 +64,23 @@ class Assumption(
5264
override fun plus(other: Assumption): Assumption = Assumption(addConstraints(other.constraints))
5365

5466
override fun toString() = constraints.joinToString(System.lineSeparator())
67+
68+
companion object {
69+
internal val EMPTY: Assumption = Assumption()
70+
}
5571
}
5672

73+
fun emptyAssumption(): Assumption = Assumption.EMPTY
74+
5775
/**
5876
* Represents one or more updates that can be applied to [SymbolicState].
5977
*
6078
* TODO: move [localMemoryUpdates] to another place
6179
*/
6280
data class SymbolicStateUpdate(
63-
val hardConstraints: HardConstraint = HardConstraint(),
64-
val softConstraints: SoftConstraint = SoftConstraint(),
65-
val assumptions: Assumption = Assumption(),
81+
val hardConstraints: HardConstraint = emptyHardConstraint(),
82+
val softConstraints: SoftConstraint = emptySoftConstraint(),
83+
val assumptions: Assumption = emptyAssumption(),
6684
val memoryUpdates: MemoryUpdate = MemoryUpdate(),
6785
val localMemoryUpdates: LocalMemoryUpdate = LocalMemoryUpdate()
6886
) {
@@ -107,7 +125,7 @@ fun Collection<UtBoolExpression>.asSoftConstraint() = SoftConstraint(transformTo
107125

108126
fun UtBoolExpression.asSoftConstraint() = SoftConstraint(setOf(this))
109127

110-
fun Collection<UtBoolExpression>.asAssumption() = Assumption(toSet())
128+
fun Collection<UtBoolExpression>.asAssumption() = Assumption(transformToSet())
111129

112130
fun UtBoolExpression.asAssumption() = Assumption(setOf(this))
113131

utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ boolean distinctExample(List<Integer> list) {
6868

6969
int prevSize = list.size();
7070

71-
int newSize = list.stream().distinct().toArray().length;
71+
int newSize = (int) list.stream().distinct().count();
7272

7373
return prevSize != newSize;
7474
}

0 commit comments

Comments
 (0)