Skip to content

Check if superclass or interfaces contain applicable types #2451

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 2 commits into from
Jul 26, 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
Expand Up @@ -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.
*
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Type, FuzzedType>): Sequence<FuzzedType> = 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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class AbstractsObjectValueProvider(

override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence<Seed<FuzzedType, FuzzedValue>> {
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
Expand All @@ -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<T> implements A<T>`.
// Should be reworked with accurate generic matching between all classes.
toFuzzerType(it, description.typeCache).traverseHierarchy(description.typeCache).contains(type)
}
} catch (ignore: Throwable) {
return false
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.utbot.fuzzing.samples;

public final class Implementations {
public interface A<T> {
T getValue();
}

public static class AString implements A<String> {

private final String value;

public AString(String value) {
this.value = value;
}

@Override
public String getValue() {
return value;
}
}

public static class AInteger implements A<Integer> {

private final Integer value;

public AInteger(Integer value) {
this.value = value;
}

@Override
public Integer getValue() {
return value;
}
}

@SuppressWarnings("unused")
public static int test(A<Integer> value) {
if (value.getValue() < 0) {
return 0;
}
return value.getValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Type, FuzzedType>()
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<FuzzedType>()
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<Type, FuzzedType>()
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>, 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<Type, FuzzedType>()
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>, 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<T>[]"
})
assertTrue(cache.keys.any { t ->
t is GenericArrayType
&& t.typeName == "java.util.List<T>[][]"
})
}
}

@Test
fun `run complex type dependency call`() {
runBlockingWithContext {
val cache = HashMap<Type, FuzzedType>()
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
Expand Down Expand Up @@ -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 <T> runBlockingWithContext(block: suspend () -> T) : T {
return withUtContext(UtContext(this::class.java.classLoader)) {
runBlocking {
withTimeout(10000) {
block()
}
}
}
}
}

class MarkerValueProvider<T, R, D : Description<T>>(
Expand Down
Loading