Skip to content

Map doesn't work without concrete #1228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1297,7 +1297,8 @@ enum class CodegenLanguage(
JAVA -> listOf(
"-d", buildDirectory,
"-cp", classPath,
"-XDignore.symbol.file" // to let javac use classes from rt.jar
"-XDignore.symbol.file", // to let javac use classes from rt.jar
"--add-exports", "java.base/sun.reflect.generics.repository=ALL-UNNAMED"
).plus(sourcesFiles)

KOTLIN -> listOf("-d", buildDirectory, "-jvm-target", jvmTarget, "-cp", classPath).plus(sourcesFiles)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,16 @@ internal class ArrayOfObjectsTest : UtValueTestCaseChecker(

@Test
fun testArrayOfArrays() {
check(
ArrayOfObjects::arrayOfArrays,
between(4..5), // might be two ClassCastExceptions
{ a, _ -> a.any { it == null } },
{ a, _ -> a.any { it != null && it !is IntArray } },
{ a, r -> (a.all { it != null && it is IntArray && it.isEmpty() } || a.isEmpty()) && r == 0 },
{ a, r -> a.all { it is IntArray } && r == a.sumBy { (it as IntArray).sum() } },
coverage = DoNotCalculate
)
withEnabledTestingCodeGeneration(testCodeGeneration = false) {
check(
ArrayOfObjects::arrayOfArrays,
between(4..5), // might be two ClassCastExceptions
{ a, _ -> a.any { it == null } },
{ a, _ -> a.any { it != null && it !is IntArray } },
{ a, r -> (a.all { it != null && it is IntArray && it.isEmpty() } || a.isEmpty()) && r == 0 },
{ a, r -> a.all { it is IntArray } && r == a.sumBy { (it as IntArray).sum() } },
coverage = DoNotCalculate
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import org.utbot.tests.infrastructure.isException
import org.utbot.framework.plugin.api.CodegenLanguage
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.utbot.testcheckers.eq
import org.utbot.testcheckers.ge
import org.utbot.testcheckers.withPushingStateFromPathSelectorForConcrete
import org.utbot.tests.infrastructure.CodeGeneration
import org.utbot.tests.infrastructure.ignoreExecutionsNumber

// TODO failed Kotlin compilation SAT-1332
class MapEntrySetTest : UtValueTestCaseChecker(
Expand Down Expand Up @@ -152,7 +152,7 @@ class MapEntrySetTest : UtValueTestCaseChecker(
withPushingStateFromPathSelectorForConcrete {
checkWithException(
MapEntrySet::iterateWithIterator,
eq(6),
ignoreExecutionsNumber,
{ map, result -> map == null && result.isException<NullPointerException>() },
{ map, result -> map.isEmpty() && result.getOrThrow().contentEquals(intArrayOf(0, 0)) },
{ map, result -> map.size % 2 == 1 && result.isException<NoSuchElementException>() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test
import org.utbot.testcheckers.eq
import org.utbot.testcheckers.ge
import org.utbot.testcheckers.withPushingStateFromPathSelectorForConcrete
import org.utbot.testcheckers.withoutConcrete
import org.utbot.testcheckers.withoutMinimization
import org.utbot.tests.infrastructure.CodeGeneration

Expand Down Expand Up @@ -84,6 +85,45 @@ internal class MapsPart1Test : UtValueTestCaseChecker(
)
}

@Test
fun testMapPutAndGet() {
withoutConcrete {
check(
Maps::mapPutAndGet,
eq(1),
{ r -> r == 3 }
)
}
}

@Test
fun testPutInMapFromParameters() {
withoutConcrete {
check(
Maps::putInMapFromParameters,
ignoreExecutionsNumber,
{ values, _ -> values == null },
{ values, r -> 1 in values.keys && r == 3 },
{ values, r -> 1 !in values.keys && r == 3 },
)
}
}

// This test doesn't check anything specific, but the code from MUT
// caused errors with NPE as results while debugging `testPutInMapFromParameters`.
@Test
fun testContainsKeyAndPuts() {
withoutConcrete {
check(
Maps::containsKeyAndPuts,
ignoreExecutionsNumber,
{ values, _ -> values == null },
{ values, r -> 1 !in values.keys && r == 3 },
coverage = DoNotCalculate
)
}
}

@Test
fun testFindAllChars() {
check(
Expand Down Expand Up @@ -324,4 +364,25 @@ internal class MapsPart1Test : UtValueTestCaseChecker(
coverage = DoNotCalculate
)
}

@Test
fun testCreateMapWithString() {
withoutConcrete {
check(
Maps::createMapWithString,
eq(1),
{ r -> r!!.isEmpty() }
)
}
}
@Test
fun testCreateMapWithEnum() {
withoutConcrete {
check(
Maps::createMapWithEnum,
eq(1),
{ r -> r != null && r.size == 2 && r[Maps.WorkDays.Monday] == 112 && r[Maps.WorkDays.Friday] == 567 }
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ void preconditionCheck() {
parameter(keys);
parameter(keys.storage);

// Following three instructions are required to avoid possible aliasing
// between nested arrays
assume(keys.storage != values.storage);
assume(keys.storage != values.touched);
assume(values.storage != values.touched);

assume(values.size == keys.end);
assume(values.touched.length == keys.end);
doesntThrow();
Expand Down Expand Up @@ -205,11 +211,18 @@ public V put(K key, V value) {
if (index == -1) {
oldValue = null;
keys.set(keys.end++, key);
values.store(key, value);
} else {
// newKey equals to oldKey so we can use it instead
oldValue = values.select(key);
K oldKey = keys.get(index);
oldValue = values.select(oldKey);
values.store(oldKey, value);
}
values.store(key, value);

// Avoid optimization here. Despite the fact that old key is equal
// to a new one in case of `index != -1`, it is important to have
// this connection between `index`, `key`, `oldKey` and `value`.
// So, do not rewrite it with `values.store(key, value)` extracted from the if instruction

return oldValue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,17 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface {
createArray(addr, valueType, useConcreteType = false)
}

listOf(
MethodResult(
SymbolicSuccess(resultObject),
typeRegistry.typeConstraintToGenericTypeParameter(addr, wrapper.addr, i = TYPE_PARAMETER_INDEX)
.asHardConstraint()
)
)
val typeIndex = wrapper.asWrapperOrNull?.getOperationTypeIndex
?: error("Wrapper was expected, got $wrapper")
val typeConstraint = typeRegistry.typeConstraintToGenericTypeParameter(
addr,
wrapper.addr,
i = typeIndex
).asHardConstraint()

val methodResult = MethodResult(SymbolicSuccess(resultObject), typeConstraint)

listOf(methodResult)
}

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

listOf(
MethodResult(
SymbolicSuccess(resultObject),
typeRegistry.typeConstraintToGenericTypeParameter(
addr,
wrapper.addr,
TYPE_PARAMETER_INDEX
).asHardConstraint()
)
)
val typeIndex = wrapper.asWrapperOrNull?.selectOperationTypeIndex
?: error("Wrapper was expected, got $wrapper")
val hardConstraints = typeRegistry.typeConstraintToGenericTypeParameter(
addr,
wrapper.addr,
typeIndex
).asHardConstraint()

val methodResult = MethodResult(SymbolicSuccess(resultObject), hardConstraints)

listOf(methodResult)
}

@Suppress("UNUSED_PARAMETER")
Expand All @@ -440,21 +445,31 @@ class AssociativeArrayWrapper : WrapperInterface {
with(traverser) {
val storageValue = getStorageArrayExpression(wrapper).store(parameters[0].addr, parameters[1].addr)
val sizeValue = getIntFieldValue(wrapper, sizeField)

// it is the reason why it's important to use an `oldKey` in `UtHashMap.put` method.
// We navigate in the associative array using only this old address, not a new one.
val touchedValue = getTouchedArrayExpression(wrapper).store(sizeValue, parameters[0].addr)
listOf(
MethodResult(
SymbolicSuccess(voidValue),
memoryUpdates = arrayUpdateWithValue(
getStorageArrayField(wrapper.addr).addr,
OBJECT_TYPE.arrayType,
storageValue
) + arrayUpdateWithValue(
getTouchedArrayField(wrapper.addr).addr,
OBJECT_TYPE.arrayType,
touchedValue,
) + objectUpdate(wrapper, sizeField, Add(sizeValue.toIntValue(), 1.toPrimitiveValue()))
)
val storageArrayAddr = getStorageArrayField(wrapper.addr).addr
val touchedArrayFieldAddr = getTouchedArrayField(wrapper.addr).addr

val storageArrayUpdate = arrayUpdateWithValue(
storageArrayAddr,
OBJECT_TYPE.arrayType,
storageValue
)

val touchedArrayUpdate = arrayUpdateWithValue(
touchedArrayFieldAddr,
OBJECT_TYPE.arrayType,
touchedValue,
)

val sizeUpdate = objectUpdate(wrapper, sizeField, Add(sizeValue.toIntValue(), 1.toPrimitiveValue()))

val memoryUpdates = storageArrayUpdate + touchedArrayUpdate + sizeUpdate
val methodResult = MethodResult(SymbolicSuccess(voidValue), memoryUpdates = memoryUpdates)

listOf(methodResult)
}

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

val storageValues = UtArrayModel(
resolver.holder.concreteAddr(UtAddrExpression(storageArrayAddr)),
objectClassId,
objectArrayClassId,
sizeValue,
UtNullModel(objectClassId),
stores = (0 until sizeValue).associateTo(mutableMapOf()) { i ->
Expand All @@ -518,10 +533,12 @@ class AssociativeArrayWrapper : WrapperInterface {
)
})

val model = UtCompositeModel(resolver.holder.concreteAddr(wrapper.addr), associativeArrayId, false)
val model = UtCompositeModel(resolver.holder.concreteAddr(wrapper.addr), associativeArrayId, isMock = false)

model.fields[sizeField.fieldId] = UtPrimitiveModel(sizeValue)
model.fields[touchedField.fieldId] = touchedValues
model.fields[storageField.fieldId] = storageValues

return model
}

Expand All @@ -537,7 +554,7 @@ class AssociativeArrayWrapper : WrapperInterface {
private fun Traverser.getStorageArrayExpression(
wrapper: ObjectValue
): UtExpression = selectArrayExpressionFromMemory(getStorageArrayField(wrapper.addr))
}

// Arrays and lists have the only type parameter with index zero
private const val TYPE_PARAMETER_INDEX = 0
override val selectOperationTypeIndex: Int
get() = 1
}
15 changes: 15 additions & 0 deletions utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,21 @@ interface WrapperInterface {
}

fun value(resolver: Resolver, wrapper: ObjectValue): UtModel

/**
* It is an index for type parameter corresponding to the result
* value of `select` operation. For example, for arrays and lists it's zero,
* for associative array it's one.
*/
open val selectOperationTypeIndex: Int
get() = 0

/**
* Similar to [selectOperationTypeIndex], it is responsible for type index
* of the returning value from `get` operation
*/
open val getOperationTypeIndex: Int
get() = 0
}

// TODO: perhaps we have to have wrapper around concrete value here
Expand Down
Loading