Skip to content

Add support for virtual invokes on enums #1746

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 3 commits into from
Jan 25, 2023
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
@@ -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

Expand Down Expand Up @@ -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,
Expand All @@ -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<NullPointerException>() },
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,
Expand All @@ -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,
Expand All @@ -111,21 +109,35 @@ class ClassWithEnumTest : UtValueTestCaseChecker(testClass = ClassWithEnum::clas
ClassWithEnum::changingStaticWithEnumInit,
eq(1),
{ t, staticsAfter, r ->
// for some reasons x is inaccessible
val x = FieldId(t.javaClass.id, "x").jField.get(t) as Int
// 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

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)) },
)
Expand All @@ -134,7 +146,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 },
Expand All @@ -145,22 +157,19 @@ 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 },
)
}

@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 },
)
}

Expand Down
25 changes: 22 additions & 3 deletions utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ 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 org.utbot.framework.plugin.api.util.isEnum
import soot.ArrayType
import soot.CharType
import soot.IntType
Expand Down Expand Up @@ -358,7 +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<ObjectValue> =
symbolicEnumValues.filter { it.type.classId == classId }
extractSymbolicEnumValues(symbolicEnumValues, classId)
}

private fun initialArray(descriptor: MemoryChunkDescriptor) =
Expand Down Expand Up @@ -430,7 +431,7 @@ data class MemoryUpdate(
)

fun getSymbolicEnumValues(classId: ClassId): List<ObjectValue> =
symbolicEnumValues.filter { it.type.classId == classId }
extractSymbolicEnumValues(symbolicEnumValues, classId)
}

// array - Java Array
Expand Down Expand Up @@ -571,3 +572,21 @@ private operator fun MockInfoEnriched.plus(update: MockInfoEnriched?): MockInfoE
private fun <K, V> MutableMap<K, List<V>>.mergeValues(other: Map<K, List<V>>): Map<K, List<V>> = apply {
other.forEach { (key, values) -> merge(key, values) { v1, v2 -> v1 + v2 } }
}

private fun extractSymbolicEnumValues(
symbolicEnumValuesSource: PersistentList<ObjectValue>,
classId: ClassId
): List<ObjectValue> = 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
}
16 changes: 15 additions & 1 deletion utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -558,11 +558,25 @@ 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 (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) {
return constructEnum(concreteAddr, actualType, defaultClazz)
}

// check if we have mock with this address
// Unfortunately we cannot do it in constructModel only cause constructTypeOrNull returns null for mocks
val mockInfo = mockInfos[concreteAddr]
Expand Down
9 changes: 6 additions & 3 deletions utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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 = true)
private fun createEnum(type: RefType, addr: UtAddrExpression, useConcreteType: Boolean): ObjectValue {
val typeStorage = typeResolver.constructTypeStorage(type, useConcreteType)

queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint()

Expand All @@ -2340,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -137,6 +156,8 @@ static StatusEnum fromIsReady(boolean isReady) {
int publicGetCode() {
return this == READY ? 10 : -10;
}

abstract int virtualFunction();
}

enum ManyConstantsEnum {
Expand Down