Skip to content

Commit 540278e

Browse files
Markouttetamarinvs19
authored andcommitted
Pelevin/improve java fuzzing type system (#1594)
1 parent cc192a7 commit 540278e

File tree

16 files changed

+470
-56
lines changed

16 files changed

+470
-56
lines changed

utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set<FuzzedConcreteVa
7474
try {
7575
finder.find(graph, unit, value)
7676
} catch (e: Exception) {
77-
logger.warn(e) { "Cannot process constant value of type '${value.type}}'" }
77+
// ignore known values that don't have a value field and can be met
78+
if (value !is NullConstant) {
79+
logger.warn(e) { "Cannot process constant value of type '${value.type}'" }
80+
}
7881
emptyList()
7982
}
8083
}.let { result ->

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedType.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ import org.utbot.framework.plugin.api.ClassId
55
/**
66
* Contains information about classId and generic types, that should be applied.
77
*
8-
* Currently, there's some limitation for generics that are supported:
9-
* 1. Only concrete types and collections are supported
10-
* 2. No relative types like: `Map<T, V extends T>`
11-
*
12-
* Note, that this class can be replaced by API mechanism for collecting parametrized types,
8+
* Note that this class can be replaced by the API mechanism for collecting parametrized types,
139
* but at the moment it doesn't fully support all necessary operations.
1410
*
1511
* @see ClassId.typeParameters
1612
*/
17-
data class FuzzedType(
13+
class FuzzedType(
1814
val classId: ClassId,
1915
val generics: List<FuzzedType> = emptyList(),
20-
)
16+
) {
17+
override fun toString(): String {
18+
return "FuzzedType(classId=$classId, generics=${generics.map { it.classId }})"
19+
}
20+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* @deprecated Code is migrating to the new fuzzing platform.
3+
* @see org.utbot.fuzzing.Fuzzing
4+
* @see org.utbot.fuzzing.JavaLanguageKt#runJavaFuzzing
5+
*/
6+
@Deprecated
7+
package org.utbot.fuzzer;

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import org.utbot.fuzzer.IdentityPreservingIdGenerator
2424
import org.utbot.fuzzer.objects.FuzzerMockableMethodId
2525
import org.utbot.fuzzer.objects.assembleModel
2626
import org.utbot.fuzzing.providers.FieldDescription
27-
import org.utbot.fuzzing.providers.findAccessibleModifableFields
27+
import org.utbot.fuzzing.providers.findAccessibleModifiableFields
2828
import org.utbot.fuzzing.providers.isAccessible
2929

3030
/**
@@ -64,10 +64,10 @@ class ObjectModelProvider(
6464
// and mutate some fields. Only if there's no option next block
6565
// with empty constructor should be used.
6666
if (constructorId.parameters.isEmpty()) {
67-
val fields = findAccessibleModifableFields(constructorId.classId, description.packageName)
67+
val fields = findAccessibleModifiableFields(null, constructorId.classId, description.packageName)
6868
if (fields.isNotEmpty()) {
6969
yield(
70-
ModelConstructor(fields.map { FuzzedType(it.classId) }) {
70+
ModelConstructor(fields.map { it.type }) {
7171
generateModelsWithFieldsInitialization(constructorId, fields, it)
7272
}
7373
)
@@ -103,7 +103,7 @@ class ObjectModelProvider(
103103
constructorId.classId,
104104
field.setter.name,
105105
field.setter.returnType.id,
106-
listOf(field.classId),
106+
listOf(field.type.classId),
107107
mock = {
108108
field.getter?.let { g ->
109109
val getterMethodID = MethodId(

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

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.utbot.fuzzing.utils.Trie
1111
import java.lang.reflect.*
1212
import java.util.concurrent.TimeUnit
1313
import kotlin.system.measureNanoTime
14+
import kotlin.random.Random
1415

1516
private val logger = KotlinLogging.logger {}
1617

@@ -19,6 +20,8 @@ typealias JavaValueProvider = ValueProvider<FuzzedType, FuzzedValue, FuzzedDescr
1920
class FuzzedDescription(
2021
val description: FuzzedMethodDescription,
2122
val tracer: Trie<Instruction, *>,
23+
val typeCache: MutableMap<Type, FuzzedType>,
24+
val random: Random,
2225
) : Description<FuzzedType>(
2326
description.parameters.mapIndexed { index, classId ->
2427
description.fuzzerType(index) ?: FuzzedType(classId)
@@ -39,6 +42,7 @@ fun defaultValueProviders(idGenerator: IdentityPreservingIdGenerator<Int>) = lis
3942
EnumValueProvider(idGenerator),
4043
ListSetValueProvider(idGenerator),
4144
MapValueProvider(idGenerator),
45+
IteratorValueProvider(idGenerator),
4246
EmptyCollectionValueProvider(idGenerator),
4347
DateValueProvider(idGenerator),
4448
// NullValueProvider,
@@ -52,13 +56,18 @@ suspend fun runJavaFuzzing(
5256
providers: List<ValueProvider<FuzzedType, FuzzedValue, FuzzedDescription>> = defaultValueProviders(idGenerator),
5357
exec: suspend (thisInstance: FuzzedValue?, description: FuzzedDescription, values: List<FuzzedValue>) -> BaseFeedback<Trie.Node<Instruction>, FuzzedType, FuzzedValue>
5458
) {
59+
val random = Random(0)
5560
val classUnderTest = methodUnderTest.classId
5661
val name = methodUnderTest.classId.simpleName + "." + methodUnderTest.name
5762
val returnType = methodUnderTest.returnType
5863
val parameters = methodUnderTest.parameters
5964

65+
// For a concrete fuzzing run we need to track types we create.
66+
// Because of generics can be declared as recursive structures like `<T extends Iterable<T>>`,
67+
// we should track them by reference and do not call `equals` and `hashCode` recursively.
68+
val typeCache = hashMapOf<Type, FuzzedType>()
6069
/**
61-
* To fuzz this instance the class of it is added into head of parameters list.
70+
* To fuzz this instance, the class of it is added into head of parameters list.
6271
* Done for compatibility with old fuzzer logic and should be reworked more robust way.
6372
*/
6473
fun createFuzzedMethodDescription(self: ClassId?) = FuzzedMethodDescription(
@@ -79,9 +88,9 @@ suspend fun runJavaFuzzing(
7988
fuzzerType = {
8089
try {
8190
when {
82-
self != null && it == 0 -> toFuzzerType(methodUnderTest.executable.declaringClass)
83-
self != null -> toFuzzerType(methodUnderTest.executable.genericParameterTypes[it - 1])
84-
else -> toFuzzerType(methodUnderTest.executable.genericParameterTypes[it])
91+
self != null && it == 0 -> toFuzzerType(methodUnderTest.executable.declaringClass, typeCache)
92+
self != null -> toFuzzerType(methodUnderTest.executable.genericParameterTypes[it - 1], typeCache)
93+
else -> toFuzzerType(methodUnderTest.executable.genericParameterTypes[it], typeCache)
8594
}
8695
} catch (_: Throwable) {
8796
null
@@ -94,15 +103,15 @@ suspend fun runJavaFuzzing(
94103
if (!isStatic && !isConstructor) { classUnderTest } else { null }
95104
}
96105
val tracer = Trie(Instruction::id)
97-
val descriptionWithOptionalThisInstance = FuzzedDescription(createFuzzedMethodDescription(thisInstance), tracer)
98-
val descriptionWithOnlyParameters = FuzzedDescription(createFuzzedMethodDescription(null), tracer)
106+
val descriptionWithOptionalThisInstance = FuzzedDescription(createFuzzedMethodDescription(thisInstance), tracer, typeCache, random)
107+
val descriptionWithOnlyParameters = FuzzedDescription(createFuzzedMethodDescription(null), tracer, typeCache, random)
99108
try {
100109
logger.info { "Starting fuzzing for method: $methodUnderTest" }
101110
logger.info { "\tuse thisInstance = ${thisInstance != null}" }
102111
logger.info { "\tparameters = $parameters" }
103112
var totalExecutionCalled = 0
104113
val totalFuzzingTime = measureNanoTime {
105-
runFuzzing(ValueProvider.of(providers), descriptionWithOptionalThisInstance) { _, t ->
114+
runFuzzing(ValueProvider.of(providers), descriptionWithOptionalThisInstance, random) { _, t ->
106115
totalExecutionCalled++
107116
if (thisInstance == null) {
108117
exec(null, descriptionWithOnlyParameters, t)
@@ -118,22 +127,86 @@ suspend fun runJavaFuzzing(
118127
}
119128
}
120129

121-
private fun toFuzzerType(type: Type): FuzzedType {
130+
/**
131+
* Resolve a fuzzer type that has class info and some generics.
132+
*
133+
* @param type to be resolved
134+
* @param cache is used to store same [FuzzedType] for same java types
135+
*/
136+
internal fun toFuzzerType(type: Type, cache: MutableMap<Type, FuzzedType>): FuzzedType {
137+
return toFuzzerType(
138+
type = type,
139+
classId = { t -> toClassId(t, cache) },
140+
generics = { t -> toGenerics(t) },
141+
cache = cache,
142+
)
143+
}
144+
145+
/**
146+
* Resolve a fuzzer type that has class info and some generics.
147+
*
148+
* Cache is used to stop recursive call in case of some recursive class definition like:
149+
*
150+
* ```
151+
* public <T extends Iterable<T>> call(T type) { ... }
152+
* ```
153+
*
154+
* @param type to be resolved into a fuzzed type.
155+
* @param classId is a function that produces classId by general type.
156+
* @param generics is a function that produced a list of generics for this concrete type.
157+
* @param cache is used to store all generated types.
158+
*/
159+
private fun toFuzzerType(
160+
type: Type,
161+
classId: (type: Type) -> ClassId,
162+
generics: (parent: Type) -> Array<out Type>,
163+
cache: MutableMap<Type, FuzzedType>
164+
): FuzzedType {
165+
val g = mutableListOf<FuzzedType>()
166+
val t = type.replaceWithUpperBoundUntilNotTypeVariable()
167+
var target = cache[t]
168+
if (target == null) {
169+
target = FuzzedType(classId(t), g)
170+
cache[t] = target
171+
g += generics(t).map {
172+
toFuzzerType(it, classId, generics, cache)
173+
}
174+
}
175+
return target
176+
}
177+
178+
internal fun Type.replaceWithUpperBoundUntilNotTypeVariable() : Type {
179+
var type: Type = this
180+
while (type is TypeVariable<*>) {
181+
type = type.bounds.firstOrNull() ?: java.lang.Object::class.java
182+
}
183+
return type
184+
}
185+
186+
private fun toClassId(type: Type, cache: MutableMap<Type, FuzzedType>): ClassId {
122187
return when (type) {
123-
is WildcardType -> type.upperBounds.firstOrNull()?.let(::toFuzzerType) ?: FuzzedType(objectClassId)
124-
is TypeVariable<*> -> type.bounds.firstOrNull()?.let(::toFuzzerType) ?: FuzzedType(objectClassId)
125-
is ParameterizedType -> FuzzedType((type.rawType as Class<*>).id, type.actualTypeArguments.map { toFuzzerType(it) })
188+
is WildcardType -> type.upperBounds.firstOrNull()?.let { toClassId(it, cache) } ?: objectClassId
126189
is GenericArrayType -> {
127190
val genericComponentType = type.genericComponentType
128-
val fuzzerType = toFuzzerType(genericComponentType)
129-
val classId = if (genericComponentType !is GenericArrayType) {
130-
ClassId("[L${fuzzerType.classId.name};", fuzzerType.classId)
191+
val classId = toFuzzerType(genericComponentType, cache).classId
192+
if (genericComponentType !is GenericArrayType) {
193+
ClassId("[L${classId.name};", classId)
131194
} else {
132-
ClassId("[" + fuzzerType.classId.name, fuzzerType.classId)
195+
ClassId("[" + classId.name, classId)
133196
}
134-
FuzzedType(classId)
135197
}
136-
is Class<*> -> FuzzedType(type.id, type.typeParameters.map { toFuzzerType(it) })
137-
else -> error("Unknown type: $type")
198+
is ParameterizedType -> (type.rawType as Class<*>).id
199+
is Class<*> -> type.id
200+
else -> error("unknown type: $type")
201+
}
202+
}
203+
204+
private fun toGenerics(t: Type) : Array<out Type> {
205+
return when (t) {
206+
is WildcardType -> t.upperBounds.firstOrNull()?.let { toGenerics(it) } ?: emptyArray()
207+
is GenericArrayType -> arrayOf(t.genericComponentType)
208+
is ParameterizedType -> t.actualTypeArguments
209+
is Class<*> -> t.typeParameters
210+
else -> emptyArray()
138211
}
139212
}

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Arrays.kt

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ import org.utbot.fuzzer.FuzzedType
77
import org.utbot.fuzzer.FuzzedValue
88
import org.utbot.fuzzer.IdGenerator
99
import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed
10-
import org.utbot.fuzzing.FuzzedDescription
11-
import org.utbot.fuzzing.Routine
12-
import org.utbot.fuzzing.Seed
13-
import org.utbot.fuzzing.ValueProvider
10+
import org.utbot.fuzzing.*
1411

1512
class ArrayValueProvider(
1613
val idGenerator: IdGenerator<Int>,
@@ -35,9 +32,24 @@ class ArrayValueProvider(
3532
summary = "%var% = ${type.classId.elementClassId!!.simpleName}[$it]"
3633
}
3734
},
38-
modify = Routine.ForEach(listOf(FuzzedType(type.classId.elementClassId!!))) { self, i, values ->
35+
modify = Routine.ForEach(listOf(resolveArrayElementType(type))) { self, i, values ->
3936
(self.model as UtArrayModel).stores[i] = values.first().model
4037
}
4138
))
4239
}
40+
41+
/**
42+
* Resolves array's element type. In case a generic type is used, like `T[]` the type should pass generics.
43+
*
44+
* For example, List<Number>[] returns List<Number>.
45+
*/
46+
private fun resolveArrayElementType(arrayType: FuzzedType): FuzzedType = when {
47+
!arrayType.classId.isArray -> error("$arrayType is not array")
48+
arrayType.generics.size == 1 -> arrayType.generics.first()
49+
arrayType.generics.isEmpty() -> FuzzedType(
50+
arrayType.classId.elementClassId ?: error("Element classId of $arrayType is not found")
51+
)
52+
53+
else -> error("Array has ${arrayType.generics.size} generic type for ($arrayType), that should not happen")
54+
}
4355
}

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@ import org.utbot.fuzzer.FuzzedType
66
import org.utbot.fuzzer.FuzzedValue
77
import org.utbot.fuzzer.IdGenerator
88
import org.utbot.fuzzer.providers.PrimitivesModelProvider.fuzzed
9-
import org.utbot.fuzzing.FuzzedDescription
10-
import org.utbot.fuzzing.Routine
11-
import org.utbot.fuzzing.Seed
12-
import org.utbot.fuzzing.ValueProvider
9+
import org.utbot.fuzzing.*
10+
import org.utbot.fuzzing.utils.hex
1311
import kotlin.reflect.KClass
1412

1513
class EmptyCollectionValueProvider(
@@ -56,10 +54,25 @@ class EmptyCollectionValueProvider(
5654
classId = classId,
5755
modelName = "",
5856
instantiationCall = UtExecutableCallModel(null, executableId, value.map { it.model })
57+
).fuzzed {
58+
summary = "%var% = ${executableId.classId.simpleName}#${executableId.name}"
59+
}
60+
},
61+
empty = Routine.Empty {
62+
if (executableId.parameters.isEmpty()) {
63+
UtAssembleModel(
64+
id = idGenerator.createId(),
65+
classId = classId,
66+
modelName = "",
67+
instantiationCall = UtExecutableCallModel(null, executableId, emptyList())
5968

60-
).fuzzed()
69+
).fuzzed{
70+
summary = "%var% = ${executableId.classId.simpleName}#${executableId.name}"
71+
}
72+
} else {
73+
UtNullModel(classId).fuzzed { summary = "%var% = null" }
74+
}
6175
},
62-
empty = Routine.Empty { UtNullModel(classId).fuzzed { summary = "%var% = null" } }
6376
))
6477
}
6578
}
@@ -205,4 +218,51 @@ abstract class CollectionValueProvider(
205218
// return isSubtypeOf(another)
206219
return klass.java.isAssignableFrom(this.jClass)
207220
}
221+
}
222+
223+
class IteratorValueProvider(val idGenerator: IdGenerator<Int>) : ValueProvider<FuzzedType, FuzzedValue, FuzzedDescription> {
224+
override fun accept(type: FuzzedType): Boolean {
225+
return type.classId == Iterator::class.id
226+
}
227+
228+
override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence<Seed<FuzzedType, FuzzedValue>> {
229+
val generic = type.generics.firstOrNull() ?: FuzzedType(objectClassId)
230+
return sequenceOf(Seed.Recursive(
231+
construct = Routine.Create(listOf(FuzzedType(iterableClassId, listOf(generic)))) { v ->
232+
val id = idGenerator.createId()
233+
val iterable = when (val model = v.first().model) {
234+
is UtAssembleModel -> model
235+
is UtNullModel -> return@Create v.first()
236+
else -> error("Model can be only UtNullModel or UtAssembleModel, but $model is met")
237+
}
238+
UtAssembleModel(
239+
id = id,
240+
classId = type.classId,
241+
modelName = "iterator#${id.hex()}",
242+
instantiationCall = UtExecutableCallModel(
243+
instance = iterable,
244+
executable = MethodId(iterableClassId, "iterator", type.classId, emptyList()),
245+
params = emptyList()
246+
)
247+
).fuzzed {
248+
summary = "%var% = ${iterable.classId.simpleName}#iterator()"
249+
}
250+
},
251+
empty = Routine.Empty {
252+
val id = idGenerator.createId()
253+
UtAssembleModel(
254+
id = id,
255+
classId = type.classId,
256+
modelName = "emptyIterator#${id.hex()}",
257+
instantiationCall = UtExecutableCallModel(
258+
instance = null,
259+
executable = MethodId(java.util.Collections::class.id, "emptyIterator", type.classId, emptyList()),
260+
params = emptyList()
261+
)
262+
).fuzzed {
263+
summary = "%var% = empty iterator"
264+
}
265+
}
266+
))
267+
}
208268
}

0 commit comments

Comments
 (0)