Skip to content

Commit 31713eb

Browse files
authored
Add support for generic information retrieved from signatures of nested methods (#1619)
1 parent a018c85 commit 31713eb

File tree

8 files changed

+312
-74
lines changed

8 files changed

+312
-74
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.utbot.examples.types
2+
3+
import org.junit.jupiter.api.Disabled
4+
import org.junit.jupiter.api.Test
5+
import org.utbot.testcheckers.eq
6+
import org.utbot.testing.UtValueTestCaseChecker
7+
import org.utbot.testing.ignoreExecutionsNumber
8+
9+
internal class GenericsTest : UtValueTestCaseChecker(
10+
testClass = GenericsTest::class,
11+
testCodeGeneration = false // TODO empty files are generated https://github.com/UnitTestBot/UTBotJava/issues/1616
12+
) {
13+
@Test
14+
fun mapAsParameterTest() {
15+
check(
16+
Generics::mapAsParameter,
17+
eq(2),
18+
{ map, _ -> map == null },
19+
{ map, r -> map != null && r == "value" },
20+
)
21+
}
22+
23+
@Test
24+
@Disabled("https://github.com/UnitTestBot/UTBotJava/issues/1620 wrong equals")
25+
fun genericAsFieldTest() {
26+
check(
27+
Generics::genericAsField,
28+
ignoreExecutionsNumber,
29+
{ obj, r -> obj?.field == null && r == false },
30+
// we can cover this line with any of these two conditions
31+
{ obj, r -> (obj.field != null && obj.field != "abc" && r == false) || (obj.field == "abc" && r == true) },
32+
)
33+
}
34+
35+
@Test
36+
fun mapAsStaticFieldTest() {
37+
check(
38+
Generics::mapAsStaticField,
39+
ignoreExecutionsNumber,
40+
{ r -> r == "value" },
41+
)
42+
}
43+
44+
@Test
45+
fun mapAsNonStaticFieldTest() {
46+
check(
47+
Generics::mapAsNonStaticField,
48+
ignoreExecutionsNumber,
49+
{ map, _ -> map == null },
50+
{ map, r -> map != null && r == "value" },
51+
)
52+
}
53+
54+
@Test
55+
fun methodWithRawTypeTest() {
56+
check(
57+
Generics::methodWithRawType,
58+
eq(2),
59+
{ map, _ -> map == null },
60+
{ map, r -> map != null && r == "value" },
61+
)
62+
}
63+
}

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,7 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface {
202202

203203
// Try to retrieve manually set type if present
204204
val valueType = typeRegistry
205-
.getTypeStoragesForObjectTypeParameters(wrapper.addr)
206-
?.singleOrNull()
205+
.extractSingleTypeParameterForRangeModifiableArray(wrapper.addr)
207206
?.leastCommonType
208207
?: OBJECT_TYPE
209208

@@ -343,8 +342,10 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface {
343342
resolver.addConstructedModel(concreteAddr, resultModel)
344343

345344
// try to retrieve type storage for the single type parameter
346-
val typeStorage =
347-
resolver.typeRegistry.getTypeStoragesForObjectTypeParameters(wrapper.addr)?.singleOrNull() ?: TypeRegistry.objectTypeStorage
345+
val typeStorage = resolver
346+
.typeRegistry
347+
.extractSingleTypeParameterForRangeModifiableArray(wrapper.addr)
348+
?: TypeRegistry.objectTypeStorage
348349

349350
(0 until sizeValue).associateWithTo(resultModel.stores) { i ->
350351
val addr = UtAddrExpression(arrayExpression.select(mkInt(i + firstValue)))
@@ -361,6 +362,9 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface {
361362
return resultModel
362363
}
363364

365+
private fun TypeRegistry.extractSingleTypeParameterForRangeModifiableArray(addr: UtAddrExpression) =
366+
extractTypeStorageForObjectWithSingleTypeParameter(addr, "Range modifiable array")
367+
364368
companion object {
365369
internal val rangeModifiableArrayClass: SootClass
366370
get() = Scene.v().getSootClass(rangeModifiableArrayId.name)

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

Lines changed: 132 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ import org.utbot.framework.plugin.api.MethodId
119119
import org.utbot.framework.plugin.api.classId
120120
import org.utbot.framework.plugin.api.id
121121
import org.utbot.framework.plugin.api.util.executable
122+
import org.utbot.framework.plugin.api.util.findFieldByIdOrNull
122123
import org.utbot.framework.plugin.api.util.jField
123124
import org.utbot.framework.plugin.api.util.jClass
124125
import org.utbot.framework.plugin.api.util.id
@@ -256,8 +257,11 @@ class Traverser(
256257

257258
/**
258259
* Contains information about the generic types used in the parameters of the method under test.
260+
*
261+
* Mutable set here is required since this object might be passed into several methods
262+
* and get several piece of information about their parameterized types
259263
*/
260-
private val parameterAddrToGenericType = mutableMapOf<UtAddrExpression, ParameterizedType>()
264+
private val instanceAddrToGenericType = mutableMapOf<UtAddrExpression, MutableSet<ParameterizedType>>()
261265

262266
private val preferredCexInstanceCache = mutableMapOf<ObjectValue, MutableSet<SootField>>()
263267

@@ -1036,7 +1040,8 @@ class Traverser(
10361040

10371041
if (createdValue is ReferenceValue) {
10381042
// Update generic type info for method under test' parameters
1039-
updateGenericTypeInfo(identityRef, createdValue)
1043+
val index = (identityRef as? ParameterRef)?.index?.plus(1) ?: 0
1044+
updateGenericTypeInfoFromMethod(methodUnderTest, createdValue, index)
10401045

10411046
if (isNonNullable) {
10421047
queuedSymbolicStateUpdates += mkNot(
@@ -1087,81 +1092,94 @@ class Traverser(
10871092
return UtMockInfoGenerator { mockAddr -> UtObjectMockInfo(type.id, mockAddr) }
10881093
}
10891094

1095+
private fun updateGenericTypeInfoFromMethod(method: ExecutableId, value: ReferenceValue, parameterIndex: Int) {
1096+
val type = extractParameterizedType(method, parameterIndex) as? ParameterizedType ?: return
1097+
1098+
updateGenericTypeInfo(type, value)
1099+
}
1100+
10901101
/**
10911102
* Stores information about the generic types used in the parameters of the method under test.
10921103
*/
1093-
private fun updateGenericTypeInfo(identityRef: IdentityRef, value: ReferenceValue) {
1104+
private fun updateGenericTypeInfo(type: ParameterizedType, value: ReferenceValue) {
1105+
val typeStorages = type.actualTypeArguments.map { actualTypeArgument ->
1106+
when (actualTypeArgument) {
1107+
is WildcardType -> {
1108+
val upperBounds = actualTypeArgument.upperBounds
1109+
val lowerBounds = actualTypeArgument.lowerBounds
1110+
val allTypes = upperBounds + lowerBounds
1111+
1112+
if (allTypes.any { it is GenericArrayType }) {
1113+
val errorTypes = allTypes.filterIsInstance<GenericArrayType>()
1114+
TODO("we do not support GenericArrayTypeImpl yet, and $errorTypes found. SAT-1446")
1115+
}
1116+
1117+
val upperBoundsTypes = typeResolver.intersectInheritors(upperBounds)
1118+
val lowerBoundsTypes = typeResolver.intersectAncestors(lowerBounds)
1119+
1120+
typeResolver.constructTypeStorage(OBJECT_TYPE, upperBoundsTypes.intersect(lowerBoundsTypes))
1121+
}
1122+
is TypeVariable<*> -> { // it is a type variable for the whole class, not the function type variable
1123+
val upperBounds = actualTypeArgument.bounds
1124+
1125+
if (upperBounds.any { it is GenericArrayType }) {
1126+
val errorTypes = upperBounds.filterIsInstance<GenericArrayType>()
1127+
TODO("we do not support GenericArrayType yet, and $errorTypes found. SAT-1446")
1128+
}
1129+
1130+
val upperBoundsTypes = typeResolver.intersectInheritors(upperBounds)
1131+
1132+
typeResolver.constructTypeStorage(OBJECT_TYPE, upperBoundsTypes)
1133+
}
1134+
is GenericArrayType -> {
1135+
// TODO bug with T[][], because there is no such time T JIRA:1446
1136+
typeResolver.constructTypeStorage(OBJECT_TYPE, useConcreteType = false)
1137+
}
1138+
is ParameterizedType, is Class<*> -> {
1139+
val sootType = Scene.v().getType(actualTypeArgument.rawType.typeName)
1140+
1141+
typeResolver.constructTypeStorage(sootType, useConcreteType = false)
1142+
}
1143+
else -> error("Unsupported argument type ${actualTypeArgument::class}")
1144+
}
1145+
}
1146+
1147+
queuedSymbolicStateUpdates += typeRegistry
1148+
.genericTypeParameterConstraint(value.addr, typeStorages)
1149+
.asHardConstraint()
1150+
1151+
instanceAddrToGenericType.getOrPut(value.addr) { mutableSetOf() }.add(type)
1152+
1153+
typeRegistry.saveObjectParameterTypeStorages(value.addr, typeStorages)
1154+
}
1155+
1156+
private fun extractParameterizedType(
1157+
method: ExecutableId,
1158+
index: Int
1159+
): java.lang.reflect.Type? {
10941160
// If we don't have access to methodUnderTest's jClass, the engine should not fail
10951161
// We just won't update generic information for it
1096-
val callable = runCatching { methodUnderTest.executable }.getOrNull() ?: return
1162+
val callable = runCatching { method.executable }.getOrNull() ?: return null
10971163

1098-
val type = if (identityRef is ThisRef) {
1164+
val type = if (index == 0) {
10991165
// TODO: for ThisRef both methods don't return parameterized type
1100-
if (methodUnderTest.isConstructor) {
1166+
if (method.isConstructor) {
11011167
callable.annotatedReturnType?.type
11021168
} else {
11031169
callable.declaringClass // same as it was, but it isn't parametrized type
1104-
?: error("No instanceParameter for ${callable} found")
1170+
?: error("No instanceParameter for $callable found")
11051171
}
11061172
} else {
11071173
// Sometimes out of bound exception occurred here, e.g., com.alibaba.fescar.core.model.GlobalStatus.<init>
11081174
workaround(HACK) {
1109-
val index = (identityRef as ParameterRef).index
11101175
val valueParameters = callable.genericParameterTypes
11111176

1112-
if (index > valueParameters.lastIndex) return
1113-
valueParameters[index]
1177+
if (index - 1 > valueParameters.lastIndex) return null
1178+
valueParameters[index - 1]
11141179
}
11151180
}
11161181

1117-
if (type is ParameterizedType) {
1118-
val typeStorages = type.actualTypeArguments.map { actualTypeArgument ->
1119-
when (actualTypeArgument) {
1120-
is WildcardType -> {
1121-
val upperBounds = actualTypeArgument.upperBounds
1122-
val lowerBounds = actualTypeArgument.lowerBounds
1123-
val allTypes = upperBounds + lowerBounds
1124-
1125-
if (allTypes.any { it is GenericArrayType }) {
1126-
val errorTypes = allTypes.filterIsInstance<GenericArrayType>()
1127-
TODO("we do not support GenericArrayTypeImpl yet, and $errorTypes found. SAT-1446")
1128-
}
1129-
1130-
val upperBoundsTypes = typeResolver.intersectInheritors(upperBounds)
1131-
val lowerBoundsTypes = typeResolver.intersectAncestors(lowerBounds)
1132-
1133-
typeResolver.constructTypeStorage(OBJECT_TYPE, upperBoundsTypes.intersect(lowerBoundsTypes))
1134-
}
1135-
is TypeVariable<*> -> { // it is a type variable for the whole class, not the function type variable
1136-
val upperBounds = actualTypeArgument.bounds
1137-
1138-
if (upperBounds.any { it is GenericArrayType }) {
1139-
val errorTypes = upperBounds.filterIsInstance<GenericArrayType>()
1140-
TODO("we do not support GenericArrayType yet, and $errorTypes found. SAT-1446")
1141-
}
1142-
1143-
val upperBoundsTypes = typeResolver.intersectInheritors(upperBounds)
1144-
1145-
typeResolver.constructTypeStorage(OBJECT_TYPE, upperBoundsTypes)
1146-
}
1147-
is GenericArrayType -> {
1148-
// TODO bug with T[][], because there is no such time T JIRA:1446
1149-
typeResolver.constructTypeStorage(OBJECT_TYPE, useConcreteType = false)
1150-
}
1151-
is ParameterizedType, is Class<*> -> {
1152-
val sootType = Scene.v().getType(actualTypeArgument.rawType.typeName)
1153-
1154-
typeResolver.constructTypeStorage(sootType, useConcreteType = false)
1155-
}
1156-
else -> error("Unsupported argument type ${actualTypeArgument::class}")
1157-
}
1158-
}
1159-
1160-
queuedSymbolicStateUpdates += typeRegistry.genericTypeParameterConstraint(value.addr, typeStorages).asHardConstraint()
1161-
parameterAddrToGenericType += value.addr to type
1162-
1163-
typeRegistry.saveObjectParameterTypeStorages(value.addr, typeStorages)
1164-
}
1182+
return type
11651183
}
11661184

11671185
private fun TraversalContext.traverseIfStmt(current: JIfStmt) {
@@ -2084,9 +2102,30 @@ class Traverser(
20842102
checkAndMarkLibraryFieldSpeculativelyNotNull(field, createdField)
20852103
}
20862104

2105+
updateGenericInfoForField(createdField, field)
2106+
20872107
return createdField
20882108
}
20892109

2110+
/**
2111+
* Updates generic info for provided [field] and [createdField] using
2112+
* type information. If [createdField] is not a reference value or
2113+
* if field's type is not a parameterized one, nothing will happen.
2114+
*/
2115+
private fun updateGenericInfoForField(createdField: SymbolicValue, field: SootField) {
2116+
runCatching {
2117+
if (createdField !is ReferenceValue) return
2118+
2119+
// We must have `runCatching` here since might be a situation when we do not have
2120+
// such declaring class in a classpath, that might (but should not) lead to an exception
2121+
val classId = field.declaringClass.id
2122+
val requiredField = classId.findFieldByIdOrNull(field.fieldId)
2123+
val genericInfo = requiredField?.genericType as? ParameterizedType ?: return
2124+
2125+
updateGenericTypeInfo(genericInfo, createdField)
2126+
}
2127+
}
2128+
20902129
/**
20912130
* Marks the [createdField] as speculatively not null if the [field] is considering as
20922131
* not producing [NullPointerException].
@@ -2509,17 +2548,28 @@ class Traverser(
25092548
): List<InvocationTarget> {
25102549
val visitor = solver.simplificator.axiomInstantiationVisitor
25112550
val simplifiedAddr = instance.addr.accept(visitor)
2551+
25122552
// UtIsExpression for object with address the same as instance.addr
2513-
val instanceOfConstraint = solver.assertions.singleOrNull {
2514-
it is UtIsExpression && it.addr == simplifiedAddr
2515-
} as? UtIsExpression
2553+
// If there are several such constraints, take the one with the least number of possible types
2554+
val instanceOfConstraint = solver.assertions
2555+
.filter { it is UtIsExpression && it.addr == simplifiedAddr }
2556+
.takeIf { it.isNotEmpty() }
2557+
?.minBy { (it as UtIsExpression).typeStorage.possibleConcreteTypes.size } as? UtIsExpression
2558+
25162559
// if we have UtIsExpression constraint for [instance], then find invocation targets
25172560
// for possibleTypes from this constraints, instead of the type maintained by solver.
25182561

25192562
// While using simplifications with RewritingVisitor, assertions can maintain types
25202563
// for objects (especially objects with type equals to type parameter of generic)
25212564
// better than engine.
2522-
val types = instanceOfConstraint?.typeStorage?.possibleConcreteTypes ?: instance.possibleConcreteTypes
2565+
val types = instanceOfConstraint
2566+
?.typeStorage
2567+
?.possibleConcreteTypes
2568+
// we should take this constraint into consideration only if it has less
2569+
// possible types than our current object, otherwise, it doesn't add
2570+
// any helpful information
2571+
?.takeIf { it.size < instance.possibleConcreteTypes.size }
2572+
?: instance.possibleConcreteTypes
25232573

25242574
val allPossibleConcreteTypes = typeResolver
25252575
.constructTypeStorage(instance.type, useConcreteType = false)
@@ -2650,6 +2700,22 @@ class Traverser(
26502700
* Returns results of native calls cause other calls push changes directly to path selector.
26512701
*/
26522702
private fun TraversalContext.commonInvokePart(invocation: Invocation): List<MethodResult> {
2703+
val method = invocation.method.executableId
2704+
2705+
// This code is supposed to support generic information from signatures for nested methods.
2706+
// If we have some method 'foo` and a method `bar(List<Integer>), and inside `foo`
2707+
// there is an invocation `bar(object)`, this object must have information about
2708+
// its `Integer` generic type.
2709+
invocation.parameters.forEachIndexed { index, param ->
2710+
if (param !is ReferenceValue) return@forEachIndexed
2711+
2712+
updateGenericTypeInfoFromMethod(method, param, parameterIndex = index + 1)
2713+
}
2714+
2715+
if (invocation.instance != null) {
2716+
updateGenericTypeInfoFromMethod(method, invocation.instance, parameterIndex = 0)
2717+
}
2718+
26532719
/**
26542720
* First, check if there is override for the invocation itself, not for the targets.
26552721
*
@@ -3469,16 +3535,13 @@ class Traverser(
34693535
if (baseTypeAfterCast is RefType) {
34703536
// Find parameterized type for the object if it is a parameter of the method under test and it has generic type
34713537
val newAddr = addr.accept(solver.simplificator) as UtAddrExpression
3472-
val parameterizedType = when (newAddr.internal) {
3473-
is UtArraySelectExpression -> parameterAddrToGenericType[findTheMostNestedAddr(newAddr.internal)]
3474-
is UtBvConst -> parameterAddrToGenericType[newAddr]
3538+
val parameterizedTypes = when (newAddr.internal) {
3539+
is UtArraySelectExpression -> instanceAddrToGenericType[findTheMostNestedAddr(newAddr.internal)]
3540+
is UtBvConst -> instanceAddrToGenericType[newAddr]
34753541
else -> null
34763542
}
34773543

3478-
if (parameterizedType != null) {
3479-
// Find all generics used in the type of the parameter and it's superclasses
3480-
// If we're trying to cast something related to the parameter and typeAfterCast is equal to one of the generic
3481-
// types used in it, don't throw ClassCastException
3544+
parameterizedTypes?.forEach { parameterizedType ->
34823545
val genericTypes = generateSequence(parameterizedType) { it.ownerType as? ParameterizedType }
34833546
.flatMapTo(mutableSetOf()) { it.actualTypeArguments.map { arg -> arg.typeName } }
34843547

0 commit comments

Comments
 (0)