Skip to content
Merged
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ commonsIoVersion=2.8.0
kotlinLoggingVersion=1.8.3
ktorVersion=1.4.1
cliktVersion=3.2.0
guavaVersion=30.0-jre
guavaVersion=32.1.2-jre
apacheCommonsExecVersion=1.2
apacheCommonsTextVersion=1.9
rgxgenVersion=1.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.utbot.framework.plugin.api.UtLambdaModel
import org.utbot.framework.plugin.api.UtModel
import org.utbot.framework.plugin.api.UtNullModel
import org.utbot.framework.plugin.api.UtPrimitiveModel
import org.utbot.framework.plugin.api.UtReferenceModel
import org.utbot.framework.plugin.api.UtStatementModel
import org.utbot.framework.plugin.api.UtVoidModel

Expand All @@ -27,30 +28,35 @@ fun EnvironmentModels.calculateSize(): Int {

/**
* We assume that "size" for "common" models is 1, 0 for [UtVoidModel] (as they do not return anything) and
* [UtPrimitiveModel] and [UtNullModel] (we use them as literals in codegen), summarising for all statements for [UtAssembleModel] and
* summarising for all fields and mocks for [UtCompositeModel]. As [UtCompositeModel] could be recursive, we need to
* store it in [used]. Moreover, if we already calculate size for [this], it means that we will use already created
* variable by this model and do not need to create it again, so size should be equal to 0.
* [UtPrimitiveModel] and 3 for [UtNullModel] (we use them as literals in codegen), summarising for
* all statements for [UtAssembleModel] and summarising for all fields and mocks for [UtCompositeModel].
*
* As [UtCompositeModel] could be recursive, we need to store it in [used]. Moreover, if we already
* calculate size for [this], it means that we will use already created variable by this model and do not
* need to create it again, so size should be equal to 0.
*/
private fun UtModel.calculateSize(used: MutableSet<UtModel> = mutableSetOf()): Int {
private fun UtModel.calculateSize(used: MutableSet<UtReferenceModel> = mutableSetOf()): Int {
if (this in used) return 0

used += this
if (this is UtReferenceModel)
used += this

return when (this) {
is UtNullModel, is UtPrimitiveModel, UtVoidModel -> 0
// `null` is assigned positive size to encourage use of non-null values
is UtNullModel -> 3
is UtPrimitiveModel, UtVoidModel -> 0
is UtClassRefModel, is UtEnumConstantModel, is UtArrayModel, is UtCustomModel -> 1
is UtAssembleModel -> {
1 + instantiationCall.calculateSize(used) + modificationsChain.sumOf { it.calculateSize(used) }
}
is UtCompositeModel -> 1 + fields.values.sumOf { it.calculateSize(used) }
is UtCompositeModel -> 1 + (fields.values + mocks.values.flatten()).sumOf { it.calculateSize(used) }
is UtLambdaModel -> 1 + capturedValues.sumOf { it.calculateSize(used) }
// PythonModel, JsUtModel, UtSpringContextModel may be here
else -> 0
}
}

private fun UtStatementModel.calculateSize(used: MutableSet<UtModel> = mutableSetOf()): Int =
private fun UtStatementModel.calculateSize(used: MutableSet<UtReferenceModel> = mutableSetOf()): Int =
when (this) {
is UtExecutableCallModel -> 1 + params.sumOf { it.calculateSize(used) } + (instance?.calculateSize(used) ?: 0)
is UtDirectSetFieldModel -> 1 + fieldModel.calculateSize(used) + instance.calculateSize(used)
Expand Down
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
Expand Up @@ -177,7 +177,7 @@ class AbstractsObjectValueProvider(
override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence<Seed<FuzzedType, FuzzedValue>> {
val t = try {
Scene.v().getRefType(type.classId.name).sootClass
} catch (ignore: NoClassDefFoundError) {
} catch (ignore: Throwable) {
logger.error(ignore) { "Soot may be not initialized" }
return@sequence
}
Expand Down
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()
}