Skip to content

Commit 2e43190

Browse files
committed
Test generation for Optional<T> in the plugin (#226)
Added a check for overridden classes in `shouldMock` to avoid access to engine classes that are not available in the plugin. Implemented more accurate speculative marking of final fields as not null to avoid losing paths involving `Optional.empty()`, to enable NPE checks for final fields in user code, and to avoid generating non-informative NPE tests for final fields in system classes. UtSettings.checkNpeForFinalFields is now set default (false) in `AbstractTestCaseGEneratorTest` and `SummaryTestCaseGeneratorTest`.
1 parent 576c9a6 commit 2e43190

File tree

8 files changed

+93
-68
lines changed

8 files changed

+93
-68
lines changed

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

+1-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import org.utbot.engine.pc.UtSeqSort
1818
import org.utbot.engine.pc.UtShortSort
1919
import org.utbot.engine.pc.UtSolverStatusKind
2020
import org.utbot.engine.pc.UtSolverStatusSAT
21-
import org.utbot.engine.symbolic.Assumption
2221
import org.utbot.engine.pc.UtSort
2322
import org.utbot.engine.pc.mkArrayWithConst
2423
import org.utbot.engine.pc.mkBool
@@ -31,7 +30,6 @@ import org.utbot.engine.pc.mkLong
3130
import org.utbot.engine.pc.mkShort
3231
import org.utbot.engine.pc.mkString
3332
import org.utbot.engine.pc.toSort
34-
import org.utbot.framework.UtSettings.checkNpeForFinalFields
3533
import org.utbot.framework.UtSettings.checkNpeInNestedMethods
3634
import org.utbot.framework.UtSettings.checkNpeInNestedNotPrivateMethods
3735
import org.utbot.framework.plugin.api.FieldId
@@ -384,11 +382,7 @@ fun arrayTypeUpdate(addr: UtAddrExpression, type: ArrayType) =
384382
fun SootField.shouldBeNotNull(): Boolean {
385383
require(type is RefLikeType)
386384

387-
if (hasNotNullAnnotation()) return true
388-
389-
if (!checkNpeForFinalFields && isFinal) return true
390-
391-
return false
385+
return hasNotNullAnnotation()
392386
}
393387

394388
/**

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

+25-4
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,16 @@ data class Memory( // TODO: split purely symbolic memory and information about s
137137
UtFalse,
138138
UtArraySort(UtAddrSort, UtBoolSort)
139139
),
140-
private val instanceFieldReadOperations: PersistentSet<InstanceFieldReadOperation> = persistentHashSetOf()
140+
private val instanceFieldReadOperations: PersistentSet<InstanceFieldReadOperation> = persistentHashSetOf(),
141+
142+
/**
143+
* Storage for addresses that we speculatively consider non-nullable (e.g., final fields of system classes).
144+
* See [org.utbot.engine.UtBotSymbolicEngine.createFieldOrMock] for details.
145+
*/
146+
private val speculativelyNotNullAddresses: UtArrayExpressionBase = UtConstArrayExpression(
147+
UtFalse,
148+
UtArraySort(UtAddrSort, UtBoolSort)
149+
)
141150
) {
142151
val chunkIds: Set<ChunkId>
143152
get() = initial.keys
@@ -167,6 +176,11 @@ data class Memory( // TODO: split purely symbolic memory and information about s
167176
*/
168177
fun isTouched(addr: UtAddrExpression): UtArraySelectExpression = touchedAddresses.select(addr)
169178

179+
/**
180+
* Returns symbolic information about whether [addr] corresponds to a final field known to be not null.
181+
*/
182+
fun isSpeculativelyNotNull(addr: UtAddrExpression): UtArraySelectExpression = speculativelyNotNullAddresses.select(addr)
183+
170184
/**
171185
* @return ImmutableCollection of the initial values for all the arrays we touched during the execution
172186
*/
@@ -260,6 +274,10 @@ data class Memory( // TODO: split purely symbolic memory and information about s
260274
acc.store(addr, UtTrue)
261275
}
262276

277+
val updSpeculativelyNotNullAddresses = update.speculativelyNotNullAddresses.fold(speculativelyNotNullAddresses) { acc, addr ->
278+
acc.store(addr, UtTrue)
279+
}
280+
263281
return this.copy(
264282
initial = updInitial,
265283
current = updCurrent,
@@ -275,7 +293,8 @@ data class Memory( // TODO: split purely symbolic memory and information about s
275293
updates = updates + update,
276294
visitedValues = updVisitedValues,
277295
touchedAddresses = updTouchedAddresses,
278-
instanceFieldReadOperations = instanceFieldReadOperations.addAll(update.instanceFieldReads)
296+
instanceFieldReadOperations = instanceFieldReadOperations.addAll(update.instanceFieldReads),
297+
speculativelyNotNullAddresses = updSpeculativelyNotNullAddresses
279298
)
280299
}
281300

@@ -955,7 +974,8 @@ data class MemoryUpdate(
955974
val visitedValues: PersistentList<UtAddrExpression> = persistentListOf(),
956975
val touchedAddresses: PersistentList<UtAddrExpression> = persistentListOf(),
957976
val classIdToClearStatics: ClassId? = null,
958-
val instanceFieldReads: PersistentSet<InstanceFieldReadOperation> = persistentHashSetOf()
977+
val instanceFieldReads: PersistentSet<InstanceFieldReadOperation> = persistentHashSetOf(),
978+
val speculativelyNotNullAddresses: PersistentList<UtAddrExpression> = persistentListOf()
959979
) {
960980
operator fun plus(other: MemoryUpdate) =
961981
this.copy(
@@ -972,7 +992,8 @@ data class MemoryUpdate(
972992
visitedValues = visitedValues.addAll(other.visitedValues),
973993
touchedAddresses = touchedAddresses.addAll(other.touchedAddresses),
974994
classIdToClearStatics = other.classIdToClearStatics,
975-
instanceFieldReads = instanceFieldReads.addAll(other.instanceFieldReads)
995+
instanceFieldReads = instanceFieldReads.addAll(other.instanceFieldReads),
996+
speculativelyNotNullAddresses = speculativelyNotNullAddresses.addAll(other.speculativelyNotNullAddresses),
976997
)
977998
}
978999

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,13 @@ class Mocker(
184184
if (mockAlways(type)) return true // always mock randoms and loggers
185185
if (mockInfo is UtFieldMockInfo) {
186186
val declaringClass = mockInfo.fieldId.declaringClass
187+
val sootDeclaringClass = Scene.v().getSootClass(declaringClass.name)
187188

188-
if (Scene.v().getSootClass(declaringClass.name).isArtificialEntity) {
189-
return false // see BaseStreamExample::minExample for an example; cannot load java class for such class
189+
if (sootDeclaringClass.isArtificialEntity || sootDeclaringClass.isOverridden) {
190+
// Cannot load Java class for artificial classes, see BaseStreamExample::minExample for an example.
191+
// Wrapper classes that override system classes ([org.utbot.engine.overrides] package) are also
192+
// unavailable to the [UtContext] class loader used by the plugin.
193+
return false
190194
}
191195

192196
return when {

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

+47-52
Original file line numberDiff line numberDiff line change
@@ -34,55 +34,7 @@ import org.utbot.engine.MockStrategy.NO_MOCKS
3434
import org.utbot.engine.overrides.UtArrayMock
3535
import org.utbot.engine.overrides.UtLogicMock
3636
import org.utbot.engine.overrides.UtOverrideMock
37-
import org.utbot.engine.pc.NotBoolExpression
38-
import org.utbot.engine.pc.UtAddNoOverflowExpression
39-
import org.utbot.engine.pc.UtAddrExpression
40-
import org.utbot.engine.pc.UtAndBoolExpression
41-
import org.utbot.engine.pc.UtArrayApplyForAll
42-
import org.utbot.engine.pc.UtArrayExpressionBase
43-
import org.utbot.engine.pc.UtArraySelectExpression
44-
import org.utbot.engine.pc.UtArraySetRange
45-
import org.utbot.engine.pc.UtArraySort
46-
import org.utbot.engine.pc.UtBoolExpression
47-
import org.utbot.engine.pc.UtBoolOpExpression
48-
import org.utbot.engine.pc.UtBvConst
49-
import org.utbot.engine.pc.UtBvLiteral
50-
import org.utbot.engine.pc.UtByteSort
51-
import org.utbot.engine.pc.UtCastExpression
52-
import org.utbot.engine.pc.UtCharSort
53-
import org.utbot.engine.pc.UtContextInitializer
54-
import org.utbot.engine.pc.UtExpression
55-
import org.utbot.engine.pc.UtFalse
56-
import org.utbot.engine.pc.UtInstanceOfExpression
57-
import org.utbot.engine.pc.UtIntSort
58-
import org.utbot.engine.pc.UtIsExpression
59-
import org.utbot.engine.pc.UtIteExpression
60-
import org.utbot.engine.pc.UtLongSort
61-
import org.utbot.engine.pc.UtMkTermArrayExpression
62-
import org.utbot.engine.pc.UtNegExpression
63-
import org.utbot.engine.pc.UtOrBoolExpression
64-
import org.utbot.engine.pc.UtPrimitiveSort
65-
import org.utbot.engine.pc.UtShortSort
66-
import org.utbot.engine.pc.UtSolver
67-
import org.utbot.engine.pc.UtSolverStatusSAT
68-
import org.utbot.engine.pc.UtSubNoOverflowExpression
69-
import org.utbot.engine.pc.UtTrue
70-
import org.utbot.engine.pc.addrEq
71-
import org.utbot.engine.pc.align
72-
import org.utbot.engine.pc.cast
73-
import org.utbot.engine.pc.findTheMostNestedAddr
74-
import org.utbot.engine.pc.isInteger
75-
import org.utbot.engine.pc.mkAnd
76-
import org.utbot.engine.pc.mkBVConst
77-
import org.utbot.engine.pc.mkBoolConst
78-
import org.utbot.engine.pc.mkChar
79-
import org.utbot.engine.pc.mkEq
80-
import org.utbot.engine.pc.mkFpConst
81-
import org.utbot.engine.pc.mkInt
82-
import org.utbot.engine.pc.mkNot
83-
import org.utbot.engine.pc.mkOr
84-
import org.utbot.engine.pc.select
85-
import org.utbot.engine.pc.store
37+
import org.utbot.engine.pc.*
8638
import org.utbot.engine.selectors.PathSelector
8739
import org.utbot.engine.selectors.StrategyOption
8840
import org.utbot.engine.selectors.coveredNewSelector
@@ -115,6 +67,7 @@ import org.utbot.engine.util.statics.concrete.makeEnumStaticFieldsUpdates
11567
import org.utbot.engine.util.statics.concrete.makeSymbolicValuesFromEnumConcreteValues
11668
import org.utbot.framework.PathSelectorType
11769
import org.utbot.framework.UtSettings
70+
import org.utbot.framework.UtSettings.checkNpeForFinalFields
11871
import org.utbot.framework.UtSettings.checkSolverTimeoutMillis
11972
import org.utbot.framework.UtSettings.enableFeatureProcess
12073
import org.utbot.framework.UtSettings.pathSelectorStepsLimit
@@ -1993,6 +1946,10 @@ class UtBotSymbolicEngine(
19931946
is JInstanceFieldRef -> {
19941947
val instance = (base.resolve() as ObjectValue)
19951948
recordInstanceFieldRead(instance.addr, field)
1949+
1950+
// We know that [base] is not null as we are accessing its field (dot access).
1951+
// At the same time, we don't want to check for NPE if [base] is a final field
1952+
// (or if it is a non-nullable field).
19961953
nullPointerExceptionCheck(instance.addr)
19971954

19981955
val objectType = if (instance.concrete?.value is BaseOverriddenWrapper) {
@@ -2199,8 +2156,41 @@ class UtBotSymbolicEngine(
21992156
val chunkId = hierarchy.chunkIdForField(objectType, field)
22002157
val createdField = createField(objectType, addr, field.type, chunkId, mockInfoGenerator)
22012158

2202-
if (field.type is RefLikeType && field.shouldBeNotNull()) {
2203-
queuedSymbolicStateUpdates += mkNot(mkEq(createdField.addr, nullObjectAddr)).asHardConstraint()
2159+
if (field.type is RefLikeType) {
2160+
if (field.shouldBeNotNull()) {
2161+
queuedSymbolicStateUpdates += mkNot(mkEq(createdField.addr, nullObjectAddr)).asHardConstraint()
2162+
}
2163+
2164+
// We suppose that accessing final fields in system classes can't produce NullPointerException
2165+
// because they are properly initialized in corresponding constructors. It is therefore
2166+
// desirable to avoid the generation of redundant test cases for NPE branches.
2167+
//
2168+
// At the same time, we can't always add the "not null" hard constraint for the field: it would break
2169+
// some special cases like `Optional<T>` class, which uses the null value of its final field
2170+
// as a marker of an empty value.
2171+
//
2172+
// The engine checks for NPE and creates an NPE branch every time the address is used
2173+
// as a base of a dot call (i.e., a method call or a field access);
2174+
// see [UtBotSymbolicEngine.nullPointerExceptionCheck]). The problem is what at that moment, we would have
2175+
// no way to check whether the address corresponds to a final field, as the corresponding node
2176+
// of the global graph would refer to a local variable. The only place where we have the complete
2177+
// information about the field is this method.
2178+
//
2179+
// We use the following approach. If the field is final and belongs to a system class,
2180+
// we mark it as a speculatively non-nullable in the memory. During the NPE check
2181+
// we will add two constraints to the NPE branch: "address has not been speculatively marked
2182+
// as non-nullable", and "address is null".
2183+
//
2184+
// For final fields, this condition can't be satisfied, as we speculatively mark final fields
2185+
// as non-nullable here. As a result, the NPE branch would be discarded. If a field is not final,
2186+
// the condition is satisfiable, so the NPE branch would stay alive.
2187+
//
2188+
// We limit this approach to the system classes only, because it is hard to speculatively assume
2189+
// something about non-nullability of final fields in the user code.
2190+
2191+
if (field.isFinal && !field.declaringClass.isApplicationClass && !checkNpeForFinalFields) {
2192+
markAsSpeculativelyNotNull(createdField.addr)
2193+
}
22042194
}
22052195

22062196
return createdField
@@ -2370,6 +2360,10 @@ class UtBotSymbolicEngine(
23702360
queuedSymbolicStateUpdates += MemoryUpdate(touchedAddresses = persistentListOf(addr))
23712361
}
23722362

2363+
private fun markAsSpeculativelyNotNull(addr: UtAddrExpression) {
2364+
queuedSymbolicStateUpdates += MemoryUpdate(speculativelyNotNullAddresses = persistentListOf(addr))
2365+
}
2366+
23732367
/**
23742368
* Add a memory update to reflect that a field was read.
23752369
*
@@ -3290,9 +3284,10 @@ class UtBotSymbolicEngine(
32903284
private fun nullPointerExceptionCheck(addr: UtAddrExpression) {
32913285
val canBeNull = addrEq(addr, nullObjectAddr)
32923286
val canNotBeNull = mkNot(canBeNull)
3287+
val notFinalAndNull = mkAnd(mkEq(memory.isSpeculativelyNotNull(addr), mkFalse()), canBeNull)
32933288

32943289
if (environment.method.checkForNPE(environment.state.executionStack.size)) {
3295-
implicitlyThrowException(NullPointerException(), setOf(canBeNull))
3290+
implicitlyThrowException(NullPointerException(), setOf(notFinalAndNull))
32963291
}
32973292

32983293
queuedSymbolicStateUpdates += canNotBeNull.asHardConstraint()

utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt

-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ abstract class AbstractTestCaseGeneratorTest(
8888
UtSettings.checkSolverTimeoutMillis = 0
8989
UtSettings.checkNpeInNestedMethods = true
9090
UtSettings.checkNpeInNestedNotPrivateMethods = true
91-
UtSettings.checkNpeForFinalFields = true
9291
UtSettings.substituteStaticsWithSymbolicVariable = true
9392
UtSettings.useAssembleModelGenerator = true
9493
UtSettings.saveRemainingStatesForConcreteExecution = false

utbot-framework/src/test/kotlin/org/utbot/examples/collections/OptionalsTest.kt

+10
Original file line numberDiff line numberDiff line change
@@ -482,4 +482,14 @@ class OptionalsTest : AbstractTestCaseGeneratorTest(
482482
coverage = DoNotCalculate
483483
)
484484
}
485+
486+
@Test
487+
fun testOptionalOfPositive() {
488+
check(
489+
Optionals::optionalOfPositive,
490+
eq(2),
491+
{ value, result -> value > 0 && result != null && result.isPresent && result.get() == value },
492+
{ value, result -> value <= 0 && result != null && !result.isPresent }
493+
)
494+
}
485495
}

utbot-sample/src/main/java/org/utbot/examples/collections/Optionals.java

+4
Original file line numberDiff line numberDiff line change
@@ -314,4 +314,8 @@ public boolean equalOptionalsDouble(OptionalDouble left, OptionalDouble right) {
314314
return false;
315315
}
316316
}
317+
318+
public Optional<Integer> optionalOfPositive(int value) {
319+
return value > 0 ? Optional.of(value) : Optional.empty();
320+
}
317321
}

utbot-summary-tests/src/test/kotlin/examples/SummaryTestCaseGeneratorTest.kt

-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import org.utbot.common.workaround
66
import org.utbot.examples.AbstractTestCaseGeneratorTest
77
import org.utbot.examples.CoverageMatcher
88
import org.utbot.examples.DoNotCalculate
9-
import org.utbot.framework.UtSettings.checkNpeForFinalFields
109
import org.utbot.framework.UtSettings.checkNpeInNestedMethods
1110
import org.utbot.framework.UtSettings.checkNpeInNestedNotPrivateMethods
1211
import org.utbot.framework.UtSettings.checkSolverTimeoutMillis
@@ -96,7 +95,6 @@ open class SummaryTestCaseGeneratorTest(
9695
checkSolverTimeoutMillis = 0
9796
checkNpeInNestedMethods = true
9897
checkNpeInNestedNotPrivateMethods = true
99-
checkNpeForFinalFields = true
10098
}
10199
val utMethod = UtMethod.from(method)
102100
val testCase = executionsModel(utMethod, mockStrategy)

0 commit comments

Comments
 (0)