Skip to content

Commit a1e577e

Browse files
committed
Map don't work without concrete
Fixes: * aliasing between nested arrays of UtHashMap and its fields * unsat for get operation from Map
1 parent 6ce8761 commit a1e577e

File tree

7 files changed

+200
-41
lines changed

7 files changed

+200
-41
lines changed

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

Lines changed: 60 additions & 0 deletions
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,44 @@ 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+
)
123+
}
124+
}
125+
87126
@Test
88127
fun testFindAllChars() {
89128
check(
@@ -324,4 +363,25 @@ internal class MapsPart1Test : UtValueTestCaseChecker(
324363
coverage = DoNotCalculate
325364
)
326365
}
366+
367+
@Test
368+
fun testCreateMapWithString() {
369+
withoutConcrete {
370+
check(
371+
Maps::createMapWithString,
372+
eq(1),
373+
{ r -> r!!.isEmpty() }
374+
)
375+
}
376+
}
377+
@Test
378+
fun testCreateMapWithEnum() {
379+
withoutConcrete {
380+
check(
381+
Maps::createMapWithEnum,
382+
eq(1),
383+
{ r -> r != null && r.size == 2 && r[Maps.WorkDays.Monday] == 112 && r[Maps.WorkDays.Friday] == 567 }
384+
)
385+
}
386+
}
327387
}

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

Lines changed: 16 additions & 3 deletions
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

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,16 @@ 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 ?: error("Unexpected wrapper $wrapper")
219+
val typeConstraint = typeRegistry.typeConstraintToGenericTypeParameter(
220+
addr,
221+
wrapper.addr,
222+
i = typeIndex
223+
).asHardConstraint()
224+
225+
val methodResult = MethodResult(SymbolicSuccess(resultObject), typeConstraint)
226+
227+
listOf(methodResult)
225228
}
226229

227230
@Suppress("UNUSED_PARAMETER")
@@ -418,16 +421,16 @@ class AssociativeArrayWrapper : WrapperInterface {
418421
val addr = UtAddrExpression(value)
419422
val resultObject = createObject(addr, OBJECT_TYPE, useConcreteType = false)
420423

421-
listOf(
422-
MethodResult(
423-
SymbolicSuccess(resultObject),
424-
typeRegistry.typeConstraintToGenericTypeParameter(
425-
addr,
426-
wrapper.addr,
427-
TYPE_PARAMETER_INDEX
428-
).asHardConstraint()
429-
)
430-
)
424+
val typeIndex = wrapper.asWrapperOrNull?.selectOperationTypeIndex ?: error("Unexpected wrapper $wrapper")
425+
val hardConstraints = typeRegistry.typeConstraintToGenericTypeParameter(
426+
addr,
427+
wrapper.addr,
428+
typeIndex
429+
).asHardConstraint()
430+
431+
val methodResult = MethodResult(SymbolicSuccess(resultObject), hardConstraints)
432+
433+
listOf(methodResult)
431434
}
432435

433436
@Suppress("UNUSED_PARAMETER")
@@ -440,21 +443,31 @@ class AssociativeArrayWrapper : WrapperInterface {
440443
with(traverser) {
441444
val storageValue = getStorageArrayExpression(wrapper).store(parameters[0].addr, parameters[1].addr)
442445
val sizeValue = getIntFieldValue(wrapper, sizeField)
446+
447+
// it is the reason why it's important to use an `oldKey` in `UtHashMap.put` method.
448+
// We navigate in the associative array using only this old address, not a new one.
443449
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-
)
450+
val storageArrayAddr = getStorageArrayField(wrapper.addr).addr
451+
val touchedArrayFieldAddr = getTouchedArrayField(wrapper.addr).addr
452+
453+
val storageArrayUpdate = arrayUpdateWithValue(
454+
storageArrayAddr,
455+
OBJECT_TYPE.arrayType,
456+
storageValue
457+
)
458+
459+
val touchedArrayUpdate = arrayUpdateWithValue(
460+
touchedArrayFieldAddr,
461+
OBJECT_TYPE.arrayType,
462+
touchedValue,
457463
)
464+
465+
val sizeUpdate = objectUpdate(wrapper, sizeField, Add(sizeValue.toIntValue(), 1.toPrimitiveValue()))
466+
467+
val memoryUpdates = storageArrayUpdate + touchedArrayUpdate + sizeUpdate
468+
val methodResult = MethodResult(SymbolicSuccess(voidValue), memoryUpdates = memoryUpdates)
469+
470+
listOf(methodResult)
458471
}
459472

460473
override val wrappedMethods: Map<String, MethodSymbolicImplementation> = mapOf(
@@ -480,7 +493,7 @@ class AssociativeArrayWrapper : WrapperInterface {
480493
// construct model values of an array
481494
val touchedValues = UtArrayModel(
482495
resolver.holder.concreteAddr(UtAddrExpression(touchedArrayAddr)),
483-
objectClassId,
496+
objectArrayClassId,
484497
sizeValue,
485498
UtNullModel(objectClassId),
486499
stores = (0 until sizeValue).associateWithTo(mutableMapOf()) { i ->
@@ -504,7 +517,7 @@ class AssociativeArrayWrapper : WrapperInterface {
504517

505518
val storageValues = UtArrayModel(
506519
resolver.holder.concreteAddr(UtAddrExpression(storageArrayAddr)),
507-
objectClassId,
520+
objectArrayClassId,
508521
sizeValue,
509522
UtNullModel(objectClassId),
510523
stores = (0 until sizeValue).associateTo(mutableMapOf()) { i ->
@@ -518,10 +531,12 @@ class AssociativeArrayWrapper : WrapperInterface {
518531
)
519532
})
520533

521-
val model = UtCompositeModel(resolver.holder.concreteAddr(wrapper.addr), associativeArrayId, false)
534+
val model = UtCompositeModel(resolver.holder.concreteAddr(wrapper.addr), associativeArrayId, isMock = false)
535+
522536
model.fields[sizeField.fieldId] = UtPrimitiveModel(sizeValue)
523537
model.fields[touchedField.fieldId] = touchedValues
524538
model.fields[storageField.fieldId] = storageValues
539+
525540
return model
526541
}
527542

@@ -537,7 +552,7 @@ class AssociativeArrayWrapper : WrapperInterface {
537552
private fun Traverser.getStorageArrayExpression(
538553
wrapper: ObjectValue
539554
): UtExpression = selectArrayExpressionFromMemory(getStorageArrayField(wrapper.addr))
540-
}
541555

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

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

Lines changed: 15 additions & 0 deletions
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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3534,7 +3534,7 @@ class Traverser(
35343534

35353535
// toplevel method
35363536
// TODO: investigate very strange behavior when some constraints are not added leading to failing CodegenExampleTest::firstExampleTest fails
3537-
val terminalExecutionState = environment.state.copy(
3537+
val terminalExecutionState = environment.state.copy(
35383538
symbolicState = symbolicState,
35393539
methodResult = methodResult, // the way to put SymbolicResult into terminal state
35403540
label = StateLabel.TERMINAL

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ class UtIsGenericTypeExpression(
455455
override fun <TResult> accept(visitor: UtExpressionVisitor<TResult>): TResult = visitor.visit(this)
456456

457457
override fun toString(): String {
458-
return "(generic-is $addr $baseAddr<\$$parameterTypeIndex>)"
458+
return "(generic-is $addr baseAddr<ParamTypeIndex>: $baseAddr<\$$parameterTypeIndex>)"
459459
}
460460

461461
override fun equals(other: Any?): Boolean {

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

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.utbot.examples.collections;
22

3+
import org.utbot.api.mock.UtMock;
4+
35
import java.util.ArrayList;
46
import java.util.HashMap;
57
import java.util.LinkedHashMap;
@@ -25,6 +27,35 @@ String mapToString(long startTime, int pageSize, int pageNum) {
2527
return params.toString();
2628
}
2729

30+
Integer mapPutAndGet() {
31+
Map<Long, Integer> values = new HashMap<>();
32+
33+
values.put(1L, 2);
34+
values.put(1L, 3);
35+
36+
return values.get(1L);
37+
}
38+
39+
@SuppressWarnings("OverwrittenKey")
40+
Integer putInMapFromParameters(Map<Long, Integer> values) {
41+
values.put(1L, 2);
42+
values.put(1L, 3);
43+
44+
return values.get(1L);
45+
}
46+
47+
@SuppressWarnings("OverwrittenKey")
48+
Integer containsKeyAndPuts(Map<Long, Integer> values) {
49+
UtMock.assume(!values.containsKey(1L));
50+
51+
values.put(1L, 2);
52+
values.put(1L, 3);
53+
54+
UtMock.assume(values.get(1L).equals(3));
55+
56+
return values.get(1L);
57+
}
58+
2859
Map<Character, Integer> countChars(String s) {
2960
Map<Character, Integer> map = new LinkedHashMap<>();
3061
for (int i = 0; i < s.length(); i++) {
@@ -261,4 +292,29 @@ public List<String> mapOperator(Map<String, String> map) {
261292
return new ArrayList<>(map.values());
262293
}
263294
}
295+
296+
public Map<String, Integer> createMapWithString() {
297+
Map<String, Integer> map = new HashMap<>();
298+
map.put("tuesday", 354);
299+
map.remove("tuesday");
300+
301+
return map;
302+
}
303+
public Map<WorkDays, Integer> createMapWithEnum() {
304+
Map<WorkDays, Integer> map = new HashMap<>();
305+
map.put(WorkDays.Monday, 112);
306+
map.put(WorkDays.Tuesday, 354);
307+
map.put(WorkDays.Friday, 567);
308+
map.remove(WorkDays.Tuesday);
309+
310+
return map;
311+
}
312+
313+
public enum WorkDays {
314+
Monday,
315+
Tuesday,
316+
Wednesday,
317+
Thursday,
318+
Friday
319+
}
264320
}

0 commit comments

Comments
 (0)