Skip to content

Commit dff228f

Browse files
authored
Map don't work without concrete (#1228)
* Map don't work without concrete Fixes: * aliasing between nested arrays of UtHashMap and its fields * unsat for get operation from Map * Fixes for type system and fallbacks in it
1 parent dcb65f3 commit dff228f

File tree

11 files changed

+267
-58
lines changed

11 files changed

+267
-58
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,8 @@ enum class CodegenLanguage(
12971297
JAVA -> listOf(
12981298
"-d", buildDirectory,
12991299
"-cp", classPath,
1300-
"-XDignore.symbol.file" // to let javac use classes from rt.jar
1300+
"-XDignore.symbol.file", // to let javac use classes from rt.jar
1301+
"--add-exports", "java.base/sun.reflect.generics.repository=ALL-UNNAMED"
13011302
).plus(sourcesFiles)
13021303

13031304
KOTLIN -> listOf("-d", buildDirectory, "-jvm-target", jvmTarget, "-cp", classPath).plus(sourcesFiles)

utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArrayOfObjectsTest.kt

+11-9
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,16 @@ internal class ArrayOfObjectsTest : UtValueTestCaseChecker(
103103

104104
@Test
105105
fun testArrayOfArrays() {
106-
check(
107-
ArrayOfObjects::arrayOfArrays,
108-
between(4..5), // might be two ClassCastExceptions
109-
{ a, _ -> a.any { it == null } },
110-
{ a, _ -> a.any { it != null && it !is IntArray } },
111-
{ a, r -> (a.all { it != null && it is IntArray && it.isEmpty() } || a.isEmpty()) && r == 0 },
112-
{ a, r -> a.all { it is IntArray } && r == a.sumBy { (it as IntArray).sum() } },
113-
coverage = DoNotCalculate
114-
)
106+
withEnabledTestingCodeGeneration(testCodeGeneration = false) {
107+
check(
108+
ArrayOfObjects::arrayOfArrays,
109+
between(4..5), // might be two ClassCastExceptions
110+
{ a, _ -> a.any { it == null } },
111+
{ a, _ -> a.any { it != null && it !is IntArray } },
112+
{ a, r -> (a.all { it != null && it is IntArray && it.isEmpty() } || a.isEmpty()) && r == 0 },
113+
{ a, r -> a.all { it is IntArray } && r == a.sumBy { (it as IntArray).sum() } },
114+
coverage = DoNotCalculate
115+
)
116+
}
115117
}
116118
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import org.utbot.tests.infrastructure.isException
77
import org.utbot.framework.plugin.api.CodegenLanguage
88
import org.junit.jupiter.api.Disabled
99
import org.junit.jupiter.api.Test
10-
import org.utbot.testcheckers.eq
1110
import org.utbot.testcheckers.ge
1211
import org.utbot.testcheckers.withPushingStateFromPathSelectorForConcrete
1312
import org.utbot.tests.infrastructure.CodeGeneration
13+
import org.utbot.tests.infrastructure.ignoreExecutionsNumber
1414

1515
// TODO failed Kotlin compilation SAT-1332
1616
class MapEntrySetTest : UtValueTestCaseChecker(
@@ -152,7 +152,7 @@ class MapEntrySetTest : UtValueTestCaseChecker(
152152
withPushingStateFromPathSelectorForConcrete {
153153
checkWithException(
154154
MapEntrySet::iterateWithIterator,
155-
eq(6),
155+
ignoreExecutionsNumber,
156156
{ map, result -> map == null && result.isException<NullPointerException>() },
157157
{ map, result -> map.isEmpty() && result.getOrThrow().contentEquals(intArrayOf(0, 0)) },
158158
{ map, result -> map.size % 2 == 1 && result.isException<NoSuchElementException>() },

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

+61
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test
1212
import org.utbot.testcheckers.eq
1313
import org.utbot.testcheckers.ge
1414
import org.utbot.testcheckers.withPushingStateFromPathSelectorForConcrete
15+
import org.utbot.testcheckers.withoutConcrete
1516
import org.utbot.testcheckers.withoutMinimization
1617
import org.utbot.tests.infrastructure.CodeGeneration
1718

@@ -84,6 +85,45 @@ internal class MapsPart1Test : UtValueTestCaseChecker(
8485
)
8586
}
8687

88+
@Test
89+
fun testMapPutAndGet() {
90+
withoutConcrete {
91+
check(
92+
Maps::mapPutAndGet,
93+
eq(1),
94+
{ r -> r == 3 }
95+
)
96+
}
97+
}
98+
99+
@Test
100+
fun testPutInMapFromParameters() {
101+
withoutConcrete {
102+
check(
103+
Maps::putInMapFromParameters,
104+
ignoreExecutionsNumber,
105+
{ values, _ -> values == null },
106+
{ values, r -> 1 in values.keys && r == 3 },
107+
{ values, r -> 1 !in values.keys && r == 3 },
108+
)
109+
}
110+
}
111+
112+
// This test doesn't check anything specific, but the code from MUT
113+
// caused errors with NPE as results while debugging `testPutInMapFromParameters`.
114+
@Test
115+
fun testContainsKeyAndPuts() {
116+
withoutConcrete {
117+
check(
118+
Maps::containsKeyAndPuts,
119+
ignoreExecutionsNumber,
120+
{ values, _ -> values == null },
121+
{ values, r -> 1 !in values.keys && r == 3 },
122+
coverage = DoNotCalculate
123+
)
124+
}
125+
}
126+
87127
@Test
88128
fun testFindAllChars() {
89129
check(
@@ -324,4 +364,25 @@ internal class MapsPart1Test : UtValueTestCaseChecker(
324364
coverage = DoNotCalculate
325365
)
326366
}
367+
368+
@Test
369+
fun testCreateMapWithString() {
370+
withoutConcrete {
371+
check(
372+
Maps::createMapWithString,
373+
eq(1),
374+
{ r -> r!!.isEmpty() }
375+
)
376+
}
377+
}
378+
@Test
379+
fun testCreateMapWithEnum() {
380+
withoutConcrete {
381+
check(
382+
Maps::createMapWithEnum,
383+
eq(1),
384+
{ r -> r != null && r.size == 2 && r[Maps.WorkDays.Monday] == 112 && r[Maps.WorkDays.Friday] == 567 }
385+
)
386+
}
387+
}
327388
}

utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java

+16-3
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ void preconditionCheck() {
102102
parameter(keys);
103103
parameter(keys.storage);
104104

105+
// Following three instructions are required to avoid possible aliasing
106+
// between nested arrays
107+
assume(keys.storage != values.storage);
108+
assume(keys.storage != values.touched);
109+
assume(values.storage != values.touched);
110+
105111
assume(values.size == keys.end);
106112
assume(values.touched.length == keys.end);
107113
doesntThrow();
@@ -205,11 +211,18 @@ public V put(K key, V value) {
205211
if (index == -1) {
206212
oldValue = null;
207213
keys.set(keys.end++, key);
214+
values.store(key, value);
208215
} else {
209-
// newKey equals to oldKey so we can use it instead
210-
oldValue = values.select(key);
216+
K oldKey = keys.get(index);
217+
oldValue = values.select(oldKey);
218+
values.store(oldKey, value);
211219
}
212-
values.store(key, value);
220+
221+
// Avoid optimization here. Despite the fact that old key is equal
222+
// to a new one in case of `index != -1`, it is important to have
223+
// this connection between `index`, `key`, `oldKey` and `value`.
224+
// So, do not rewrite it with `values.store(key, value)` extracted from the if instruction
225+
213226
return oldValue;
214227
}
215228

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

+53-36
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,17 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface {
215215
createArray(addr, valueType, useConcreteType = false)
216216
}
217217

218-
listOf(
219-
MethodResult(
220-
SymbolicSuccess(resultObject),
221-
typeRegistry.typeConstraintToGenericTypeParameter(addr, wrapper.addr, i = TYPE_PARAMETER_INDEX)
222-
.asHardConstraint()
223-
)
224-
)
218+
val typeIndex = wrapper.asWrapperOrNull?.getOperationTypeIndex
219+
?: error("Wrapper was expected, got $wrapper")
220+
val typeConstraint = typeRegistry.typeConstraintToGenericTypeParameter(
221+
addr,
222+
wrapper.addr,
223+
i = typeIndex
224+
).asHardConstraint()
225+
226+
val methodResult = MethodResult(SymbolicSuccess(resultObject), typeConstraint)
227+
228+
listOf(methodResult)
225229
}
226230

227231
@Suppress("UNUSED_PARAMETER")
@@ -418,16 +422,17 @@ class AssociativeArrayWrapper : WrapperInterface {
418422
val addr = UtAddrExpression(value)
419423
val resultObject = createObject(addr, OBJECT_TYPE, useConcreteType = false)
420424

421-
listOf(
422-
MethodResult(
423-
SymbolicSuccess(resultObject),
424-
typeRegistry.typeConstraintToGenericTypeParameter(
425-
addr,
426-
wrapper.addr,
427-
TYPE_PARAMETER_INDEX
428-
).asHardConstraint()
429-
)
430-
)
425+
val typeIndex = wrapper.asWrapperOrNull?.selectOperationTypeIndex
426+
?: error("Wrapper was expected, got $wrapper")
427+
val hardConstraints = typeRegistry.typeConstraintToGenericTypeParameter(
428+
addr,
429+
wrapper.addr,
430+
typeIndex
431+
).asHardConstraint()
432+
433+
val methodResult = MethodResult(SymbolicSuccess(resultObject), hardConstraints)
434+
435+
listOf(methodResult)
431436
}
432437

433438
@Suppress("UNUSED_PARAMETER")
@@ -440,21 +445,31 @@ class AssociativeArrayWrapper : WrapperInterface {
440445
with(traverser) {
441446
val storageValue = getStorageArrayExpression(wrapper).store(parameters[0].addr, parameters[1].addr)
442447
val sizeValue = getIntFieldValue(wrapper, sizeField)
448+
449+
// it is the reason why it's important to use an `oldKey` in `UtHashMap.put` method.
450+
// We navigate in the associative array using only this old address, not a new one.
443451
val touchedValue = getTouchedArrayExpression(wrapper).store(sizeValue, parameters[0].addr)
444-
listOf(
445-
MethodResult(
446-
SymbolicSuccess(voidValue),
447-
memoryUpdates = arrayUpdateWithValue(
448-
getStorageArrayField(wrapper.addr).addr,
449-
OBJECT_TYPE.arrayType,
450-
storageValue
451-
) + arrayUpdateWithValue(
452-
getTouchedArrayField(wrapper.addr).addr,
453-
OBJECT_TYPE.arrayType,
454-
touchedValue,
455-
) + objectUpdate(wrapper, sizeField, Add(sizeValue.toIntValue(), 1.toPrimitiveValue()))
456-
)
452+
val storageArrayAddr = getStorageArrayField(wrapper.addr).addr
453+
val touchedArrayFieldAddr = getTouchedArrayField(wrapper.addr).addr
454+
455+
val storageArrayUpdate = arrayUpdateWithValue(
456+
storageArrayAddr,
457+
OBJECT_TYPE.arrayType,
458+
storageValue
459+
)
460+
461+
val touchedArrayUpdate = arrayUpdateWithValue(
462+
touchedArrayFieldAddr,
463+
OBJECT_TYPE.arrayType,
464+
touchedValue,
457465
)
466+
467+
val sizeUpdate = objectUpdate(wrapper, sizeField, Add(sizeValue.toIntValue(), 1.toPrimitiveValue()))
468+
469+
val memoryUpdates = storageArrayUpdate + touchedArrayUpdate + sizeUpdate
470+
val methodResult = MethodResult(SymbolicSuccess(voidValue), memoryUpdates = memoryUpdates)
471+
472+
listOf(methodResult)
458473
}
459474

460475
override val wrappedMethods: Map<String, MethodSymbolicImplementation> = mapOf(
@@ -480,7 +495,7 @@ class AssociativeArrayWrapper : WrapperInterface {
480495
// construct model values of an array
481496
val touchedValues = UtArrayModel(
482497
resolver.holder.concreteAddr(UtAddrExpression(touchedArrayAddr)),
483-
objectClassId,
498+
objectArrayClassId,
484499
sizeValue,
485500
UtNullModel(objectClassId),
486501
stores = (0 until sizeValue).associateWithTo(mutableMapOf()) { i ->
@@ -504,7 +519,7 @@ class AssociativeArrayWrapper : WrapperInterface {
504519

505520
val storageValues = UtArrayModel(
506521
resolver.holder.concreteAddr(UtAddrExpression(storageArrayAddr)),
507-
objectClassId,
522+
objectArrayClassId,
508523
sizeValue,
509524
UtNullModel(objectClassId),
510525
stores = (0 until sizeValue).associateTo(mutableMapOf()) { i ->
@@ -518,10 +533,12 @@ class AssociativeArrayWrapper : WrapperInterface {
518533
)
519534
})
520535

521-
val model = UtCompositeModel(resolver.holder.concreteAddr(wrapper.addr), associativeArrayId, false)
536+
val model = UtCompositeModel(resolver.holder.concreteAddr(wrapper.addr), associativeArrayId, isMock = false)
537+
522538
model.fields[sizeField.fieldId] = UtPrimitiveModel(sizeValue)
523539
model.fields[touchedField.fieldId] = touchedValues
524540
model.fields[storageField.fieldId] = storageValues
541+
525542
return model
526543
}
527544

@@ -537,7 +554,7 @@ class AssociativeArrayWrapper : WrapperInterface {
537554
private fun Traverser.getStorageArrayExpression(
538555
wrapper: ObjectValue
539556
): UtExpression = selectArrayExpressionFromMemory(getStorageArrayField(wrapper.addr))
540-
}
541557

542-
// Arrays and lists have the only type parameter with index zero
543-
private const val TYPE_PARAMETER_INDEX = 0
558+
override val selectOperationTypeIndex: Int
559+
get() = 1
560+
}

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

+15
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,21 @@ interface WrapperInterface {
244244
}
245245

246246
fun value(resolver: Resolver, wrapper: ObjectValue): UtModel
247+
248+
/**
249+
* It is an index for type parameter corresponding to the result
250+
* value of `select` operation. For example, for arrays and lists it's zero,
251+
* for associative array it's one.
252+
*/
253+
open val selectOperationTypeIndex: Int
254+
get() = 0
255+
256+
/**
257+
* Similar to [selectOperationTypeIndex], it is responsible for type index
258+
* of the returning value from `get` operation
259+
*/
260+
open val getOperationTypeIndex: Int
261+
get() = 0
247262
}
248263

249264
// TODO: perhaps we have to have wrapper around concrete value here

0 commit comments

Comments
 (0)