Skip to content

Commit 339ef49

Browse files
authored
Check if superclass or interfaces contain applicable types (#2451)
1 parent 33cf8ad commit 339ef49

File tree

7 files changed

+263
-141
lines changed

7 files changed

+263
-141
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt

+1-5
Original file line numberDiff line numberDiff line change
@@ -872,8 +872,6 @@ val Type.classId: ClassId
872872
else -> error("Unknown type $this")
873873
}
874874

875-
private val logger = KotlinLogging.logger {}
876-
877875
/**
878876
* Class id. Contains name, not a full qualified name.
879877
*
@@ -898,9 +896,7 @@ open class ClassId @JvmOverloads constructor(
898896
get() = jClass.modifiers
899897

900898
open val canonicalName: String
901-
get() = jClass.canonicalName ?: name.also {
902-
logger.error("ClassId $name does not have canonical name")
903-
}
899+
get() = jClass.canonicalName ?: error("ClassId $name does not have canonical name")
904900

905901
open val simpleName: String get() = jClass.simpleName
906902

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt

+23
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,29 @@ suspend fun runJavaFuzzing(
141141
}
142142
}
143143

144+
/**
145+
* Traverse though type hierarchy of this fuzzed type.
146+
* Ignores all set [FuzzedType.generics] of source type.
147+
*
148+
* todo Process types like `Fuzzed[Any, generics = T1, T2]` to match those T1 and T2 types with superclass and interfaces
149+
*/
150+
internal fun FuzzedType.traverseHierarchy(typeCache: MutableMap<Type, FuzzedType>): Sequence<FuzzedType> = sequence {
151+
val typeQueue = mutableListOf(this@traverseHierarchy)
152+
var index = 0
153+
while (typeQueue.isNotEmpty()) {
154+
val next = typeQueue.removeFirst()
155+
if (index++ > 0) {
156+
yield(next)
157+
}
158+
val jClass = next.classId.jClass
159+
val superclass = jClass.genericSuperclass
160+
if (superclass != null) {
161+
typeQueue += toFuzzerType(superclass, typeCache)
162+
}
163+
typeQueue += jClass.genericInterfaces.asSequence().map { toFuzzerType(it, typeCache) }
164+
}
165+
}
166+
144167
/**
145168
* Resolve a fuzzer type that has class info and some generics.
146169
*

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt

+7-2
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ class AbstractsObjectValueProvider(
143143

144144
override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence<Seed<FuzzedType, FuzzedValue>> {
145145
val t = try {
146-
Scene.v().getRefType(type.classId.canonicalName).sootClass
146+
Scene.v().getRefType(type.classId.name).sootClass
147147
} catch (ignore: NoClassDefFoundError) {
148148
logger.error(ignore) { "Soot may be not initialized" }
149149
return@sequence
@@ -164,7 +164,12 @@ class AbstractsObjectValueProvider(
164164
}
165165
val jClass = sc.id.jClass
166166
return isAccessible(jClass, description.description.packageName) &&
167-
jClass.declaredConstructors.any { isAccessible(it, description.description.packageName) }
167+
jClass.declaredConstructors.any { isAccessible(it, description.description.packageName) } &&
168+
jClass.let {
169+
// This won't work in case of implementations with generics like `Impl<T> implements A<T>`.
170+
// Should be reworked with accurate generic matching between all classes.
171+
toFuzzerType(it, description.typeCache).traverseHierarchy(description.typeCache).contains(type)
172+
}
168173
} catch (ignore: Throwable) {
169174
return false
170175
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.utbot.fuzzing.samples;
2+
3+
public final class Implementations {
4+
public interface A<T> {
5+
T getValue();
6+
}
7+
8+
public static class AString implements A<String> {
9+
10+
private final String value;
11+
12+
public AString(String value) {
13+
this.value = value;
14+
}
15+
16+
@Override
17+
public String getValue() {
18+
return value;
19+
}
20+
}
21+
22+
public static class AInteger implements A<Integer> {
23+
24+
private final Integer value;
25+
26+
public AInteger(Integer value) {
27+
this.value = value;
28+
}
29+
30+
@Override
31+
public Integer getValue() {
32+
return value;
33+
}
34+
}
35+
36+
@SuppressWarnings("unused")
37+
public static int test(A<Integer> value) {
38+
if (value.getValue() < 0) {
39+
return 0;
40+
}
41+
return value.getValue();
42+
}
43+
}

utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt

-134
Original file line numberDiff line numberDiff line change
@@ -89,130 +89,6 @@ class JavaFuzzingTest {
8989
assertEquals(probe1, probe2)
9090
}
9191

92-
@Test
93-
fun `recursive generic types are recognized correctly`() {
94-
runBlockingWithContext {
95-
val methods = Stubs::class.java.methods
96-
val method = methods.first { it.name == "resolve" && it.returnType == Int::class.javaPrimitiveType }
97-
val typeCache = IdentityHashMap<Type, FuzzedType>()
98-
val type = toFuzzerType(method.genericParameterTypes.first(), typeCache)
99-
assertEquals(1, typeCache.size)
100-
assertTrue(typeCache.values.all { type === it })
101-
assertEquals(1, type.generics.size)
102-
assertTrue(typeCache.values.all { type.generics[0] === it })
103-
104-
try {
105-
// If FuzzerType has implemented `equals` and `hashCode` or is data class,
106-
// that implements those methods implicitly,
107-
// then adding it to hash table throws [StackOverflowError]
108-
val set = HashSet<FuzzedType>()
109-
set += type
110-
} catch (soe: StackOverflowError) {
111-
fail("Looks like FuzzerType implements equals and hashCode, " +
112-
"which leads unstable behaviour in recursive generics ", soe)
113-
}
114-
}
115-
}
116-
117-
@Test
118-
fun `can pass types through`() {
119-
runBlockingWithContext {
120-
val cache = HashMap<Type, FuzzedType>()
121-
val methods = Stubs::class.java.methods
122-
val method = methods.first { it.name == "types" }
123-
val types = method.genericParameterTypes.map {
124-
toFuzzerType(it, cache)
125-
}
126-
assertEquals(3, cache.size) { "Cache should contain following types: List<Number>, Number and T[] for $method" }
127-
assertTrue(cache.keys.any { t ->
128-
t is Class<*> && t == java.lang.Number::class.java
129-
})
130-
assertTrue(cache.keys.any { t ->
131-
t is ParameterizedType
132-
&& t.rawType == java.util.List::class.java
133-
&& t.actualTypeArguments.size == 1
134-
&& t.actualTypeArguments.first() == java.lang.Number::class.java
135-
})
136-
assertTrue(cache.keys.any { t ->
137-
t is GenericArrayType
138-
&& t.typeName == "T[]"
139-
})
140-
}
141-
}
142-
143-
@Test
144-
fun `arrays with generics can be resolved`() {
145-
runBlockingWithContext {
146-
val cache = HashMap<Type, FuzzedType>()
147-
val methods = Stubs::class.java.methods
148-
val method = methods.first { it.name == "arrayLength" }
149-
method.genericParameterTypes.map {
150-
toFuzzerType(it, cache)
151-
}
152-
assertEquals(4, cache.size) { "Cache should contain following types: List<Number>, Number and T[] for $method" }
153-
assertTrue(cache.keys.any { t ->
154-
t is Class<*> && t == java.lang.Number::class.java
155-
})
156-
assertTrue(cache.keys.any { t ->
157-
t is ParameterizedType
158-
&& t.rawType == java.util.List::class.java
159-
&& t.actualTypeArguments.size == 1
160-
&& t.actualTypeArguments.first().typeName == "T"
161-
})
162-
assertTrue(cache.keys.any { t ->
163-
t is GenericArrayType
164-
&& t.typeName == "java.util.List<T>[]"
165-
})
166-
assertTrue(cache.keys.any { t ->
167-
t is GenericArrayType
168-
&& t.typeName == "java.util.List<T>[][]"
169-
})
170-
}
171-
}
172-
173-
@Test
174-
fun `run complex type dependency call`() {
175-
runBlockingWithContext {
176-
val cache = HashMap<Type, FuzzedType>()
177-
val methods = Stubs::class.java.methods
178-
val method = methods.first { it.name == "example" }
179-
val types = method.genericParameterTypes
180-
assertTrue(types.size == 3 && types[0].typeName == "A" && types[1].typeName == "B" && types[2].typeName == "C") { "bad input parameters" }
181-
method.genericParameterTypes.map {
182-
toFuzzerType(it, cache)
183-
}
184-
assertEquals(4, cache.size)
185-
val typeIterableB = cache[types[0].replaceWithUpperBoundUntilNotTypeVariable()]!!
186-
val genericOfIterableB = with(typeIterableB) {
187-
assertEquals(iterableClassId, classId)
188-
assertEquals(1, generics.size)
189-
generics[0]
190-
}
191-
val typeListA = cache[types[1].replaceWithUpperBoundUntilNotTypeVariable()]!!
192-
val genericOfListA = with(typeListA) {
193-
assertEquals(java.util.List::class.id, classId)
194-
assertEquals(1, generics.size)
195-
generics[0]
196-
}
197-
assertEquals(1, genericOfIterableB.generics.size)
198-
assertEquals(1, genericOfListA.generics.size)
199-
assertTrue(genericOfIterableB.generics[0] === typeIterableB) { "Because of recursive types generic of B must depend on B itself" }
200-
assertTrue(genericOfListA.generics[0] === typeListA) { "Because of recursive types generic of A must depend on A itself" }
201-
202-
val typeListC = cache[types[2].replaceWithUpperBoundUntilNotTypeVariable()]!!
203-
val genericOfListC = with(typeListC) {
204-
assertEquals(java.util.List::class.id, classId)
205-
assertEquals(1, generics.size)
206-
generics[0]
207-
}
208-
209-
assertEquals(1, genericOfListC.generics.size)
210-
assertEquals(iterableClassId, genericOfListC.generics[0].classId)
211-
assertTrue(genericOfListC.generics[0].generics[0] === typeListA) { "Generic of C must lead to type A" }
212-
213-
}
214-
}
215-
21692
@Test
21793
fun `fuzzing should not generate values of private classes`() {
21894
var exec = 0
@@ -353,16 +229,6 @@ class JavaFuzzingTest {
353229
assertNotEquals(0, valueProvider.generate) { "Generate is never called for ${valueProvider.name}" }
354230
assertEquals(0, executions) { "Execution must be never called, because of empty seed supply for ${valueProvider.name}" }
355231
}
356-
357-
private fun <T> runBlockingWithContext(block: suspend () -> T) : T {
358-
return withUtContext(UtContext(this::class.java.classLoader)) {
359-
runBlocking {
360-
withTimeout(10000) {
361-
block()
362-
}
363-
}
364-
}
365-
}
366232
}
367233

368234
class MarkerValueProvider<T, R, D : Description<T>>(

0 commit comments

Comments
 (0)