Skip to content

Commit 20c95ff

Browse files
committed
Add support for generic information of nested methods
1 parent b6c89b8 commit 20c95ff

File tree

8 files changed

+304
-72
lines changed

8 files changed

+304
-72
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: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,14 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface {
201201
val addr = UtAddrExpression(value)
202202

203203
// Try to retrieve manually set type if present
204-
val valueType = typeRegistry
205-
.getTypeStoragesForObjectTypeParameters(wrapper.addr)
206-
?.singleOrNull()
204+
val valueTypeFromGenerics = typeRegistry.getTypeStoragesForObjectTypeParameters(wrapper.addr)
205+
206+
if (valueTypeFromGenerics != null && valueTypeFromGenerics.size > 1) {
207+
error("Range modifiable wrapper must have only one type parameter, but it got ${valueTypeFromGenerics.size}")
208+
}
209+
210+
val valueType = valueTypeFromGenerics
211+
?.single()
207212
?.leastCommonType
208213
?: OBJECT_TYPE
209214

@@ -342,9 +347,16 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface {
342347
// the constructed model to avoid infinite recursion below
343348
resolver.addConstructedModel(concreteAddr, resultModel)
344349

350+
val valueTypeStorageFromGenerics = resolver.typeRegistry.getTypeStoragesForObjectTypeParameters(wrapper.addr)
351+
352+
if (valueTypeStorageFromGenerics != null && valueTypeStorageFromGenerics.size > 1) {
353+
error("Range modifiable wrapper must have only one type parameter, but it got ${valueTypeStorageFromGenerics.size}")
354+
}
355+
345356
// try to retrieve type storage for the single type parameter
346-
val typeStorage =
347-
resolver.typeRegistry.getTypeStoragesForObjectTypeParameters(wrapper.addr)?.singleOrNull() ?: TypeRegistry.objectTypeStorage
357+
val typeStorage = valueTypeStorageFromGenerics
358+
?.single()
359+
?: TypeRegistry.objectTypeStorage
348360

349361
(0 until sizeValue).associateWithTo(resultModel.stores) { i ->
350362
val addr = UtAddrExpression(arrayExpression.select(mkInt(i + firstValue)))

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

Lines changed: 133 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ import org.utbot.framework.plugin.api.FieldId
118118
import org.utbot.framework.plugin.api.MethodId
119119
import org.utbot.framework.plugin.api.classId
120120
import org.utbot.framework.plugin.api.id
121+
import org.utbot.framework.plugin.api.util.allDeclaredFieldIds
121122
import org.utbot.framework.plugin.api.util.executable
123+
import org.utbot.framework.plugin.api.util.fieldId
122124
import org.utbot.framework.plugin.api.util.jField
123125
import org.utbot.framework.plugin.api.util.jClass
124126
import org.utbot.framework.plugin.api.util.id
@@ -256,8 +258,11 @@ class Traverser(
256258

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

262267
private val preferredCexInstanceCache = mutableMapOf<ObjectValue, MutableSet<SootField>>()
263268

@@ -1036,7 +1041,8 @@ class Traverser(
10361041

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

10411047
if (isNonNullable) {
10421048
queuedSymbolicStateUpdates += mkNot(
@@ -1087,81 +1093,94 @@ class Traverser(
10871093
return UtMockInfoGenerator { mockAddr -> UtObjectMockInfo(type.id, mockAddr) }
10881094
}
10891095

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

1098-
val type = if (identityRef is ThisRef) {
1165+
val type = if (index == 0) {
10991166
// TODO: for ThisRef both methods don't return parameterized type
1100-
if (methodUnderTest.isConstructor) {
1167+
if (method.isConstructor) {
11011168
callable.annotatedReturnType?.type
11021169
} else {
11031170
callable.declaringClass // same as it was, but it isn't parametrized type
1104-
?: error("No instanceParameter for ${callable} found")
1171+
?: error("No instanceParameter for $callable found")
11051172
}
11061173
} else {
11071174
// Sometimes out of bound exception occurred here, e.g., com.alibaba.fescar.core.model.GlobalStatus.<init>
11081175
workaround(HACK) {
1109-
val index = (identityRef as ParameterRef).index
11101176
val valueParameters = callable.genericParameterTypes
11111177

1112-
if (index > valueParameters.lastIndex) return
1113-
valueParameters[index]
1178+
if (index - 1 > valueParameters.lastIndex) return null
1179+
valueParameters[index - 1]
11141180
}
11151181
}
11161182

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-
}
1183+
return type
11651184
}
11661185

11671186
private fun TraversalContext.traverseIfStmt(current: JIfStmt) {
@@ -2084,9 +2103,33 @@ class Traverser(
20842103
checkAndMarkLibraryFieldSpeculativelyNotNull(field, createdField)
20852104
}
20862105

2106+
updateGenericInfoForField(createdField, field)
2107+
20872108
return createdField
20882109
}
20892110

2111+
/**
2112+
* Updates generic info for provided [field] and [createdField] using
2113+
* type information. If [createdField] is not a reference value or
2114+
* if field's type is not a parameterized one, nothing will happen.
2115+
*/
2116+
private fun updateGenericInfoForField(createdField: SymbolicValue, field: SootField) {
2117+
runCatching {
2118+
if (createdField !is ReferenceValue) return
2119+
2120+
// We must have `runCatching` here since might be a situation when we do not have
2121+
// such declaring class in a classpath, that might (but should not) lead to an exception
2122+
val jClass = field.declaringClass.id.jClass
2123+
val requiredField = generateSequence(jClass) { it.superclass }
2124+
.flatMap { it.declaredFields.asSequence() }
2125+
.singleOrNull { it.name == field.name && it.declaringClass.name == field.declaringClass.name }
2126+
2127+
val genericInfo = requiredField?.genericType as? ParameterizedType ?: return
2128+
2129+
updateGenericTypeInfo(genericInfo, createdField)
2130+
}
2131+
}
2132+
20902133
/**
20912134
* Marks the [createdField] as speculatively not null if the [field] is considering as
20922135
* not producing [NullPointerException].
@@ -2519,7 +2562,11 @@ class Traverser(
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+
?.takeIf { it.size < instance.possibleConcreteTypes.size }
2569+
?: instance.possibleConcreteTypes
25232570

25242571
val allPossibleConcreteTypes = typeResolver
25252572
.constructTypeStorage(instance.type, useConcreteType = false)
@@ -2650,6 +2697,29 @@ class Traverser(
26502697
* Returns results of native calls cause other calls push changes directly to path selector.
26512698
*/
26522699
private fun TraversalContext.commonInvokePart(invocation: Invocation): List<MethodResult> {
2700+
val method = invocation.method.executableId
2701+
2702+
// This code is supposed to support generic information from signatures for nested methods.
2703+
// If we have some method 'foo` and a method `bar(List<Integer>), and inside `foo`
2704+
// there is an invocation `bar(object)`, this object must have information about
2705+
// its `Integer` generic type.
2706+
2707+
// Note that we must have `runCatching` here since might be a situation when we do not have
2708+
// such declaring class in a classpath, that might (but should not) lead to an exception
2709+
invocation.parameters.forEachIndexed { index, param ->
2710+
if (param !is ReferenceValue) return@forEachIndexed
2711+
2712+
runCatching {
2713+
updateGenericTypeInfoFromMethod(method, param, parameterIndex = index + 1)
2714+
}
2715+
}
2716+
2717+
if (invocation.instance != null) {
2718+
runCatching {
2719+
updateGenericTypeInfoFromMethod(method, invocation.instance, parameterIndex = 0)
2720+
}
2721+
}
2722+
26532723
/**
26542724
* First, check if there is override for the invocation itself, not for the targets.
26552725
*
@@ -3469,16 +3539,13 @@ class Traverser(
34693539
if (baseTypeAfterCast is RefType) {
34703540
// Find parameterized type for the object if it is a parameter of the method under test and it has generic type
34713541
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]
3542+
val parameterizedTypes = when (newAddr.internal) {
3543+
is UtArraySelectExpression -> instanceAddrToGenericType[findTheMostNestedAddr(newAddr.internal)]
3544+
is UtBvConst -> instanceAddrToGenericType[newAddr]
34753545
else -> null
34763546
}
34773547

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
3548+
parameterizedTypes?.forEach { parameterizedType ->
34823549
val genericTypes = generateSequence(parameterizedType) { it.ownerType as? ParameterizedType }
34833550
.flatMapTo(mutableSetOf()) { it.actualTypeArguments.map { arg -> arg.typeName } }
34843551

0 commit comments

Comments
 (0)