From 88e6473639b62ce0cc2f34b6ce87398dfc7b84cc Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 4 Jan 2023 16:18:32 +0800 Subject: [PATCH 1/2] Add support for generic information of nested methods --- .../org/utbot/examples/types/GenericsTest.kt | 63 ++++++ .../org/utbot/engine/ArrayObjectWrappers.kt | 22 +- .../main/kotlin/org/utbot/engine/Traverser.kt | 199 ++++++++++++------ .../utbot/engine/pc/Z3TranslatorVisitor.kt | 11 + .../org/utbot/engine/types/TypeRegistry.kt | 29 ++- .../org/utbot/examples/collections/Maps.java | 1 + .../examples/types/CollectionAsField.java | 10 + .../org/utbot/examples/types/Generics.java | 41 ++++ 8 files changed, 304 insertions(+), 72 deletions(-) create mode 100644 utbot-framework-test/src/test/kotlin/org/utbot/examples/types/GenericsTest.kt create mode 100644 utbot-sample/src/main/java/org/utbot/examples/types/CollectionAsField.java create mode 100644 utbot-sample/src/main/java/org/utbot/examples/types/Generics.java diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/GenericsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/GenericsTest.kt new file mode 100644 index 0000000000..56492c1538 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/GenericsTest.kt @@ -0,0 +1,63 @@ +package org.utbot.examples.types + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber + +internal class GenericsTest : UtValueTestCaseChecker( + testClass = GenericsTest::class, + testCodeGeneration = false // TODO empty files are generated https://github.com/UnitTestBot/UTBotJava/issues/1616 +) { + @Test + fun mapAsParameterTest() { + check( + Generics::mapAsParameter, + eq(2), + { map, _ -> map == null }, + { map, r -> map != null && r == "value" }, + ) + } + + @Test + @Disabled("https://github.com/UnitTestBot/UTBotJava/issues/1620 wrong equals") + fun genericAsFieldTest() { + check( + Generics::genericAsField, + ignoreExecutionsNumber, + { obj, r -> obj?.field == null && r == false }, + // we can cover this line with any of these two conditions + { obj, r -> (obj.field != null && obj.field != "abc" && r == false) || (obj.field == "abc" && r == true) }, + ) + } + + @Test + fun mapAsStaticFieldTest() { + check( + Generics::mapAsStaticField, + ignoreExecutionsNumber, + { r -> r == "value" }, + ) + } + + @Test + fun mapAsNonStaticFieldTest() { + check( + Generics::mapAsNonStaticField, + ignoreExecutionsNumber, + { map, _ -> map == null }, + { map, r -> map != null && r == "value" }, + ) + } + + @Test + fun methodWithRawTypeTest() { + check( + Generics::methodWithRawType, + eq(2), + { map, _ -> map == null }, + { map, r -> map != null && r == "value" }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt index 855e98de83..42d3dbad58 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt @@ -201,9 +201,14 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { val addr = UtAddrExpression(value) // Try to retrieve manually set type if present - val valueType = typeRegistry - .getTypeStoragesForObjectTypeParameters(wrapper.addr) - ?.singleOrNull() + val valueTypeFromGenerics = typeRegistry.getTypeStoragesForObjectTypeParameters(wrapper.addr) + + if (valueTypeFromGenerics != null && valueTypeFromGenerics.size > 1) { + error("Range modifiable wrapper must have only one type parameter, but it got ${valueTypeFromGenerics.size}") + } + + val valueType = valueTypeFromGenerics + ?.single() ?.leastCommonType ?: OBJECT_TYPE @@ -342,9 +347,16 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { // the constructed model to avoid infinite recursion below resolver.addConstructedModel(concreteAddr, resultModel) + val valueTypeStorageFromGenerics = resolver.typeRegistry.getTypeStoragesForObjectTypeParameters(wrapper.addr) + + if (valueTypeStorageFromGenerics != null && valueTypeStorageFromGenerics.size > 1) { + error("Range modifiable wrapper must have only one type parameter, but it got ${valueTypeStorageFromGenerics.size}") + } + // try to retrieve type storage for the single type parameter - val typeStorage = - resolver.typeRegistry.getTypeStoragesForObjectTypeParameters(wrapper.addr)?.singleOrNull() ?: TypeRegistry.objectTypeStorage + val typeStorage = valueTypeStorageFromGenerics + ?.single() + ?: TypeRegistry.objectTypeStorage (0 until sizeValue).associateWithTo(resultModel.stores) { i -> val addr = UtAddrExpression(arrayExpression.select(mkInt(i + firstValue))) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 0a5bcee5bb..d4ebb64248 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -118,7 +118,9 @@ import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.allDeclaredFieldIds import org.utbot.framework.plugin.api.util.executable +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.jField import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.id @@ -256,8 +258,11 @@ class Traverser( /** * Contains information about the generic types used in the parameters of the method under test. + * + * Mutable set here is required since this object might be passed into several methods + * and get several piece of information about their parameterized types */ - private val parameterAddrToGenericType = mutableMapOf() + private val instanceAddrToGenericType = mutableMapOf>() private val preferredCexInstanceCache = mutableMapOf>() @@ -1036,7 +1041,8 @@ class Traverser( if (createdValue is ReferenceValue) { // Update generic type info for method under test' parameters - updateGenericTypeInfo(identityRef, createdValue) + val index = (identityRef as? ParameterRef)?.index?.plus(1) ?: 0 + updateGenericTypeInfoFromMethod(methodUnderTest, createdValue, index) if (isNonNullable) { queuedSymbolicStateUpdates += mkNot( @@ -1087,81 +1093,94 @@ class Traverser( return UtMockInfoGenerator { mockAddr -> UtObjectMockInfo(type.id, mockAddr) } } + private fun updateGenericTypeInfoFromMethod(method: ExecutableId, value: ReferenceValue, parameterIndex: Int) { + val type = extractParameterizedType(method, parameterIndex) as? ParameterizedType ?: return + + updateGenericTypeInfo(type, value) + } + /** * Stores information about the generic types used in the parameters of the method under test. */ - private fun updateGenericTypeInfo(identityRef: IdentityRef, value: ReferenceValue) { + private fun updateGenericTypeInfo(type: ParameterizedType, value: ReferenceValue) { + val typeStorages = type.actualTypeArguments.map { actualTypeArgument -> + when (actualTypeArgument) { + is WildcardType -> { + val upperBounds = actualTypeArgument.upperBounds + val lowerBounds = actualTypeArgument.lowerBounds + val allTypes = upperBounds + lowerBounds + + if (allTypes.any { it is GenericArrayType }) { + val errorTypes = allTypes.filterIsInstance() + TODO("we do not support GenericArrayTypeImpl yet, and $errorTypes found. SAT-1446") + } + + val upperBoundsTypes = typeResolver.intersectInheritors(upperBounds) + val lowerBoundsTypes = typeResolver.intersectAncestors(lowerBounds) + + typeResolver.constructTypeStorage(OBJECT_TYPE, upperBoundsTypes.intersect(lowerBoundsTypes)) + } + is TypeVariable<*> -> { // it is a type variable for the whole class, not the function type variable + val upperBounds = actualTypeArgument.bounds + + if (upperBounds.any { it is GenericArrayType }) { + val errorTypes = upperBounds.filterIsInstance() + TODO("we do not support GenericArrayType yet, and $errorTypes found. SAT-1446") + } + + val upperBoundsTypes = typeResolver.intersectInheritors(upperBounds) + + typeResolver.constructTypeStorage(OBJECT_TYPE, upperBoundsTypes) + } + is GenericArrayType -> { + // TODO bug with T[][], because there is no such time T JIRA:1446 + typeResolver.constructTypeStorage(OBJECT_TYPE, useConcreteType = false) + } + is ParameterizedType, is Class<*> -> { + val sootType = Scene.v().getType(actualTypeArgument.rawType.typeName) + + typeResolver.constructTypeStorage(sootType, useConcreteType = false) + } + else -> error("Unsupported argument type ${actualTypeArgument::class}") + } + } + + queuedSymbolicStateUpdates += typeRegistry + .genericTypeParameterConstraint(value.addr, typeStorages) + .asHardConstraint() + + instanceAddrToGenericType.getOrPut(value.addr) { mutableSetOf() }.add(type) + + typeRegistry.saveObjectParameterTypeStorages(value.addr, typeStorages) + } + + private fun extractParameterizedType( + method: ExecutableId, + index: Int + ): java.lang.reflect.Type? { // If we don't have access to methodUnderTest's jClass, the engine should not fail // We just won't update generic information for it - val callable = runCatching { methodUnderTest.executable }.getOrNull() ?: return + val callable = runCatching { method.executable }.getOrNull() ?: return null - val type = if (identityRef is ThisRef) { + val type = if (index == 0) { // TODO: for ThisRef both methods don't return parameterized type - if (methodUnderTest.isConstructor) { + if (method.isConstructor) { callable.annotatedReturnType?.type } else { callable.declaringClass // same as it was, but it isn't parametrized type - ?: error("No instanceParameter for ${callable} found") + ?: error("No instanceParameter for $callable found") } } else { // Sometimes out of bound exception occurred here, e.g., com.alibaba.fescar.core.model.GlobalStatus. workaround(HACK) { - val index = (identityRef as ParameterRef).index val valueParameters = callable.genericParameterTypes - if (index > valueParameters.lastIndex) return - valueParameters[index] + if (index - 1 > valueParameters.lastIndex) return null + valueParameters[index - 1] } } - if (type is ParameterizedType) { - val typeStorages = type.actualTypeArguments.map { actualTypeArgument -> - when (actualTypeArgument) { - is WildcardType -> { - val upperBounds = actualTypeArgument.upperBounds - val lowerBounds = actualTypeArgument.lowerBounds - val allTypes = upperBounds + lowerBounds - - if (allTypes.any { it is GenericArrayType }) { - val errorTypes = allTypes.filterIsInstance() - TODO("we do not support GenericArrayTypeImpl yet, and $errorTypes found. SAT-1446") - } - - val upperBoundsTypes = typeResolver.intersectInheritors(upperBounds) - val lowerBoundsTypes = typeResolver.intersectAncestors(lowerBounds) - - typeResolver.constructTypeStorage(OBJECT_TYPE, upperBoundsTypes.intersect(lowerBoundsTypes)) - } - is TypeVariable<*> -> { // it is a type variable for the whole class, not the function type variable - val upperBounds = actualTypeArgument.bounds - - if (upperBounds.any { it is GenericArrayType }) { - val errorTypes = upperBounds.filterIsInstance() - TODO("we do not support GenericArrayType yet, and $errorTypes found. SAT-1446") - } - - val upperBoundsTypes = typeResolver.intersectInheritors(upperBounds) - - typeResolver.constructTypeStorage(OBJECT_TYPE, upperBoundsTypes) - } - is GenericArrayType -> { - // TODO bug with T[][], because there is no such time T JIRA:1446 - typeResolver.constructTypeStorage(OBJECT_TYPE, useConcreteType = false) - } - is ParameterizedType, is Class<*> -> { - val sootType = Scene.v().getType(actualTypeArgument.rawType.typeName) - - typeResolver.constructTypeStorage(sootType, useConcreteType = false) - } - else -> error("Unsupported argument type ${actualTypeArgument::class}") - } - } - - queuedSymbolicStateUpdates += typeRegistry.genericTypeParameterConstraint(value.addr, typeStorages).asHardConstraint() - parameterAddrToGenericType += value.addr to type - - typeRegistry.saveObjectParameterTypeStorages(value.addr, typeStorages) - } + return type } private fun TraversalContext.traverseIfStmt(current: JIfStmt) { @@ -2084,9 +2103,33 @@ class Traverser( checkAndMarkLibraryFieldSpeculativelyNotNull(field, createdField) } + updateGenericInfoForField(createdField, field) + return createdField } + /** + * Updates generic info for provided [field] and [createdField] using + * type information. If [createdField] is not a reference value or + * if field's type is not a parameterized one, nothing will happen. + */ + private fun updateGenericInfoForField(createdField: SymbolicValue, field: SootField) { + runCatching { + if (createdField !is ReferenceValue) return + + // We must have `runCatching` here since might be a situation when we do not have + // such declaring class in a classpath, that might (but should not) lead to an exception + val jClass = field.declaringClass.id.jClass + val requiredField = generateSequence(jClass) { it.superclass } + .flatMap { it.declaredFields.asSequence() } + .singleOrNull { it.name == field.name && it.declaringClass.name == field.declaringClass.name } + + val genericInfo = requiredField?.genericType as? ParameterizedType ?: return + + updateGenericTypeInfo(genericInfo, createdField) + } + } + /** * Marks the [createdField] as speculatively not null if the [field] is considering as * not producing [NullPointerException]. @@ -2519,7 +2562,11 @@ class Traverser( // While using simplifications with RewritingVisitor, assertions can maintain types // for objects (especially objects with type equals to type parameter of generic) // better than engine. - val types = instanceOfConstraint?.typeStorage?.possibleConcreteTypes ?: instance.possibleConcreteTypes + val types = instanceOfConstraint + ?.typeStorage + ?.possibleConcreteTypes + ?.takeIf { it.size < instance.possibleConcreteTypes.size } + ?: instance.possibleConcreteTypes val allPossibleConcreteTypes = typeResolver .constructTypeStorage(instance.type, useConcreteType = false) @@ -2650,6 +2697,29 @@ class Traverser( * Returns results of native calls cause other calls push changes directly to path selector. */ private fun TraversalContext.commonInvokePart(invocation: Invocation): List { + val method = invocation.method.executableId + + // This code is supposed to support generic information from signatures for nested methods. + // If we have some method 'foo` and a method `bar(List), and inside `foo` + // there is an invocation `bar(object)`, this object must have information about + // its `Integer` generic type. + + // Note that we must have `runCatching` here since might be a situation when we do not have + // such declaring class in a classpath, that might (but should not) lead to an exception + invocation.parameters.forEachIndexed { index, param -> + if (param !is ReferenceValue) return@forEachIndexed + + runCatching { + updateGenericTypeInfoFromMethod(method, param, parameterIndex = index + 1) + } + } + + if (invocation.instance != null) { + runCatching { + updateGenericTypeInfoFromMethod(method, invocation.instance, parameterIndex = 0) + } + } + /** * First, check if there is override for the invocation itself, not for the targets. * @@ -3469,16 +3539,13 @@ class Traverser( if (baseTypeAfterCast is RefType) { // Find parameterized type for the object if it is a parameter of the method under test and it has generic type val newAddr = addr.accept(solver.simplificator) as UtAddrExpression - val parameterizedType = when (newAddr.internal) { - is UtArraySelectExpression -> parameterAddrToGenericType[findTheMostNestedAddr(newAddr.internal)] - is UtBvConst -> parameterAddrToGenericType[newAddr] + val parameterizedTypes = when (newAddr.internal) { + is UtArraySelectExpression -> instanceAddrToGenericType[findTheMostNestedAddr(newAddr.internal)] + is UtBvConst -> instanceAddrToGenericType[newAddr] else -> null } - if (parameterizedType != null) { - // Find all generics used in the type of the parameter and it's superclasses - // If we're trying to cast something related to the parameter and typeAfterCast is equal to one of the generic - // types used in it, don't throw ClassCastException + parameterizedTypes?.forEach { parameterizedType -> val genericTypes = generateSequence(parameterizedType) { it.ownerType as? ParameterizedType } .flatMapTo(mutableSetOf()) { it.actualTypeArguments.map { arg -> arg.typeName } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt index 376ceec294..89e775fbd1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt @@ -203,10 +203,21 @@ open class Z3TranslatorVisitor( val constraints = mutableListOf() for (i in types.indices) { val symType = translate(typeRegistry.genericTypeId(addr, i)) + val genericNumDimensions = translate(typeRegistry.genericNumDimensions(addr, i)) as BitVecExpr + val possibleConcreteTypes = types[i].possibleConcreteTypes + val leastCommonType = types[i].leastCommonType + + val numDimensions = z3Context.mkBV(leastCommonType.numDimensions, Int.SIZE_BITS) if (possibleConcreteTypes.size > UtSettings.maxNumberOfTypesToEncode) continue + constraints += if (leastCommonType.isJavaLangObject()) { + z3Context.mkBVSGE(genericNumDimensions, numDimensions) + } else { + z3Context.mkEq(genericNumDimensions, numDimensions) + } + constraints += encodePossibleTypes(symType, possibleConcreteTypes) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeRegistry.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeRegistry.kt index 4a8c422fcf..cec7d39c19 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeRegistry.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeRegistry.kt @@ -479,7 +479,34 @@ class TypeRegistry { * Associates provided [typeStorages] with an object with the provided [addr]. */ fun saveObjectParameterTypeStorages(addr: UtAddrExpression, typeStorages: List) { - genericTypeStorageByAddr += addr to typeStorages + if (addr !in genericTypeStorageByAddr.keys) { + genericTypeStorageByAddr += addr to typeStorages + return + } + + val alreadyAddedTypeStorages = genericTypeStorageByAddr.getValue(addr) + + // Because of the design decision for genericTypeStorage map, it contains a + // mapping from addresses to associated with them type arguments. + // Therefore, first element of the list is a first type argument for the instance, and so on. + // To update type information, we have to update a corresponding type storage. + // Because of that, update is only possible when we have information about all type arguments. + require(typeStorages.size == alreadyAddedTypeStorages.size) { + "Wrong number of type storages is provided," + + " expected ${alreadyAddedTypeStorages.size} arguments," + + " but only ${typeStorages.size} found" + } + + val modifiedTypeStorages = alreadyAddedTypeStorages.mapIndexed { index, typeStorage -> + val newTypeStorage = typeStorages[index] + + val updatedTypes = typeStorage.possibleConcreteTypes.intersect(newTypeStorage.possibleConcreteTypes) + + // TODO should be really the least common type + TypeStorage.constructTypeStorageUnsafe(typeStorage.leastCommonType, updatedTypes) + } + + genericTypeStorageByAddr[addr] = modifiedTypeStorages } /** diff --git a/utbot-sample/src/main/java/org/utbot/examples/collections/Maps.java b/utbot-sample/src/main/java/org/utbot/examples/collections/Maps.java index 2d20a80a72..0be06782f5 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/collections/Maps.java +++ b/utbot-sample/src/main/java/org/utbot/examples/collections/Maps.java @@ -301,6 +301,7 @@ public Map createMapWithString() { return map; } + public Map createMapWithEnum() { Map map = new HashMap<>(); map.put(WorkDays.Monday, 112); diff --git a/utbot-sample/src/main/java/org/utbot/examples/types/CollectionAsField.java b/utbot-sample/src/main/java/org/utbot/examples/types/CollectionAsField.java new file mode 100644 index 0000000000..157f34f63c --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/types/CollectionAsField.java @@ -0,0 +1,10 @@ +package org.utbot.examples.types; + +import java.util.HashMap; +import java.util.Map; + +public class CollectionAsField { + public static Map staticMap = new HashMap<>(); + public Map nonStaticMap = new HashMap<>(); + public T field; +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/types/Generics.java b/utbot-sample/src/main/java/org/utbot/examples/types/Generics.java new file mode 100644 index 0000000000..b9f5f9c322 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/types/Generics.java @@ -0,0 +1,41 @@ +package org.utbot.examples.types; + +import java.util.Map; + +public class Generics { + public boolean genericAsField(CollectionAsField object) { + if (object != null && object.field != null) { + return object.field.equals("abc"); + } + + return false; + } + + public String mapAsStaticField() { + CollectionAsField.staticMap.put("key", "value"); + return CollectionAsField.staticMap.get("key"); + } + + public String mapAsParameter(Map map) { + map.put("key", "value"); + return map.get("key"); + } + + public String mapAsNonStaticField(CollectionAsField object) { + object.nonStaticMap.put("key", "value"); + return object.nonStaticMap.get("key"); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public String methodWithRawType(Map map) { + nestedMethodWithGenericInfo(map); + map.put("key", "value"); + + return (String) map.get("key"); + } + + @SuppressWarnings("UnusedReturnValue") + private Map nestedMethodWithGenericInfo(Map map) { + return map; + } +} From df9fec73a45e937ba4a7a4f41c1142014d0274e2 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Thu, 5 Jan 2023 12:05:06 +0800 Subject: [PATCH 2/2] Review fixes --- .../org/utbot/engine/ArrayObjectWrappers.kt | 24 +++++-------- .../main/kotlin/org/utbot/engine/Traverser.kt | 34 ++++++++----------- .../org/utbot/engine/types/TypeRegistry.kt | 18 ++++++++++ 3 files changed, 41 insertions(+), 35 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt index 42d3dbad58..36f5017248 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt @@ -201,14 +201,8 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { val addr = UtAddrExpression(value) // Try to retrieve manually set type if present - val valueTypeFromGenerics = typeRegistry.getTypeStoragesForObjectTypeParameters(wrapper.addr) - - if (valueTypeFromGenerics != null && valueTypeFromGenerics.size > 1) { - error("Range modifiable wrapper must have only one type parameter, but it got ${valueTypeFromGenerics.size}") - } - - val valueType = valueTypeFromGenerics - ?.single() + val valueType = typeRegistry + .extractSingleTypeParameterForRangeModifiableArray(wrapper.addr) ?.leastCommonType ?: OBJECT_TYPE @@ -347,15 +341,10 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { // the constructed model to avoid infinite recursion below resolver.addConstructedModel(concreteAddr, resultModel) - val valueTypeStorageFromGenerics = resolver.typeRegistry.getTypeStoragesForObjectTypeParameters(wrapper.addr) - - if (valueTypeStorageFromGenerics != null && valueTypeStorageFromGenerics.size > 1) { - error("Range modifiable wrapper must have only one type parameter, but it got ${valueTypeStorageFromGenerics.size}") - } - // try to retrieve type storage for the single type parameter - val typeStorage = valueTypeStorageFromGenerics - ?.single() + val typeStorage = resolver + .typeRegistry + .extractSingleTypeParameterForRangeModifiableArray(wrapper.addr) ?: TypeRegistry.objectTypeStorage (0 until sizeValue).associateWithTo(resultModel.stores) { i -> @@ -373,6 +362,9 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { return resultModel } + private fun TypeRegistry.extractSingleTypeParameterForRangeModifiableArray(addr: UtAddrExpression) = + extractTypeStorageForObjectWithSingleTypeParameter(addr, "Range modifiable array") + companion object { internal val rangeModifiableArrayClass: SootClass get() = Scene.v().getSootClass(rangeModifiableArrayId.name) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index d4ebb64248..4360c35252 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -118,9 +118,8 @@ import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.id -import org.utbot.framework.plugin.api.util.allDeclaredFieldIds import org.utbot.framework.plugin.api.util.executable -import org.utbot.framework.plugin.api.util.fieldId +import org.utbot.framework.plugin.api.util.findFieldByIdOrNull import org.utbot.framework.plugin.api.util.jField import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.id @@ -2119,11 +2118,8 @@ class Traverser( // We must have `runCatching` here since might be a situation when we do not have // such declaring class in a classpath, that might (but should not) lead to an exception - val jClass = field.declaringClass.id.jClass - val requiredField = generateSequence(jClass) { it.superclass } - .flatMap { it.declaredFields.asSequence() } - .singleOrNull { it.name == field.name && it.declaringClass.name == field.declaringClass.name } - + val classId = field.declaringClass.id + val requiredField = classId.findFieldByIdOrNull(field.fieldId) val genericInfo = requiredField?.genericType as? ParameterizedType ?: return updateGenericTypeInfo(genericInfo, createdField) @@ -2552,10 +2548,14 @@ class Traverser( ): List { val visitor = solver.simplificator.axiomInstantiationVisitor val simplifiedAddr = instance.addr.accept(visitor) + // UtIsExpression for object with address the same as instance.addr - val instanceOfConstraint = solver.assertions.singleOrNull { - it is UtIsExpression && it.addr == simplifiedAddr - } as? UtIsExpression + // If there are several such constraints, take the one with the least number of possible types + val instanceOfConstraint = solver.assertions + .filter { it is UtIsExpression && it.addr == simplifiedAddr } + .takeIf { it.isNotEmpty() } + ?.minBy { (it as UtIsExpression).typeStorage.possibleConcreteTypes.size } as? UtIsExpression + // if we have UtIsExpression constraint for [instance], then find invocation targets // for possibleTypes from this constraints, instead of the type maintained by solver. @@ -2565,6 +2565,9 @@ class Traverser( val types = instanceOfConstraint ?.typeStorage ?.possibleConcreteTypes + // we should take this constraint into consideration only if it has less + // possible types than our current object, otherwise, it doesn't add + // any helpful information ?.takeIf { it.size < instance.possibleConcreteTypes.size } ?: instance.possibleConcreteTypes @@ -2703,21 +2706,14 @@ class Traverser( // If we have some method 'foo` and a method `bar(List), and inside `foo` // there is an invocation `bar(object)`, this object must have information about // its `Integer` generic type. - - // Note that we must have `runCatching` here since might be a situation when we do not have - // such declaring class in a classpath, that might (but should not) lead to an exception invocation.parameters.forEachIndexed { index, param -> if (param !is ReferenceValue) return@forEachIndexed - runCatching { - updateGenericTypeInfoFromMethod(method, param, parameterIndex = index + 1) - } + updateGenericTypeInfoFromMethod(method, param, parameterIndex = index + 1) } if (invocation.instance != null) { - runCatching { - updateGenericTypeInfoFromMethod(method, invocation.instance, parameterIndex = 0) - } + updateGenericTypeInfoFromMethod(method, invocation.instance, parameterIndex = 0) } /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeRegistry.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeRegistry.kt index cec7d39c19..88d7bbdecd 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeRegistry.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeRegistry.kt @@ -503,6 +503,11 @@ class TypeRegistry { val updatedTypes = typeStorage.possibleConcreteTypes.intersect(newTypeStorage.possibleConcreteTypes) // TODO should be really the least common type + // we have two type storages and know that one of them is subset of another one. + // Therefore, when we intersect them, we should chose correct least common type among them, + // but we don't do it here since it is not obvious, what is a correct way to do it. + // There is no access from here to typeResolver or Hierarchy, so it need to be + // reconsidered in the future, how to intersect type storages here or extract this function. TypeStorage.constructTypeStorageUnsafe(typeStorage.leastCommonType, updatedTypes) } @@ -514,6 +519,19 @@ class TypeRegistry { */ fun getTypeStoragesForObjectTypeParameters(addr: UtAddrExpression): List? = genericTypeStorageByAddr[addr] + fun extractTypeStorageForObjectWithSingleTypeParameter( + addr: UtAddrExpression, + objectClassName: String + ): TypeStorage? { + val valueTypeFromGenerics = getTypeStoragesForObjectTypeParameters(addr) + + if (valueTypeFromGenerics != null && valueTypeFromGenerics.size > 1) { + error("$objectClassName must have only one type parameter, but it got ${valueTypeFromGenerics.size}") + } + + return valueTypeFromGenerics?.single() + } + /** * Set types storages for [firstAddr]'s type parameters equal to type storages for [secondAddr]'s type parameters * according to provided types injection represented by [indexInjection].