@@ -4,6 +4,7 @@ package org.utbot.fuzzing
4
4
import kotlinx.coroutines.yield
5
5
import mu.KotlinLogging
6
6
import org.utbot.fuzzing.seeds.KnownValue
7
+ import org.utbot.fuzzing.utils.Multiset
7
8
import org.utbot.fuzzing.utils.chooseOne
8
9
import org.utbot.fuzzing.utils.flipCoin
9
10
import org.utbot.fuzzing.utils.transformIfNotEmpty
@@ -36,18 +37,34 @@ interface Fuzzing<TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feed
36
37
*
37
38
* Fuzzing combines, randomize and mutates values using the seeds.
38
39
* Then it generates values and runs them with this method. This method should provide some feedback,
39
- * which is the most important part for good fuzzing result. [emptyFeedback] can be provided only for test
40
- * or infinite loops. Consider to implement own implementation of [Feedback] to provide more correct data or
40
+ * which is the most important part for a good fuzzing result. [emptyFeedback] can be provided only for test
41
+ * or infinite loops. Consider implementing own implementation of [Feedback] to provide more correct data or
41
42
* use [BaseFeedback] to generate key based feedback. In this case, the key is used to analyse what value should be next.
42
43
*
43
44
* @param description contains user-defined information about current run. Can be used as a state of the run.
44
45
* @param values current values to process.
45
46
*/
46
47
suspend fun handle (description : DESCRIPTION , values : List <RESULT >): FEEDBACK
48
+
49
+ /* *
50
+ * This method is called before every fuzzing attempt.
51
+ *
52
+ * Usually, is used to update configuration or manipulate some data in the statistics
53
+ * (for example, clear some [Statistic.missedTypes] types).
54
+ *
55
+ * @param description contains user-defined information about current run. Can be used as a state of the run.
56
+ * @param statistic statistic about fuzzing generation like elapsed time or number of the runs.
57
+ * @param configuration current used configuration; it can be changes for tuning fuzzing.
58
+ */
59
+ fun update (description : DESCRIPTION , statistic : Statistic <TYPE >, configuration : Configuration ) {
60
+ if (! configuration.generateEmptyListForMissedTypes && statistic.missedTypes.isNotEmpty()) {
61
+ error(" No seed candidates generated for types:\n\t ${statistic.missedTypes.joinToString(" \n\t " )} " )
62
+ }
63
+ }
47
64
}
48
65
49
66
/* *
50
- * Some description of current fuzzing run. Usually, contains name of the target method and its parameter list.
67
+ * Some description of current fuzzing run. Usually, it contains the name of the target method and its parameter list.
51
68
*/
52
69
open class Description <TYPE >(
53
70
val parameters : List <TYPE >
@@ -235,6 +252,11 @@ private object EmptyFeedback : Feedback<Nothing, Nothing> {
235
252
}
236
253
}
237
254
255
+ private class NoSeedValueException (
256
+ // this type cannot be generalized because Java forbids types for [Throwable].
257
+ val type : Any?
258
+ ) : Exception(" No seed candidates generated for type: $type " )
259
+
238
260
/* *
239
261
* Starts fuzzing for this [Fuzzing] object.
240
262
*
@@ -245,6 +267,15 @@ suspend fun <T, R, D : Description<T>, F : Feedback<T, R>> Fuzzing<T, R, D, F>.f
245
267
random : Random = Random (0),
246
268
configuration : Configuration = Configuration ()
247
269
) {
270
+ class StatImpl (
271
+ override var totalRuns : Long = 0 ,
272
+ val startTime : Long = System .nanoTime(),
273
+ override var missedTypes : Multiset <T > = Multiset (),
274
+ ) : Statistic<T> {
275
+ override val elapsedTime: Long
276
+ get() = System .nanoTime() - startTime
277
+ }
278
+ val userStatistic = StatImpl ()
248
279
val fuzzing = this
249
280
val typeCache = hashMapOf<T , List <Seed <T , R >>>()
250
281
fun fuzzOne (): Node <T , R > = fuzz(
@@ -254,17 +285,24 @@ suspend fun <T, R, D : Description<T>, F : Feedback<T, R>> Fuzzing<T, R, D, F>.f
254
285
random = random,
255
286
configuration = configuration,
256
287
builder = PassRoutine (" Main Routine" ),
257
- state = State (1 , typeCache),
288
+ state = State (1 , typeCache, userStatistic.missedTypes ),
258
289
)
259
290
val dynamicallyGenerated = mutableListOf<Node <T , R >>()
260
291
val seeds = Statistics <T , R , F >()
261
292
run breaking@ {
262
293
sequence {
263
294
while (description.parameters.isNotEmpty()) {
295
+ fuzzing.update(description, userStatistic, configuration)
264
296
if (dynamicallyGenerated.isNotEmpty()) {
265
297
yield (dynamicallyGenerated.removeFirst())
266
298
} else {
267
- val fuzzOne = fuzzOne()
299
+ val fuzzOne = try {
300
+ fuzzOne()
301
+ } catch (nsv: NoSeedValueException ) {
302
+ @Suppress(" UNCHECKED_CAST" )
303
+ userStatistic.missedTypes.add(nsv.type as T )
304
+ continue
305
+ }
268
306
// fuzz one value, seems to be bad, when have only a few and simple values
269
307
yield (fuzzOne)
270
308
@@ -275,7 +313,7 @@ suspend fun <T, R, D : Description<T>, F : Feedback<T, R>> Fuzzing<T, R, D, F>.f
275
313
fuzzing,
276
314
random,
277
315
configuration,
278
- State (1 , typeCache)
316
+ State (1 , typeCache, userStatistic.missedTypes )
279
317
)
280
318
}
281
319
}
@@ -318,6 +356,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
318
356
val result = parameters.map { type ->
319
357
val results = typeCache.computeIfAbsent(type) { mutableListOf () }
320
358
if (results.isNotEmpty() && random.flipCoin(configuration.probReuseGeneratedValueForSameType)) {
359
+ // we need to check cases when one value is passed for different arguments
321
360
results.random(random)
322
361
} else {
323
362
produce(type, fuzzing, description, random, configuration, state).also {
@@ -347,7 +386,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
347
386
}
348
387
}
349
388
if (candidates.isEmpty()) {
350
- error( " No seed candidates generated for type: $type " )
389
+ throw NoSeedValueException ( type)
351
390
}
352
391
return candidates.random(random)
353
392
}
@@ -367,10 +406,10 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
367
406
return if (state.recursionTreeDepth > configuration.recursionTreeDepth) {
368
407
Result .Empty { task.construct.builder(0 ) }
369
408
} else {
370
- val iterations = if (state.iterations >= 0 && random.flipCoin(configuration.probCreateRectangleCollectionInsteadSawLike)) {
371
- state.iterations
372
- } else {
373
- random.nextInt(0 , configuration.collectionIterations + 1 )
409
+ val iterations = when {
410
+ configuration.generateEmptyListForMissedTypes && task.modify.types.any( state.missedTypes::contains) -> 0
411
+ state.iterations >= 0 && random.flipCoin(configuration.probCreateRectangleCollectionInsteadSawLike) -> state.iterations
412
+ else -> random.nextInt(0 , configuration.collectionIterations + 1 )
374
413
}
375
414
Result .Collection (
376
415
construct = fuzz(
@@ -380,18 +419,18 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
380
419
random,
381
420
configuration,
382
421
task.construct,
383
- State (state.recursionTreeDepth + 1 , state.cache, iterations)
422
+ State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes, iterations)
384
423
),
385
424
modify = if (random.flipCoin(configuration.probCollectionMutationInsteadCreateNew)) {
386
- val result = fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, State (state.recursionTreeDepth + 1 , state.cache, iterations))
425
+ val result = fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes, iterations))
387
426
arrayListOf (result).apply {
388
427
(1 until iterations).forEach { _ ->
389
428
add(mutate(result, fuzzing, random, configuration, state))
390
429
}
391
430
}
392
431
} else {
393
432
(0 until iterations).map {
394
- fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, State (state.recursionTreeDepth + 1 , state.cache, iterations))
433
+ fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes, iterations))
395
434
}
396
435
},
397
436
iterations = iterations
@@ -422,7 +461,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
422
461
random,
423
462
configuration,
424
463
task.construct,
425
- State (state.recursionTreeDepth + 1 , state.cache)
464
+ State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes )
426
465
),
427
466
modify = task.modify
428
467
.shuffled(random)
@@ -434,7 +473,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
434
473
random,
435
474
configuration,
436
475
routine,
437
- State (state.recursionTreeDepth + 1 , state.cache)
476
+ State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes )
438
477
)
439
478
}.transformIfNotEmpty {
440
479
take(random.nextInt(size + 1 ).coerceAtLeast(1 ))
@@ -472,7 +511,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
472
511
is Result .Recursive <TYPE , RESULT > -> {
473
512
if (resultToMutate.modify.isEmpty() || random.flipCoin(configuration.probConstructorMutationInsteadModificationMutation)) {
474
513
Result .Recursive (
475
- construct = mutate(resultToMutate.construct, fuzzing, random, configuration, State (state.recursionTreeDepth + 1 , state.cache)),
514
+ construct = mutate(resultToMutate.construct, fuzzing, random, configuration, State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes )),
476
515
modify = resultToMutate.modify
477
516
)
478
517
} else if (random.flipCoin(configuration.probShuffleAndCutRecursiveObjectModificationMutation)) {
@@ -485,7 +524,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
485
524
construct = resultToMutate.construct,
486
525
modify = resultToMutate.modify.toMutableList().apply {
487
526
val i = random.nextInt(0 , resultToMutate.modify.size)
488
- set(i, mutate(resultToMutate.modify[i], fuzzing, random, configuration, State (state.recursionTreeDepth + 1 , state.cache)))
527
+ set(i, mutate(resultToMutate.modify[i], fuzzing, random, configuration, State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes )))
489
528
}
490
529
)
491
530
}
@@ -496,7 +535,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
496
535
if (isNotEmpty()) {
497
536
if (random.flipCoin(100 - configuration.probCollectionShuffleInsteadResultMutation)) {
498
537
val i = random.nextInt(0 , resultToMutate.modify.size)
499
- set(i, mutate(resultToMutate.modify[i], fuzzing, random, configuration, State (state.recursionTreeDepth + 1 , state.cache)))
538
+ set(i, mutate(resultToMutate.modify[i], fuzzing, random, configuration, State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes )))
500
539
} else {
501
540
shuffle(random)
502
541
}
@@ -578,7 +617,8 @@ private data class PassRoutine<T, R>(val description: String) : Routine<T, R>(em
578
617
private class State <TYPE , RESULT >(
579
618
val recursionTreeDepth : Int = 1 ,
580
619
val cache : MutableMap <TYPE , List <Seed <TYPE , RESULT >>>,
581
- val iterations : Int = -1
620
+ val missedTypes : Multiset <TYPE >,
621
+ val iterations : Int = -1 ,
582
622
)
583
623
584
624
/* *
0 commit comments