@@ -11,6 +11,7 @@ import org.utbot.fuzzing.utils.Trie
11
11
import java.lang.reflect.*
12
12
import java.util.concurrent.TimeUnit
13
13
import kotlin.system.measureNanoTime
14
+ import kotlin.random.Random
14
15
15
16
private val logger = KotlinLogging .logger {}
16
17
@@ -19,6 +20,8 @@ typealias JavaValueProvider = ValueProvider<FuzzedType, FuzzedValue, FuzzedDescr
19
20
class FuzzedDescription (
20
21
val description : FuzzedMethodDescription ,
21
22
val tracer : Trie <Instruction , * >,
23
+ val typeCache : MutableMap <Type , FuzzedType >,
24
+ val random : Random ,
22
25
) : Description<FuzzedType>(
23
26
description.parameters.mapIndexed { index, classId ->
24
27
description.fuzzerType(index) ? : FuzzedType (classId)
@@ -39,6 +42,7 @@ fun defaultValueProviders(idGenerator: IdentityPreservingIdGenerator<Int>) = lis
39
42
EnumValueProvider (idGenerator),
40
43
ListSetValueProvider (idGenerator),
41
44
MapValueProvider (idGenerator),
45
+ IteratorValueProvider (idGenerator),
42
46
EmptyCollectionValueProvider (idGenerator),
43
47
DateValueProvider (idGenerator),
44
48
// NullValueProvider,
@@ -52,13 +56,18 @@ suspend fun runJavaFuzzing(
52
56
providers : List <ValueProvider <FuzzedType , FuzzedValue , FuzzedDescription >> = defaultValueProviders(idGenerator),
53
57
exec : suspend (thisInstance: FuzzedValue ? , description: FuzzedDescription , values: List <FuzzedValue >) -> BaseFeedback <Trie .Node <Instruction >, FuzzedType , FuzzedValue >
54
58
) {
59
+ val random = Random (0 )
55
60
val classUnderTest = methodUnderTest.classId
56
61
val name = methodUnderTest.classId.simpleName + " ." + methodUnderTest.name
57
62
val returnType = methodUnderTest.returnType
58
63
val parameters = methodUnderTest.parameters
59
64
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 >()
60
69
/* *
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.
62
71
* Done for compatibility with old fuzzer logic and should be reworked more robust way.
63
72
*/
64
73
fun createFuzzedMethodDescription (self : ClassId ? ) = FuzzedMethodDescription (
@@ -79,9 +88,9 @@ suspend fun runJavaFuzzing(
79
88
fuzzerType = {
80
89
try {
81
90
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 )
85
94
}
86
95
} catch (_: Throwable ) {
87
96
null
@@ -94,15 +103,15 @@ suspend fun runJavaFuzzing(
94
103
if (! isStatic && ! isConstructor) { classUnderTest } else { null }
95
104
}
96
105
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 )
99
108
try {
100
109
logger.info { " Starting fuzzing for method: $methodUnderTest " }
101
110
logger.info { " \t use thisInstance = ${thisInstance != null } " }
102
111
logger.info { " \t parameters = $parameters " }
103
112
var totalExecutionCalled = 0
104
113
val totalFuzzingTime = measureNanoTime {
105
- runFuzzing(ValueProvider .of(providers), descriptionWithOptionalThisInstance) { _, t ->
114
+ runFuzzing(ValueProvider .of(providers), descriptionWithOptionalThisInstance, random ) { _, t ->
106
115
totalExecutionCalled++
107
116
if (thisInstance == null ) {
108
117
exec(null , descriptionWithOnlyParameters, t)
@@ -118,22 +127,86 @@ suspend fun runJavaFuzzing(
118
127
}
119
128
}
120
129
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 {
122
187
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
126
189
is GenericArrayType -> {
127
190
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)
131
194
} else {
132
- ClassId (" [" + fuzzerType. classId.name, fuzzerType. classId)
195
+ ClassId (" [" + classId.name, classId)
133
196
}
134
- FuzzedType (classId)
135
197
}
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()
138
211
}
139
212
}
0 commit comments