From 95e4dc70365c832d14200301a0fc670309bb9c9f Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Tue, 24 Jan 2023 18:32:26 +0300 Subject: [PATCH 1/3] Add support for virtual invokes on enums --- .../utbot/examples/enums/ClassWithEnumTest.kt | 53 +++++++++++-------- .../main/kotlin/org/utbot/engine/Memory.kt | 8 +-- .../main/kotlin/org/utbot/engine/Resolver.kt | 8 ++- .../main/kotlin/org/utbot/engine/Traverser.kt | 2 +- .../utbot/examples/enums/ClassWithEnum.java | 25 ++++++++- 5 files changed, 65 insertions(+), 31 deletions(-) diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt index 8e029877ea..fc61a73930 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt @@ -1,17 +1,19 @@ package org.utbot.examples.enums +import org.junit.jupiter.api.Disabled import org.utbot.examples.enums.ClassWithEnum.StatusEnum.ERROR import org.utbot.examples.enums.ClassWithEnum.StatusEnum.READY import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.util.id -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.examples.enums.ClassWithEnum.StatusEnum import org.utbot.framework.plugin.api.util.jField import org.utbot.testcheckers.eq import org.utbot.testcheckers.withPushingStateFromPathSelectorForConcrete import org.utbot.testcheckers.withoutConcrete import org.utbot.testing.DoNotCalculate import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between import org.utbot.testing.ignoreExecutionsNumber import org.utbot.testing.isException @@ -44,19 +46,16 @@ class ClassWithEnumTest : UtValueTestCaseChecker(testClass = ClassWithEnum::clas } @Test - @Disabled("TODO JIRA:1686") fun testNullParameter() { check( ClassWithEnum::nullEnumAsParameter, - eq(3), + between(2..3), { e, _ -> e == null }, - { e, r -> e == READY && r == 0 }, - { e, r -> e == ERROR && r == -1 }, + { e, r -> e == READY && r == 0 || e == ERROR && r == -1 }, ) } @Test - @Disabled("TODO JIRA:1686") fun testNullField() { checkWithException( ClassWithEnum::nullField, @@ -67,22 +66,21 @@ class ClassWithEnumTest : UtValueTestCaseChecker(testClass = ClassWithEnum::clas ) } + @Suppress("KotlinConstantConditions") @Test - @Disabled("TODO JIRA:1686") fun testChangeEnum() { checkWithException( ClassWithEnum::changeEnum, - eq(3), - { e, r -> e == null && r.isException() }, + eq(2), { e, r -> e == READY && r.getOrNull()!! == ERROR.ordinal }, - { e, r -> e == ERROR && r.getOrNull()!! == READY.ordinal }, + { e, r -> (e == ERROR || e == null) && r.getOrNull()!! == READY.ordinal }, ) } @Test fun testChangeMutableField() { // TODO testing code generation for this method is disabled because we need to restore original field state - // should be enabled after solving JIRA:1648 + // should be enabled after solving https://github.com/UnitTestBot/UTBotJava/issues/80 withEnabledTestingCodeGeneration(testCodeGeneration = false) { checkWithException( ClassWithEnum::changeMutableField, @@ -94,7 +92,7 @@ class ClassWithEnumTest : UtValueTestCaseChecker(testClass = ClassWithEnum::clas } @Test - @Disabled("TODO JIRA:1686") + @Disabled("https://github.com/UnitTestBot/UTBotJava/issues/1745") fun testCheckName() { check( ClassWithEnum::checkName, @@ -111,21 +109,33 @@ class ClassWithEnumTest : UtValueTestCaseChecker(testClass = ClassWithEnum::clas ClassWithEnum::changingStaticWithEnumInit, eq(1), { t, staticsAfter, r -> + // we cannot check x since it is not a meaningful value // for some reasons x is inaccessible - val x = FieldId(t.javaClass.id, "x").jField.get(t) as Int + // val x = FieldId(t.javaClass.id, "x").jField.get(t) as Int val y = staticsAfter[FieldId(ClassWithEnum.ClassWithStaticField::class.id, "y")]!!.value as Int - val areStaticsCorrect = x == 1 && y == 11 + val areStaticsCorrect = /*x == 1 &&*/ y == 11 areStaticsCorrect && r == true } ) } + @Test + fun testVirtualFunction() { + check( + ClassWithEnum::virtualFunction, + eq(3), + { parameter, _ -> parameter == null }, + { parameter, r -> r == 1 && parameter == ERROR }, + { parameter, r -> r == 0 && parameter == READY }, + ) + } + @Test fun testEnumValues() { checkStaticMethod( - ClassWithEnum.StatusEnum::values, + StatusEnum::values, eq(1), { r -> r.contentEquals(arrayOf(READY, ERROR)) }, ) @@ -134,7 +144,7 @@ class ClassWithEnumTest : UtValueTestCaseChecker(testClass = ClassWithEnum::clas @Test fun testFromCode() { checkStaticMethod( - ClassWithEnum.StatusEnum::fromCode, + StatusEnum::fromCode, eq(3), { code, r -> code == 10 && r == READY }, { code, r -> code == -10 && r == ERROR }, @@ -145,7 +155,7 @@ class ClassWithEnumTest : UtValueTestCaseChecker(testClass = ClassWithEnum::clas @Test fun testFromIsReady() { checkStaticMethod( - ClassWithEnum.StatusEnum::fromIsReady, + StatusEnum::fromIsReady, eq(2), { isFirst, r -> isFirst && r == READY }, { isFirst, r -> !isFirst && r == ERROR }, @@ -153,14 +163,11 @@ class ClassWithEnumTest : UtValueTestCaseChecker(testClass = ClassWithEnum::clas } @Test - @Disabled("TODO JIRA:1450") fun testPublicGetCodeMethod() { checkWithThis( - ClassWithEnum.StatusEnum::publicGetCode, - eq(2), - { enumInstance, r -> enumInstance == READY && r == 10 }, - { enumInstance, r -> enumInstance == ERROR && r == -10 }, - coverage = DoNotCalculate + StatusEnum::publicGetCode, + between(1..2), + { enumInstance, r -> enumInstance == READY && r == 10 || enumInstance == ERROR && r == -10 }, ) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index eaf118627c..fb68545abb 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -40,7 +40,7 @@ import kotlinx.collections.immutable.toPersistentMap import org.utbot.engine.types.STRING_TYPE import org.utbot.engine.types.SeqType import org.utbot.engine.types.TypeResolver -import org.utbot.framework.plugin.api.classId +import org.utbot.framework.plugin.api.id import soot.ArrayType import soot.CharType import soot.IntType @@ -357,8 +357,9 @@ data class Memory( // TODO: split purely symbolic memory and information about s fun findTypeForArrayOrNull(addr: UtAddrExpression): ArrayType? = addrToArrayType[addr] + // We check a superclass here since we added a superclass in here, not enum instances fun getSymbolicEnumValues(classId: ClassId): List = - symbolicEnumValues.filter { it.type.classId == classId } + symbolicEnumValues.filter { it.type.sootClass.superClassOrNull()?.id == classId } } private fun initialArray(descriptor: MemoryChunkDescriptor) = @@ -429,8 +430,9 @@ data class MemoryUpdate( symbolicEnumValues = symbolicEnumValues.addAll(other.symbolicEnumValues), ) + // We check a superclass here since we added a superclass in here, not enum instances fun getSymbolicEnumValues(classId: ClassId): List = - symbolicEnumValues.filter { it.type.classId == classId } + symbolicEnumValues.filter { it.type.sootClass.superClassOrNull()?.id == classId } } // array - Java Array diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt index 23db078781..efe620ca49 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt @@ -558,9 +558,13 @@ class Resolver( } val clazz = classLoader.loadClass(sootClass.name) + // Since actualType for enums represents its instances and enum constants are not actually + // enum instances, we have to check the defaultType below instead on the actual one + // whether is it an Enum or not + val defaultClazz = classLoader.loadClass(defaultType.sootClass.name) - if (clazz.isEnum) { - return constructEnum(concreteAddr, actualType, clazz) + if (defaultClazz.isEnum) { + return constructEnum(concreteAddr, actualType, defaultClazz) } // check if we have mock with this address 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 d93ca2dbeb..9ac55a8f11 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -2324,7 +2324,7 @@ class Traverser( } private fun createEnum(type: RefType, addr: UtAddrExpression): ObjectValue { - val typeStorage = typeResolver.constructTypeStorage(type, useConcreteType = true) + val typeStorage = typeResolver.constructTypeStorage(type, useConcreteType = false) queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() diff --git a/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnum.java b/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnum.java index c8d472781d..61cc9c4a83 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnum.java +++ b/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnum.java @@ -98,9 +98,28 @@ public boolean changingStaticWithEnumInit() { return true; } + public int virtualFunction(StatusEnum parameter) { + int value = parameter.virtualFunction(); + if (value > 0) { + return value; + } + + return Math.abs(value); + } + enum StatusEnum { - READY(0, 10, "200"), - ERROR(-1, -10, null); + READY(0, 10, "200") { + @Override + public int virtualFunction() { + return 0; + } + }, + ERROR(-1, -10, null) { + @Override + int virtualFunction() { + return 1; + } + }; int mutableInt; final int code; @@ -137,6 +156,8 @@ static StatusEnum fromIsReady(boolean isReady) { int publicGetCode() { return this == READY ? 10 : -10; } + + abstract int virtualFunction(); } enum ManyConstantsEnum { From 88c5d652cc98eaadca10c56c69eec7a7c83c8433 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 25 Jan 2023 11:39:07 +0300 Subject: [PATCH 2/3] Review fixes --- .../utbot/examples/enums/ClassWithEnumTest.kt | 6 ++-- .../main/kotlin/org/utbot/engine/Memory.kt | 33 ++++++++++++++++--- .../main/kotlin/org/utbot/engine/Resolver.kt | 14 ++++++-- .../main/kotlin/org/utbot/engine/Traverser.kt | 8 +++-- 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt index fc61a73930..7b9de124ba 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt @@ -109,8 +109,10 @@ class ClassWithEnumTest : UtValueTestCaseChecker(testClass = ClassWithEnum::clas ClassWithEnum::changingStaticWithEnumInit, eq(1), { t, staticsAfter, r -> - // we cannot check x since it is not a meaningful value - // for some reasons x is inaccessible + // We cannot check `x` since it is not a meaningful value since + // it is accessed only in a static initializer. + + // For some reasons x is inaccessible // val x = FieldId(t.javaClass.id, "x").jField.get(t) as Int val y = staticsAfter[FieldId(ClassWithEnum.ClassWithStaticField::class.id, "y")]!!.value as Int diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index fb68545abb..ecb45a0871 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -41,6 +41,7 @@ import org.utbot.engine.types.STRING_TYPE import org.utbot.engine.types.SeqType import org.utbot.engine.types.TypeResolver import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.isEnum import soot.ArrayType import soot.CharType import soot.IntType @@ -357,9 +358,21 @@ data class Memory( // TODO: split purely symbolic memory and information about s fun findTypeForArrayOrNull(addr: UtAddrExpression): ArrayType? = addrToArrayType[addr] - // We check a superclass here since we added a superclass in here, not enum instances fun getSymbolicEnumValues(classId: ClassId): List = - symbolicEnumValues.filter { it.type.sootClass.superClassOrNull()?.id == classId } + symbolicEnumValues.filter { + val symbolicValueClassId = it.type.id + + // If symbolicValueClassId is not an enum in accordance with java.lang.Class.isEnum + // function, we have to take results for its superclass (a direct inheritor of java.lang.Enum). + // Otherwise, we should get results by its own classId. + val enumClass = if (symbolicValueClassId.isEnum) { + symbolicValueClassId + } else { + it.type.sootClass.superClassOrNull()?.id + } + + enumClass == classId + } } private fun initialArray(descriptor: MemoryChunkDescriptor) = @@ -430,9 +443,21 @@ data class MemoryUpdate( symbolicEnumValues = symbolicEnumValues.addAll(other.symbolicEnumValues), ) - // We check a superclass here since we added a superclass in here, not enum instances fun getSymbolicEnumValues(classId: ClassId): List = - symbolicEnumValues.filter { it.type.sootClass.superClassOrNull()?.id == classId } + symbolicEnumValues.filter { + val symbolicValueClassId = it.type.id + + // If symbolicValueClassId is not an enum in accordance with java.lang.Class.isEnum + // function, we have to take results for its superclass (a direct inheritor of java.lang.Enum). + // Otherwise, we should get results by its own classId. + val enumClass = if (symbolicValueClassId.isEnum) { + symbolicValueClassId + } else { + it.type.sootClass.superClassOrNull()?.id + } + + enumClass == classId + } } // array - Java Array diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt index efe620ca49..280006fda8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt @@ -558,9 +558,19 @@ class Resolver( } val clazz = classLoader.loadClass(sootClass.name) + // If we have an actual type as an Enum instance (a direct inheritor of java.lang.Enum), + // we should construct it using information about corresponding ordinal. + // The right enum constant will be constructed due to constraints added to the solver. + if (clazz.isEnum) { + return constructEnum(concreteAddr, actualType, clazz) + } + + // But, if we have the actualType that is not a direct inheritor of java.lang.Enum, + // we have to check its ancestor instead, because we might be in a situation when + // we worked with an enum constant as a parameter, and now we have correctly calculated actual type. // Since actualType for enums represents its instances and enum constants are not actually - // enum instances, we have to check the defaultType below instead on the actual one - // whether is it an Enum or not + // enum instances (in accordance to java.lang.Class#isEnum), we have to check + // the defaultType below instead on the actual one whether is it an Enum or not. val defaultClazz = classLoader.loadClass(defaultType.sootClass.name) if (defaultClazz.isEnum) { 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 9ac55a8f11..7c54790524 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -2314,7 +2314,9 @@ class Traverser( queuedSymbolicStateUpdates += addrEq(addr, nullObjectAddr).asSoftConstraint() if (type.sootClass.isEnum) { - createEnum(type, addr) + // We don't know which enum constant should we create, so we + // have to create an instance of unknown type to support virtual invokes. + createEnum(type, addr, useConcreteType = false) } else { createObject(addr, type, useConcreteType = false, mockInfoGenerator) } @@ -2323,8 +2325,8 @@ class Traverser( else -> error("Can't create const from ${type::class}") } - private fun createEnum(type: RefType, addr: UtAddrExpression): ObjectValue { - val typeStorage = typeResolver.constructTypeStorage(type, useConcreteType = false) + private fun createEnum(type: RefType, addr: UtAddrExpression, useConcreteType: Boolean): ObjectValue { + val typeStorage = typeResolver.constructTypeStorage(type, useConcreteType) queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() From e13f6b5cb9ac10471f100bf7bc98c5ed26f3b415 Mon Sep 17 00:00:00 2001 From: Alexey Menshutin Date: Wed, 25 Jan 2023 12:33:48 +0300 Subject: [PATCH 3/3] Extract copy paste --- .../main/kotlin/org/utbot/engine/Memory.kt | 48 ++++++++----------- .../main/kotlin/org/utbot/engine/Traverser.kt | 1 + 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index ecb45a0871..26100d8853 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -359,20 +359,7 @@ data class Memory( // TODO: split purely symbolic memory and information about s fun findTypeForArrayOrNull(addr: UtAddrExpression): ArrayType? = addrToArrayType[addr] fun getSymbolicEnumValues(classId: ClassId): List = - symbolicEnumValues.filter { - val symbolicValueClassId = it.type.id - - // If symbolicValueClassId is not an enum in accordance with java.lang.Class.isEnum - // function, we have to take results for its superclass (a direct inheritor of java.lang.Enum). - // Otherwise, we should get results by its own classId. - val enumClass = if (symbolicValueClassId.isEnum) { - symbolicValueClassId - } else { - it.type.sootClass.superClassOrNull()?.id - } - - enumClass == classId - } + extractSymbolicEnumValues(symbolicEnumValues, classId) } private fun initialArray(descriptor: MemoryChunkDescriptor) = @@ -444,20 +431,7 @@ data class MemoryUpdate( ) fun getSymbolicEnumValues(classId: ClassId): List = - symbolicEnumValues.filter { - val symbolicValueClassId = it.type.id - - // If symbolicValueClassId is not an enum in accordance with java.lang.Class.isEnum - // function, we have to take results for its superclass (a direct inheritor of java.lang.Enum). - // Otherwise, we should get results by its own classId. - val enumClass = if (symbolicValueClassId.isEnum) { - symbolicValueClassId - } else { - it.type.sootClass.superClassOrNull()?.id - } - - enumClass == classId - } + extractSymbolicEnumValues(symbolicEnumValues, classId) } // array - Java Array @@ -598,3 +572,21 @@ private operator fun MockInfoEnriched.plus(update: MockInfoEnriched?): MockInfoE private fun MutableMap>.mergeValues(other: Map>): Map> = apply { other.forEach { (key, values) -> merge(key, values) { v1, v2 -> v1 + v2 } } } + +private fun extractSymbolicEnumValues( + symbolicEnumValuesSource: PersistentList, + classId: ClassId +): List = symbolicEnumValuesSource.filter { + val symbolicValueClassId = it.type.id + + // If symbolicValueClassId is not an enum in accordance with java.lang.Class.isEnum + // function, we have to take results for its superclass (a direct inheritor of java.lang.Enum). + // Otherwise, we should get results by its own classId. + val enumClass = if (symbolicValueClassId.isEnum) { + symbolicValueClassId + } else { + it.type.sootClass.superClassOrNull()?.id + } + + enumClass == classId +} \ No newline at end of file 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 7c54790524..3ae7ca31d8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -2342,6 +2342,7 @@ class Traverser( return ObjectValue(typeStorage, addr) } + @Suppress("SameParameterValue") private fun arrayUpdate(array: ArrayValue, index: PrimitiveValue, value: UtExpression): MemoryUpdate { val type = array.type val chunkId = typeRegistry.arrayChunkId(type)