@@ -11,6 +11,7 @@ import org.utbot.fuzzing.utils.Trie
1111import java.lang.reflect.*
1212import java.util.concurrent.TimeUnit
1313import kotlin.system.measureNanoTime
14+ import kotlin.random.Random
1415
1516private val logger = KotlinLogging .logger {}
1617
@@ -19,6 +20,8 @@ typealias JavaValueProvider = ValueProvider<FuzzedType, FuzzedValue, FuzzedDescr
1920class 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 { " \t use thisInstance = ${thisInstance != null } " }
102111 logger.info { " \t parameters = $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}
0 commit comments