Skip to content

Commit

Permalink
Improve mock type inference in fuzzer
Browse files Browse the repository at this point in the history
  • Loading branch information
IlyaMuravjov committed Aug 21, 2023
1 parent 4d3dda4 commit acde078
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import org.utbot.instrumentation.instrumentation.execution.mock.MethodMockContro
import org.utbot.instrumentation.instrumentation.execution.mock.MockController
import org.utbot.instrumentation.process.runSandbox
import java.lang.reflect.Modifier
import java.lang.reflect.TypeVariable
import java.security.AccessController
import java.security.PrivilegedAction
import java.util.*
Expand Down Expand Up @@ -280,22 +281,25 @@ class InstrumentationContextAwareValueConstructor(

private val dynamicMockModelToDepth = mutableMapOf<UtCompositeModel, Int>()

private fun generateNewAnswerModel(executableId: ExecutableId, depth: Int) =
executableId.returnType.defaultValueModel().takeUnless { it.isNull() } ?: when {
private fun generateNewAnswerModel(methodId: MethodId, depth: Int) =
methodId.returnType.defaultValueModel().takeUnless { it.isNull() } ?: when {
// use `null` to avoid false positive `ClassCastException`
methodId.method.genericReturnType is TypeVariable<*> -> UtNullModel(methodId.returnType)

// mockito can't mock `String` and `Class`
executableId.returnType == stringClassId -> UtNullModel(stringClassId)
executableId.returnType == classClassId -> UtClassRefModel(
methodId.returnType == stringClassId -> UtNullModel(stringClassId)
methodId.returnType == classClassId -> UtClassRefModel(
id = idGenerator.createId(),
classId = classClassId,
value = classClassId,
)
depth > MAX_DYNAMIC_MOCK_DEPTH -> UtNullModel(executableId.classId)
depth > MAX_DYNAMIC_MOCK_DEPTH -> UtNullModel(methodId.classId)

else -> UtCompositeModel(
id = idGenerator.createId(),
// TODO mockito can't mock sealed interfaces,
// we have to mock their implementations or use null
classId = executableId.returnType,
classId = methodId.returnType,
isMock = true,
canHaveRedundantOrMissingMocks = true,
).also { dynamicMockModelToDepth[it] = depth + 1 }
Expand Down
2 changes: 2 additions & 0 deletions utbot-java-fuzzing/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
val sootVersion: String by rootProject
val kotlinLoggingVersion: String by rootProject
val rgxgenVersion: String by rootProject
val guavaVersion: String by rootProject

dependencies {
implementation(project(":utbot-framework-api"))
Expand All @@ -12,4 +13,5 @@ dependencies {
}
implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion)
implementation(group = "com.github.curious-odd-man", name = "rgxgen", version = rgxgenVersion)
implementation(group = "com.google.guava", name = "guava", version = guavaVersion)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.utbot.fuzzing.spring.unit

import com.google.common.reflect.TypeResolver
import mu.KotlinLogging
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.MethodId
Expand All @@ -17,6 +18,9 @@ import org.utbot.fuzzing.Routine
import org.utbot.fuzzing.Scope
import org.utbot.fuzzing.ScopeProperty
import org.utbot.fuzzing.Seed
import org.utbot.fuzzing.spring.utils.jType
import org.utbot.fuzzing.spring.utils.toTypeParametrizedByTypeVariables
import org.utbot.fuzzing.spring.utils.typeToken
import org.utbot.fuzzing.toFuzzerType

val methodsToMockProperty = ScopeProperty<Set<MethodId>>(
Expand All @@ -28,6 +32,7 @@ class MockValueProvider(private val idGenerator: IdGenerator<Int>) : JavaValuePr
companion object {
private val logger = KotlinLogging.logger {}
private val loggedMockedMethods = mutableSetOf<MethodId>()
private val loggedUnresolvedMethods = mutableSetOf<MethodId>()
}

private val methodsToMock = mutableSetOf<MethodId>()
Expand All @@ -44,12 +49,26 @@ class MockValueProvider(private val idGenerator: IdGenerator<Int>) : JavaValuePr
construct = Routine.Create(types = emptyList()) { emptyMockFuzzedValue(type.classId) },
empty = Routine.Empty { emptyMockFuzzedValue(type.classId) },
modify = (description.scope?.getProperty(methodsToMockProperty)?.asSequence() ?: emptySequence()).map { methodId ->
if (loggedMockedMethods.add(methodId))
logger.info { "Actually mocked $methodId for the first time" }
// TODO accept `List<returnType>` instead of singular `returnType`
Routine.Call(types = listOf(
toFuzzerType(methodId.method.genericReturnType, description.typeCache)
)) { instance, (value) ->
val methodDeclaringClass = methodId.classId.jClass

val returnType = try {
TypeResolver().where(
methodDeclaringClass.toTypeParametrizedByTypeVariables(),
@Suppress("UNCHECKED_CAST")
type.jType.typeToken.getSupertype(methodDeclaringClass as Class<in Any>).type
).resolveType(methodId.method.genericReturnType)
} catch (e: Exception) {
if (loggedUnresolvedMethods.add(methodId))
logger.error(e) { "Failed to resolve return type for $methodId, using unresolved generic type" }

methodId.method.genericReturnType
}

// TODO accept `List<resolvedReturnType>` instead of singular `resolvedReturnType`
Routine.Call(types = listOf(toFuzzerType(returnType, description.typeCache))) { instance, (value) ->
if (loggedMockedMethods.add(methodId))
logger.info { "Actually mocked $methodId for the first time" }

(instance.model as UtCompositeModel).mocks[methodId] = listOf(value.model)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.utbot.fuzzing.spring.utils

import com.google.common.reflect.TypeToken
import org.utbot.framework.plugin.api.util.isArray
import org.utbot.framework.plugin.api.util.jClass
import org.utbot.fuzzer.FuzzedType
import java.lang.reflect.GenericArrayType
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type

val Type.typeToken: TypeToken<*> get() = TypeToken.of(this)

val FuzzedType.jType: Type get() = toType(mutableMapOf())

private fun FuzzedType.toType(cache: MutableMap<FuzzedType, Type>): Type = cache.getOrPut(this) {
when {
generics.isEmpty() -> classId.jClass
classId.isArray && generics.size == 1 -> GenericArrayType { generics.single().toType(cache) }
else -> object : ParameterizedType {
override fun getActualTypeArguments(): Array<Type> =
generics.map { it.toType(cache) }.toTypedArray()

override fun getRawType(): Type =
classId.jClass

override fun getOwnerType(): Type? = null
}
}
}

/**
* Returns fully parameterized type, e.g. for `Map` class
* `Map<K, V>` type is returned, where `K` and `V` are type variables.
*/
fun Class<*>.toTypeParametrizedByTypeVariables(): Type =
if (typeParameters.isEmpty()) this
else object : ParameterizedType {
override fun getActualTypeArguments(): Array<Type> =
typeParameters.toList().toTypedArray()

override fun getRawType(): Type =
this@toTypeParametrizedByTypeVariables

override fun getOwnerType(): Type? =
declaringClass?.toTypeParametrizedByTypeVariables()
}

0 comments on commit acde078

Please sign in to comment.