Skip to content

Add more correct type resolving for collections and maps #2610

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
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,11 +1,13 @@
package org.utbot.fuzzing.providers

import com.google.common.reflect.TypeToken
import org.utbot.framework.plugin.api.*
import org.utbot.framework.plugin.api.util.*
import org.utbot.fuzzer.FuzzedType
import org.utbot.fuzzer.FuzzedValue
import org.utbot.fuzzer.IdGenerator
import org.utbot.fuzzer.fuzzed
import org.utbot.fuzzer.jType
import org.utbot.fuzzing.*
import org.utbot.fuzzing.utils.hex
import kotlin.reflect.KClass
Expand Down Expand Up @@ -80,9 +82,9 @@ class EmptyCollectionValueProvider(
class MapValueProvider(
idGenerator: IdGenerator<Int>
) : CollectionValueProvider(idGenerator, java.util.Map::class.id) {

override fun resolveType(description: FuzzedDescription, type: FuzzedType) = sequence {
val keyGeneric = type.generics.getOrNull(0) ?: FuzzedType(objectClassId)
val valueGeneric = type.generics.getOrNull(1) ?: FuzzedType(objectClassId)
val (keyGeneric, valueGeneric) = resolveGenericsOfSuperClass<Map<*, *>>(description, type)
when (type.classId) {
java.util.Map::class.id -> {
if (keyGeneric.classId isSubtypeOf Comparable::class) {
Expand All @@ -108,8 +110,9 @@ class MapValueProvider(
class ListSetValueProvider(
idGenerator: IdGenerator<Int>
) : CollectionValueProvider(idGenerator, java.util.Collection::class.id) {

override fun resolveType(description: FuzzedDescription, type: FuzzedType) = sequence {
val generic = type.generics.firstOrNull() ?: FuzzedType(objectClassId)
val (generic) = resolveGenericsOfSuperClass<Collection<*>>(description, type)
when (type.classId) {
java.util.Queue::class.id,
java.util.Deque::class.id-> {
Expand Down Expand Up @@ -222,7 +225,7 @@ class IteratorValueProvider(val idGenerator: IdGenerator<Int>) : JavaValueProvid
}

override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence<Seed<FuzzedType, FuzzedValue>> {
val generic = type.generics.firstOrNull() ?: FuzzedType(objectClassId)
val (generic) = resolveGenericsOfSuperClass<Iterator<*>>(description, type)
return sequenceOf(Seed.Recursive(
construct = Routine.Create(listOf(FuzzedType(iterableClassId, listOf(generic)))) { v ->
val id = idGenerator.createId()
Expand Down Expand Up @@ -261,4 +264,21 @@ class IteratorValueProvider(val idGenerator: IdGenerator<Int>) : JavaValueProvid
}
))
}
}

private inline fun <reified T> resolveGenericsOfSuperClass(
description: FuzzedDescription,
type: FuzzedType,
): List<FuzzedType> {
val superClass = T::class.java
return try {
check(superClass.isAssignableFrom(type.classId.jClass)) { "$superClass isn't super class of $type" }
@Suppress("UNCHECKED_CAST")
toFuzzerType(
TypeToken.of(type.jType).getSupertype(superClass as Class<in Any>).type,
description.typeCache
).generics
} catch (e: Throwable) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think e should be logged here, since it should never be thrown if everything goes right. Also, without logging check(superClass.isAssignableFrom(type.classId.jClass)) is pretty much useless.

superClass.typeParameters.map { toFuzzerType(it, description.typeCache) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.utbot.fuzzing.samples;

import java.util.HashMap;

public class ConcreateMap<V extends Number> extends HashMap<String, V> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.utbot.fuzzing.samples;

import java.util.ArrayList;
import java.util.Collection;

public class ConcreteList extends ArrayList<Collection<String>> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.utbot.fuzzing

import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.utbot.framework.plugin.api.Instruction
import org.utbot.framework.plugin.api.util.collectionClassId
import org.utbot.framework.plugin.api.util.id
import org.utbot.framework.plugin.api.util.stringClassId
import org.utbot.framework.plugin.api.util.voidClassId
import org.utbot.fuzzer.FuzzedMethodDescription
import org.utbot.fuzzer.FuzzedType
import org.utbot.fuzzing.providers.ListSetValueProvider
import org.utbot.fuzzing.providers.MapValueProvider
import org.utbot.fuzzing.samples.ConcreateMap
import org.utbot.fuzzing.samples.ConcreteList
import org.utbot.fuzzing.utils.Trie
import java.lang.reflect.Type
import kotlin.random.Random

fun emptyFuzzerDescription(typeCache: MutableMap<Type, FuzzedType>) = FuzzedDescription(
FuzzedMethodDescription("no name", voidClassId, emptyList()),
Trie(Instruction::id),
typeCache,
Random(42)
)

class JavaValueProviderTest {

@Test
fun `collection value provider correctly resolves types for concrete types of map`() {
val typeCache = mutableMapOf<Type, FuzzedType>()
runBlockingWithContext {
val seed = MapValueProvider(TestIdentityPreservingIdGenerator).generate(
emptyFuzzerDescription(typeCache),
toFuzzerType(ConcreateMap::class.java, typeCache)
).first()
val collection = seed as Seed.Collection
val types = collection.modify.types
Assertions.assertEquals(2, types.size)
Assertions.assertEquals(types[0].classId, stringClassId)
Assertions.assertEquals(types[1].classId, java.lang.Number::class.java.id)
}
}

@Test
fun `collection value provider correctly resolves types for concrete types of list`() {
val typeCache = mutableMapOf<Type, FuzzedType>()
runBlockingWithContext {
val seed = ListSetValueProvider(TestIdentityPreservingIdGenerator).generate(
emptyFuzzerDescription(typeCache),
toFuzzerType(ConcreteList::class.java, typeCache)
).first()
val collection = seed as Seed.Collection
val types = collection.modify.types
Assertions.assertEquals(1, types.size)
Assertions.assertEquals(types[0].classId, collectionClassId)
Assertions.assertEquals(1, types[0].generics.size)
Assertions.assertEquals(types[0].generics[0].classId, stringClassId)
}
}
}