Skip to content

Commit ace9742

Browse files
authored
Improve mock type inference in fuzzer (#2536)
1 parent 2379473 commit ace9742

File tree

7 files changed

+100
-23
lines changed

7 files changed

+100
-23
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ commonsIoVersion=2.8.0
6060
kotlinLoggingVersion=1.8.3
6161
ktorVersion=1.4.1
6262
cliktVersion=3.2.0
63-
guavaVersion=30.0-jre
63+
guavaVersion=32.1.2-jre
6464
apacheCommonsExecVersion=1.2
6565
apacheCommonsTextVersion=1.9
6666
rgxgenVersion=1.3

utbot-framework/src/main/kotlin/org/utbot/framework/util/SizeUtils.kt

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.utbot.framework.plugin.api.UtLambdaModel
1414
import org.utbot.framework.plugin.api.UtModel
1515
import org.utbot.framework.plugin.api.UtNullModel
1616
import org.utbot.framework.plugin.api.UtPrimitiveModel
17+
import org.utbot.framework.plugin.api.UtReferenceModel
1718
import org.utbot.framework.plugin.api.UtStatementModel
1819
import org.utbot.framework.plugin.api.UtVoidModel
1920

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

2829
/**
2930
* We assume that "size" for "common" models is 1, 0 for [UtVoidModel] (as they do not return anything) and
30-
* [UtPrimitiveModel] and [UtNullModel] (we use them as literals in codegen), summarising for all statements for [UtAssembleModel] and
31-
* summarising for all fields and mocks for [UtCompositeModel]. As [UtCompositeModel] could be recursive, we need to
32-
* store it in [used]. Moreover, if we already calculate size for [this], it means that we will use already created
33-
* variable by this model and do not need to create it again, so size should be equal to 0.
31+
* [UtPrimitiveModel] and 2 for [UtNullModel] (we use them as literals in codegen), summarising for
32+
* all statements for [UtAssembleModel] and summarising for all fields and mocks for [UtCompositeModel].
33+
*
34+
* As [UtReferenceModel] could be recursive, we need to store it in [used]. Moreover, if we have already
35+
* calculated the size for [this] model and [this] is [UtReferenceModel], then in codegen we would have already
36+
* created variable for [this] model and do not need to create it again, so size should be equal to 0.
3437
*/
35-
private fun UtModel.calculateSize(used: MutableSet<UtModel> = mutableSetOf()): Int {
38+
private fun UtModel.calculateSize(used: MutableSet<UtReferenceModel> = mutableSetOf()): Int {
3639
if (this in used) return 0
3740

38-
used += this
41+
if (this is UtReferenceModel)
42+
used += this
3943

4044
return when (this) {
41-
is UtNullModel, is UtPrimitiveModel, UtVoidModel -> 0
45+
// `null` is assigned size of `2` to encourage use of empty mocks which have size of `1` over `null`s
46+
is UtNullModel -> 2
47+
is UtPrimitiveModel, UtVoidModel -> 0
4248
is UtClassRefModel, is UtEnumConstantModel, is UtArrayModel, is UtCustomModel -> 1
4349
is UtAssembleModel -> {
4450
1 + instantiationCall.calculateSize(used) + modificationsChain.sumOf { it.calculateSize(used) }
4551
}
46-
is UtCompositeModel -> 1 + fields.values.sumOf { it.calculateSize(used) }
52+
is UtCompositeModel -> 1 + (fields.values + mocks.values.flatten()).sumOf { it.calculateSize(used) }
4753
is UtLambdaModel -> 1 + capturedValues.sumOf { it.calculateSize(used) }
4854
// PythonModel, JsUtModel, UtSpringContextModel may be here
4955
else -> 0
5056
}
5157
}
5258

53-
private fun UtStatementModel.calculateSize(used: MutableSet<UtModel> = mutableSetOf()): Int =
59+
private fun UtStatementModel.calculateSize(used: MutableSet<UtReferenceModel> = mutableSetOf()): Int =
5460
when (this) {
5561
is UtExecutableCallModel -> 1 + params.sumOf { it.calculateSize(used) } + (instance?.calculateSize(used) ?: 0)
5662
is UtDirectSetFieldModel -> 1 + fieldModel.calculateSize(used) + instance.calculateSize(used)

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/InstrumentationContextAwareValueConstructor.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import org.utbot.instrumentation.instrumentation.execution.mock.MethodMockContro
5252
import org.utbot.instrumentation.instrumentation.execution.mock.MockController
5353
import org.utbot.instrumentation.process.runSandbox
5454
import java.lang.reflect.Modifier
55+
import java.lang.reflect.TypeVariable
5556
import java.security.AccessController
5657
import java.security.PrivilegedAction
5758
import java.util.*
@@ -280,22 +281,25 @@ class InstrumentationContextAwareValueConstructor(
280281

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

283-
private fun generateNewAnswerModel(executableId: ExecutableId, depth: Int) =
284-
executableId.returnType.defaultValueModel().takeUnless { it.isNull() } ?: when {
284+
private fun generateNewAnswerModel(methodId: MethodId, depth: Int) =
285+
methodId.returnType.defaultValueModel().takeUnless { it.isNull() } ?: when {
286+
// use `null` to avoid false positive `ClassCastException`
287+
methodId.method.genericReturnType is TypeVariable<*> -> UtNullModel(methodId.returnType)
288+
285289
// mockito can't mock `String` and `Class`
286-
executableId.returnType == stringClassId -> UtNullModel(stringClassId)
287-
executableId.returnType == classClassId -> UtClassRefModel(
290+
methodId.returnType == stringClassId -> UtNullModel(stringClassId)
291+
methodId.returnType == classClassId -> UtClassRefModel(
288292
id = idGenerator.createId(),
289293
classId = classClassId,
290294
value = classClassId,
291295
)
292-
depth > MAX_DYNAMIC_MOCK_DEPTH -> UtNullModel(executableId.classId)
296+
depth > MAX_DYNAMIC_MOCK_DEPTH -> UtNullModel(methodId.classId)
293297

294298
else -> UtCompositeModel(
295299
id = idGenerator.createId(),
296300
// TODO mockito can't mock sealed interfaces,
297301
// we have to mock their implementations or use null
298-
classId = executableId.returnType,
302+
classId = methodId.returnType,
299303
isMock = true,
300304
canHaveRedundantOrMissingMocks = true,
301305
).also { dynamicMockModelToDepth[it] = depth + 1 }

utbot-java-fuzzing/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
val sootVersion: String by rootProject
22
val kotlinLoggingVersion: String by rootProject
33
val rgxgenVersion: String by rootProject
4+
val guavaVersion: String by rootProject
45

56
dependencies {
67
implementation(project(":utbot-framework-api"))
@@ -12,4 +13,5 @@ dependencies {
1213
}
1314
implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion)
1415
implementation(group = "com.github.curious-odd-man", name = "rgxgen", version = rgxgenVersion)
16+
implementation(group = "com.google.guava", name = "guava", version = guavaVersion)
1517
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class AbstractsObjectValueProvider(
177177
override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence<Seed<FuzzedType, FuzzedValue>> {
178178
val t = try {
179179
Scene.v().getRefType(type.classId.name).sootClass
180-
} catch (ignore: NoClassDefFoundError) {
180+
} catch (ignore: Throwable) {
181181
logger.error(ignore) { "Soot may be not initialized" }
182182
return@sequence
183183
}

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/Mocks.kt

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.utbot.fuzzing.spring.unit
22

3+
import com.google.common.reflect.TypeResolver
34
import mu.KotlinLogging
45
import org.utbot.framework.plugin.api.ClassId
56
import org.utbot.framework.plugin.api.MethodId
@@ -17,6 +18,9 @@ import org.utbot.fuzzing.Routine
1718
import org.utbot.fuzzing.Scope
1819
import org.utbot.fuzzing.ScopeProperty
1920
import org.utbot.fuzzing.Seed
21+
import org.utbot.fuzzing.spring.utils.jType
22+
import org.utbot.fuzzing.spring.utils.toTypeParametrizedByTypeVariables
23+
import org.utbot.fuzzing.spring.utils.typeToken
2024
import org.utbot.fuzzing.toFuzzerType
2125

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

3338
private val methodsToMock = mutableSetOf<MethodId>()
@@ -44,12 +49,26 @@ class MockValueProvider(private val idGenerator: IdGenerator<Int>) : JavaValuePr
4449
construct = Routine.Create(types = emptyList()) { emptyMockFuzzedValue(type.classId) },
4550
empty = Routine.Empty { emptyMockFuzzedValue(type.classId) },
4651
modify = (description.scope?.getProperty(methodsToMockProperty)?.asSequence() ?: emptySequence()).map { methodId ->
47-
if (loggedMockedMethods.add(methodId))
48-
logger.info { "Actually mocked $methodId for the first time" }
49-
// TODO accept `List<returnType>` instead of singular `returnType`
50-
Routine.Call(types = listOf(
51-
toFuzzerType(methodId.method.genericReturnType, description.typeCache)
52-
)) { instance, (value) ->
52+
val methodDeclaringClass = methodId.classId.jClass
53+
54+
val returnType = try {
55+
TypeResolver().where(
56+
methodDeclaringClass.toTypeParametrizedByTypeVariables(),
57+
@Suppress("UNCHECKED_CAST")
58+
type.jType.typeToken.getSupertype(methodDeclaringClass as Class<in Any>).type
59+
).resolveType(methodId.method.genericReturnType)
60+
} catch (e: Exception) {
61+
if (loggedUnresolvedMethods.add(methodId))
62+
logger.error(e) { "Failed to resolve return type for $methodId, using unresolved generic type" }
63+
64+
methodId.method.genericReturnType
65+
}
66+
67+
// TODO accept `List<resolvedReturnType>` instead of singular `resolvedReturnType`
68+
Routine.Call(types = listOf(toFuzzerType(returnType, description.typeCache))) { instance, (value) ->
69+
if (loggedMockedMethods.add(methodId))
70+
logger.info { "Actually mocked $methodId for the first time" }
71+
5372
(instance.model as UtCompositeModel).mocks[methodId] = listOf(value.model)
5473
}
5574
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package org.utbot.fuzzing.spring.utils
2+
3+
import com.google.common.reflect.TypeToken
4+
import org.utbot.framework.plugin.api.util.isArray
5+
import org.utbot.framework.plugin.api.util.jClass
6+
import org.utbot.fuzzer.FuzzedType
7+
import java.lang.reflect.GenericArrayType
8+
import java.lang.reflect.ParameterizedType
9+
import java.lang.reflect.Type
10+
11+
val Type.typeToken: TypeToken<*> get() = TypeToken.of(this)
12+
13+
val FuzzedType.jType: Type get() = toType(cache = mutableMapOf())
14+
15+
private fun FuzzedType.toType(cache: MutableMap<FuzzedType, Type>): Type = cache.getOrPut(this) {
16+
when {
17+
generics.isEmpty() -> classId.jClass
18+
classId.isArray && generics.size == 1 -> GenericArrayType { generics.single().toType(cache) }
19+
else -> object : ParameterizedType {
20+
override fun getActualTypeArguments(): Array<Type> =
21+
generics.map { it.toType(cache) }.toTypedArray()
22+
23+
override fun getRawType(): Type =
24+
classId.jClass
25+
26+
override fun getOwnerType(): Type? = null
27+
}
28+
}
29+
}
30+
31+
/**
32+
* Returns fully parameterized type, e.g. for `Map` class
33+
* `Map<K, V>` type is returned, where `K` and `V` are type variables.
34+
*/
35+
fun Class<*>.toTypeParametrizedByTypeVariables(): Type =
36+
if (typeParameters.isEmpty()) this
37+
else object : ParameterizedType {
38+
override fun getActualTypeArguments(): Array<Type> =
39+
typeParameters.toList().toTypedArray()
40+
41+
override fun getRawType(): Type =
42+
this@toTypeParametrizedByTypeVariables
43+
44+
override fun getOwnerType(): Type? =
45+
declaringClass?.toTypeParametrizedByTypeVariables()
46+
}

0 commit comments

Comments
 (0)