diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index cd6c36939e..a3fa74b66f 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -872,8 +872,6 @@ val Type.classId: ClassId else -> error("Unknown type $this") } -private val logger = KotlinLogging.logger {} - /** * Class id. Contains name, not a full qualified name. * @@ -898,9 +896,7 @@ open class ClassId @JvmOverloads constructor( get() = jClass.modifiers open val canonicalName: String - get() = jClass.canonicalName ?: name.also { - logger.error("ClassId $name does not have canonical name") - } + get() = jClass.canonicalName ?: error("ClassId $name does not have canonical name") open val simpleName: String get() = jClass.simpleName diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt index f6cc458787..38565368e9 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt @@ -141,6 +141,29 @@ suspend fun runJavaFuzzing( } } +/** + * Traverse though type hierarchy of this fuzzed type. + * Ignores all set [FuzzedType.generics] of source type. + * + * todo Process types like `Fuzzed[Any, generics = T1, T2]` to match those T1 and T2 types with superclass and interfaces + */ +internal fun FuzzedType.traverseHierarchy(typeCache: MutableMap): Sequence = sequence { + val typeQueue = mutableListOf(this@traverseHierarchy) + var index = 0 + while (typeQueue.isNotEmpty()) { + val next = typeQueue.removeFirst() + if (index++ > 0) { + yield(next) + } + val jClass = next.classId.jClass + val superclass = jClass.genericSuperclass + if (superclass != null) { + typeQueue += toFuzzerType(superclass, typeCache) + } + typeQueue += jClass.genericInterfaces.asSequence().map { toFuzzerType(it, typeCache) } + } +} + /** * Resolve a fuzzer type that has class info and some generics. * diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt index 77612897c9..b244761b4a 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt @@ -143,7 +143,7 @@ class AbstractsObjectValueProvider( override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence> { val t = try { - Scene.v().getRefType(type.classId.canonicalName).sootClass + Scene.v().getRefType(type.classId.name).sootClass } catch (ignore: NoClassDefFoundError) { logger.error(ignore) { "Soot may be not initialized" } return@sequence @@ -164,7 +164,12 @@ class AbstractsObjectValueProvider( } val jClass = sc.id.jClass return isAccessible(jClass, description.description.packageName) && - jClass.declaredConstructors.any { isAccessible(it, description.description.packageName) } + jClass.declaredConstructors.any { isAccessible(it, description.description.packageName) } && + jClass.let { + // This won't work in case of implementations with generics like `Impl implements A`. + // Should be reworked with accurate generic matching between all classes. + toFuzzerType(it, description.typeCache).traverseHierarchy(description.typeCache).contains(type) + } } catch (ignore: Throwable) { return false } diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/Implementations.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/Implementations.java new file mode 100644 index 0000000000..8ad0b55a08 --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/Implementations.java @@ -0,0 +1,43 @@ +package org.utbot.fuzzing.samples; + +public final class Implementations { + public interface A { + T getValue(); + } + + public static class AString implements A { + + private final String value; + + public AString(String value) { + this.value = value; + } + + @Override + public String getValue() { + return value; + } + } + + public static class AInteger implements A { + + private final Integer value; + + public AInteger(Integer value) { + this.value = value; + } + + @Override + public Integer getValue() { + return value; + } + } + + @SuppressWarnings("unused") + public static int test(A value) { + if (value.getValue() < 0) { + return 0; + } + return value.getValue(); + } +} diff --git a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt index 10f6a8d2a9..360d706356 100644 --- a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt +++ b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt @@ -89,130 +89,6 @@ class JavaFuzzingTest { assertEquals(probe1, probe2) } - @Test - fun `recursive generic types are recognized correctly`() { - runBlockingWithContext { - val methods = Stubs::class.java.methods - val method = methods.first { it.name == "resolve" && it.returnType == Int::class.javaPrimitiveType } - val typeCache = IdentityHashMap() - val type = toFuzzerType(method.genericParameterTypes.first(), typeCache) - assertEquals(1, typeCache.size) - assertTrue(typeCache.values.all { type === it }) - assertEquals(1, type.generics.size) - assertTrue(typeCache.values.all { type.generics[0] === it }) - - try { - // If FuzzerType has implemented `equals` and `hashCode` or is data class, - // that implements those methods implicitly, - // then adding it to hash table throws [StackOverflowError] - val set = HashSet() - set += type - } catch (soe: StackOverflowError) { - fail("Looks like FuzzerType implements equals and hashCode, " + - "which leads unstable behaviour in recursive generics ", soe) - } - } - } - - @Test - fun `can pass types through`() { - runBlockingWithContext { - val cache = HashMap() - val methods = Stubs::class.java.methods - val method = methods.first { it.name == "types" } - val types = method.genericParameterTypes.map { - toFuzzerType(it, cache) - } - assertEquals(3, cache.size) { "Cache should contain following types: List, Number and T[] for $method" } - assertTrue(cache.keys.any { t -> - t is Class<*> && t == java.lang.Number::class.java - }) - assertTrue(cache.keys.any { t -> - t is ParameterizedType - && t.rawType == java.util.List::class.java - && t.actualTypeArguments.size == 1 - && t.actualTypeArguments.first() == java.lang.Number::class.java - }) - assertTrue(cache.keys.any { t -> - t is GenericArrayType - && t.typeName == "T[]" - }) - } - } - - @Test - fun `arrays with generics can be resolved`() { - runBlockingWithContext { - val cache = HashMap() - val methods = Stubs::class.java.methods - val method = methods.first { it.name == "arrayLength" } - method.genericParameterTypes.map { - toFuzzerType(it, cache) - } - assertEquals(4, cache.size) { "Cache should contain following types: List, Number and T[] for $method" } - assertTrue(cache.keys.any { t -> - t is Class<*> && t == java.lang.Number::class.java - }) - assertTrue(cache.keys.any { t -> - t is ParameterizedType - && t.rawType == java.util.List::class.java - && t.actualTypeArguments.size == 1 - && t.actualTypeArguments.first().typeName == "T" - }) - assertTrue(cache.keys.any { t -> - t is GenericArrayType - && t.typeName == "java.util.List[]" - }) - assertTrue(cache.keys.any { t -> - t is GenericArrayType - && t.typeName == "java.util.List[][]" - }) - } - } - - @Test - fun `run complex type dependency call`() { - runBlockingWithContext { - val cache = HashMap() - val methods = Stubs::class.java.methods - val method = methods.first { it.name == "example" } - val types = method.genericParameterTypes - assertTrue(types.size == 3 && types[0].typeName == "A" && types[1].typeName == "B" && types[2].typeName == "C") { "bad input parameters" } - method.genericParameterTypes.map { - toFuzzerType(it, cache) - } - assertEquals(4, cache.size) - val typeIterableB = cache[types[0].replaceWithUpperBoundUntilNotTypeVariable()]!! - val genericOfIterableB = with(typeIterableB) { - assertEquals(iterableClassId, classId) - assertEquals(1, generics.size) - generics[0] - } - val typeListA = cache[types[1].replaceWithUpperBoundUntilNotTypeVariable()]!! - val genericOfListA = with(typeListA) { - assertEquals(java.util.List::class.id, classId) - assertEquals(1, generics.size) - generics[0] - } - assertEquals(1, genericOfIterableB.generics.size) - assertEquals(1, genericOfListA.generics.size) - assertTrue(genericOfIterableB.generics[0] === typeIterableB) { "Because of recursive types generic of B must depend on B itself" } - assertTrue(genericOfListA.generics[0] === typeListA) { "Because of recursive types generic of A must depend on A itself" } - - val typeListC = cache[types[2].replaceWithUpperBoundUntilNotTypeVariable()]!! - val genericOfListC = with(typeListC) { - assertEquals(java.util.List::class.id, classId) - assertEquals(1, generics.size) - generics[0] - } - - assertEquals(1, genericOfListC.generics.size) - assertEquals(iterableClassId, genericOfListC.generics[0].classId) - assertTrue(genericOfListC.generics[0].generics[0] === typeListA) { "Generic of C must lead to type A" } - - } - } - @Test fun `fuzzing should not generate values of private classes`() { var exec = 0 @@ -353,16 +229,6 @@ class JavaFuzzingTest { assertNotEquals(0, valueProvider.generate) { "Generate is never called for ${valueProvider.name}" } assertEquals(0, executions) { "Execution must be never called, because of empty seed supply for ${valueProvider.name}" } } - - private fun runBlockingWithContext(block: suspend () -> T) : T { - return withUtContext(UtContext(this::class.java.classLoader)) { - runBlocking { - withTimeout(10000) { - block() - } - } - } - } } class MarkerValueProvider>( diff --git a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaTypesTest.kt b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaTypesTest.kt new file mode 100644 index 0000000000..5b6a5fbbfa --- /dev/null +++ b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaTypesTest.kt @@ -0,0 +1,173 @@ +package org.utbot.fuzzing + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.iterableClassId +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzing.samples.Implementations +import org.utbot.fuzzing.samples.Stubs +import java.lang.Number +import java.lang.reflect.GenericArrayType +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.util.* +import java.util.List +import kotlin.collections.HashMap +import kotlin.collections.HashSet + +class JavaTypesTest { + + @Test + fun `recursive generic types are recognized correctly`() { + runBlockingWithContext { + val methods = Stubs::class.java.methods + val method = methods.first { it.name == "resolve" && it.returnType == Int::class.javaPrimitiveType } + val typeCache = IdentityHashMap() + val type = toFuzzerType(method.genericParameterTypes.first(), typeCache) + Assertions.assertEquals(1, typeCache.size) + Assertions.assertTrue(typeCache.values.all { type === it }) + Assertions.assertEquals(1, type.generics.size) + Assertions.assertTrue(typeCache.values.all { type.generics[0] === it }) + + try { + // If FuzzerType has implemented `equals` and `hashCode` or is data class, + // that implements those methods implicitly, + // then adding it to hash table throws [StackOverflowError] + val set = HashSet() + set += type + } catch (soe: StackOverflowError) { + Assertions.fail( + "Looks like FuzzerType implements equals and hashCode, " + + "which leads unstable behaviour in recursive generics ", soe + ) + } + } + } + + @Test + fun `can pass types through`() { + runBlockingWithContext { + val cache = HashMap() + val methods = Stubs::class.java.methods + val method = methods.first { it.name == "types" } + val types = method.genericParameterTypes.map { + toFuzzerType(it, cache) + } + Assertions.assertEquals( + 3, + cache.size + ) { "Cache should contain following types: List, Number and T[] for $method" } + Assertions.assertTrue(cache.keys.any { t -> + t is Class<*> && t == Number::class.java + }) + Assertions.assertTrue(cache.keys.any { t -> + t is ParameterizedType + && t.rawType == List::class.java + && t.actualTypeArguments.size == 1 + && t.actualTypeArguments.first() == Number::class.java + }) + Assertions.assertTrue(cache.keys.any { t -> + t is GenericArrayType + && t.typeName == "T[]" + }) + } + } + + @Test + fun `arrays with generics can be resolved`() { + runBlockingWithContext { + val cache = HashMap() + val methods = Stubs::class.java.methods + val method = methods.first { it.name == "arrayLength" } + method.genericParameterTypes.map { + toFuzzerType(it, cache) + } + Assertions.assertEquals( + 4, + cache.size + ) { "Cache should contain following types: List, Number and T[] for $method" } + Assertions.assertTrue(cache.keys.any { t -> + t is Class<*> && t == Number::class.java + }) + Assertions.assertTrue(cache.keys.any { t -> + t is ParameterizedType + && t.rawType == List::class.java + && t.actualTypeArguments.size == 1 + && t.actualTypeArguments.first().typeName == "T" + }) + Assertions.assertTrue(cache.keys.any { t -> + t is GenericArrayType + && t.typeName == "java.util.List[]" + }) + Assertions.assertTrue(cache.keys.any { t -> + t is GenericArrayType + && t.typeName == "java.util.List[][]" + }) + } + } + + @Test + fun `run complex type dependency call`() { + runBlockingWithContext { + val cache = HashMap() + val methods = Stubs::class.java.methods + val method = methods.first { it.name == "example" } + val types = method.genericParameterTypes + Assertions.assertTrue(types.size == 3 && types[0].typeName == "A" && types[1].typeName == "B" && types[2].typeName == "C") { "bad input parameters" } + method.genericParameterTypes.map { + toFuzzerType(it, cache) + } + Assertions.assertEquals(4, cache.size) + val typeIterableB = cache[types[0].replaceWithUpperBoundUntilNotTypeVariable()]!! + val genericOfIterableB = with(typeIterableB) { + Assertions.assertEquals(iterableClassId, classId) + Assertions.assertEquals(1, generics.size) + generics[0] + } + val typeListA = cache[types[1].replaceWithUpperBoundUntilNotTypeVariable()]!! + val genericOfListA = with(typeListA) { + Assertions.assertEquals(List::class.id, classId) + Assertions.assertEquals(1, generics.size) + generics[0] + } + Assertions.assertEquals(1, genericOfIterableB.generics.size) + Assertions.assertEquals(1, genericOfListA.generics.size) + Assertions.assertTrue(genericOfIterableB.generics[0] === typeIterableB) { "Because of recursive types generic of B must depend on B itself" } + Assertions.assertTrue(genericOfListA.generics[0] === typeListA) { "Because of recursive types generic of A must depend on A itself" } + + val typeListC = cache[types[2].replaceWithUpperBoundUntilNotTypeVariable()]!! + val genericOfListC = with(typeListC) { + Assertions.assertEquals(List::class.id, classId) + Assertions.assertEquals(1, generics.size) + generics[0] + } + + Assertions.assertEquals(1, genericOfListC.generics.size) + Assertions.assertEquals(iterableClassId, genericOfListC.generics[0].classId) + Assertions.assertTrue(genericOfListC.generics[0].generics[0] === typeListA) { "Generic of C must lead to type A" } + } + } + + @Test + fun `can correctly gather hierarchy information`() { + runBlockingWithContext { + val cache = HashMap() + val methods = Implementations::class.java.methods + val method = methods.first { it.name == "test" } + val type = method.genericParameterTypes.map { + toFuzzerType(it, cache) + }.first() + + val badType = toFuzzerType(Implementations.AString::class.java, cache) + val badTypeHierarchy = badType.traverseHierarchy(cache).toSet() + Assertions.assertEquals(2, badTypeHierarchy.size) { "There's only one class (Object) and one interface should be found" } + Assertions.assertFalse(badTypeHierarchy.contains(type)) { "Bad type hierarchy should not contain tested type $type" } + + val goodType = toFuzzerType(Implementations.AInteger::class.java, cache) + val goodTypeHierarchy = goodType.traverseHierarchy(cache).toSet() + Assertions.assertEquals(2, goodTypeHierarchy.size) { "There's only one class (Object) and one interface should be found" } + Assertions.assertTrue(goodTypeHierarchy.contains(type)) { "Good type hierarchy should contain tested type $type" } + } + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/Utils.kt b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/Utils.kt new file mode 100644 index 0000000000..d9a7b10c7e --- /dev/null +++ b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/Utils.kt @@ -0,0 +1,16 @@ +package org.utbot.fuzzing + +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.withUtContext + +internal fun V.runBlockingWithContext(block: suspend () -> T) : T { + return withUtContext(UtContext(this!!::class.java.classLoader)) { + runBlocking { + withTimeout(10000) { + block() + } + } + } +} \ No newline at end of file