@@ -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.MissedSeed
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,30 @@ 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
+ suspend fun update (description : DESCRIPTION , statistic : Statistic <TYPE , RESULT >, configuration : Configuration ) {}
47
60
}
48
61
49
62
/* *
50
- * Some description of current fuzzing run. Usually, contains name of the target method and its parameter list.
63
+ * Some description of current fuzzing run. Usually, it contains the name of the target method and its parameter list.
51
64
*/
52
65
open class Description <TYPE >(
53
66
val parameters : List <TYPE >
@@ -235,6 +248,11 @@ private object EmptyFeedback : Feedback<Nothing, Nothing> {
235
248
}
236
249
}
237
250
251
+ private class NoSeedValueException (
252
+ // this type cannot be generalized because Java forbids types for [Throwable].
253
+ val type : Any?
254
+ ) : Exception(" No seed candidates generated for type: $type " )
255
+
238
256
/* *
239
257
* Starts fuzzing for this [Fuzzing] object.
240
258
*
@@ -245,6 +263,15 @@ suspend fun <T, R, D : Description<T>, F : Feedback<T, R>> Fuzzing<T, R, D, F>.f
245
263
random : Random = Random (0),
246
264
configuration : Configuration = Configuration ()
247
265
) {
266
+ class StatImpl (
267
+ override var totalRuns : Long = 0 ,
268
+ val startTime : Long = System .nanoTime(),
269
+ override var missedTypes : MissedSeed <T , R > = MissedSeed (),
270
+ ) : Statistic<T, R> {
271
+ override val elapsedTime: Long
272
+ get() = System .nanoTime() - startTime
273
+ }
274
+ val userStatistic = StatImpl ()
248
275
val fuzzing = this
249
276
val typeCache = hashMapOf<T , List <Seed <T , R >>>()
250
277
fun fuzzOne (): Node <T , R > = fuzz(
@@ -254,7 +281,7 @@ suspend fun <T, R, D : Description<T>, F : Feedback<T, R>> Fuzzing<T, R, D, F>.f
254
281
random = random,
255
282
configuration = configuration,
256
283
builder = PassRoutine (" Main Routine" ),
257
- state = State (1 , typeCache),
284
+ state = State (1 , typeCache, userStatistic.missedTypes ),
258
285
)
259
286
val dynamicallyGenerated = mutableListOf<Node <T , R >>()
260
287
val seeds = Statistics <T , R , F >()
@@ -275,13 +302,16 @@ suspend fun <T, R, D : Description<T>, F : Feedback<T, R>> Fuzzing<T, R, D, F>.f
275
302
fuzzing,
276
303
random,
277
304
configuration,
278
- State (1 , typeCache)
305
+ State (1 , typeCache, userStatistic.missedTypes )
279
306
)
280
307
}
281
308
}
282
309
}
283
310
}.forEach execution@ { values ->
284
311
yield ()
312
+ fuzzing.update(description, userStatistic.apply {
313
+ totalRuns++
314
+ }, configuration)
285
315
check(values.parameters.size == values.result.size) { " Cannot create value for ${values.parameters} " }
286
316
val valuesCache = mutableMapOf<Result <T , R >, R > ()
287
317
val result = values.result.map { valuesCache.computeIfAbsent(it) { r -> create(r) } }
@@ -318,6 +348,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
318
348
val result = parameters.map { type ->
319
349
val results = typeCache.computeIfAbsent(type) { mutableListOf () }
320
350
if (results.isNotEmpty() && random.flipCoin(configuration.probReuseGeneratedValueForSameType)) {
351
+ // we need to check cases when one value is passed for different arguments
321
352
results.random(random)
322
353
} else {
323
354
produce(type, fuzzing, description, random, configuration, state).also {
@@ -347,7 +378,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
347
378
}
348
379
}
349
380
if (candidates.isEmpty()) {
350
- error( " No seed candidates generated for type: $type " )
381
+ throw NoSeedValueException ( type)
351
382
}
352
383
return candidates.random(random)
353
384
}
@@ -366,11 +397,10 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
366
397
): Result <TYPE , RESULT > {
367
398
return if (state.recursionTreeDepth > configuration.recursionTreeDepth) {
368
399
Result .Empty { task.construct.builder(0 ) }
369
- } else {
370
- val iterations = if (state.iterations >= 0 && random.flipCoin(configuration.probCreateRectangleCollectionInsteadSawLike)) {
371
- state.iterations
372
- } else {
373
- random.nextInt(0 , configuration.collectionIterations + 1 )
400
+ } else try {
401
+ val iterations = when {
402
+ state.iterations >= 0 && random.flipCoin(configuration.probCreateRectangleCollectionInsteadSawLike) -> state.iterations
403
+ else -> random.nextInt(0 , configuration.collectionIterations + 1 )
374
404
}
375
405
Result .Collection (
376
406
construct = fuzz(
@@ -380,22 +410,30 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
380
410
random,
381
411
configuration,
382
412
task.construct,
383
- State (state.recursionTreeDepth + 1 , state.cache, iterations)
413
+ State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes, iterations)
384
414
),
385
415
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))
416
+ val result = fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes, iterations))
387
417
arrayListOf (result).apply {
388
418
(1 until iterations).forEach { _ ->
389
419
add(mutate(result, fuzzing, random, configuration, state))
390
420
}
391
421
}
392
422
} else {
393
423
(0 until iterations).map {
394
- fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, State (state.recursionTreeDepth + 1 , state.cache, iterations))
424
+ fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes, iterations))
395
425
}
396
426
},
397
427
iterations = iterations
398
428
)
429
+ } catch (nsv: NoSeedValueException ) {
430
+ @Suppress(" UNCHECKED_CAST" )
431
+ state.missedTypes[nsv.type as TYPE ] = task
432
+ if (configuration.generateEmptyCollectionsForMissedTypes) {
433
+ Result .Empty { task.construct.builder(0 ) }
434
+ } else {
435
+ throw nsv
436
+ }
399
437
}
400
438
}
401
439
@@ -413,7 +451,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
413
451
): Result <TYPE , RESULT > {
414
452
return if (state.recursionTreeDepth > configuration.recursionTreeDepth) {
415
453
Result .Empty { task.empty.builder() }
416
- } else {
454
+ } else try {
417
455
Result .Recursive (
418
456
construct = fuzz(
419
457
task.construct.types,
@@ -422,7 +460,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
422
460
random,
423
461
configuration,
424
462
task.construct,
425
- State (state.recursionTreeDepth + 1 , state.cache)
463
+ State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes )
426
464
),
427
465
modify = task.modify
428
466
.shuffled(random)
@@ -434,12 +472,20 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
434
472
random,
435
473
configuration,
436
474
routine,
437
- State (state.recursionTreeDepth + 1 , state.cache)
475
+ State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes )
438
476
)
439
477
}.transformIfNotEmpty {
440
478
take(random.nextInt(size + 1 ).coerceAtLeast(1 ))
441
479
}
442
480
)
481
+ } catch (nsv: NoSeedValueException ) {
482
+ @Suppress(" UNCHECKED_CAST" )
483
+ state.missedTypes[nsv.type as TYPE ] = task
484
+ if (configuration.generateEmptyRecursiveForMissedTypes) {
485
+ Result .Empty { task.empty() }
486
+ } else {
487
+ throw nsv
488
+ }
443
489
}
444
490
}
445
491
@@ -472,7 +518,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
472
518
is Result .Recursive <TYPE , RESULT > -> {
473
519
if (resultToMutate.modify.isEmpty() || random.flipCoin(configuration.probConstructorMutationInsteadModificationMutation)) {
474
520
Result .Recursive (
475
- construct = mutate(resultToMutate.construct, fuzzing, random, configuration, State (state.recursionTreeDepth + 1 , state.cache)),
521
+ construct = mutate(resultToMutate.construct, fuzzing, random, configuration, State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes )),
476
522
modify = resultToMutate.modify
477
523
)
478
524
} else if (random.flipCoin(configuration.probShuffleAndCutRecursiveObjectModificationMutation)) {
@@ -485,7 +531,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
485
531
construct = resultToMutate.construct,
486
532
modify = resultToMutate.modify.toMutableList().apply {
487
533
val i = random.nextInt(0 , resultToMutate.modify.size)
488
- set(i, mutate(resultToMutate.modify[i], fuzzing, random, configuration, State (state.recursionTreeDepth + 1 , state.cache)))
534
+ set(i, mutate(resultToMutate.modify[i], fuzzing, random, configuration, State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes )))
489
535
}
490
536
)
491
537
}
@@ -496,7 +542,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
496
542
if (isNotEmpty()) {
497
543
if (random.flipCoin(100 - configuration.probCollectionShuffleInsteadResultMutation)) {
498
544
val i = random.nextInt(0 , resultToMutate.modify.size)
499
- set(i, mutate(resultToMutate.modify[i], fuzzing, random, configuration, State (state.recursionTreeDepth + 1 , state.cache)))
545
+ set(i, mutate(resultToMutate.modify[i], fuzzing, random, configuration, State (state.recursionTreeDepth + 1 , state.cache, state.missedTypes )))
500
546
} else {
501
547
shuffle(random)
502
548
}
@@ -578,7 +624,8 @@ private data class PassRoutine<T, R>(val description: String) : Routine<T, R>(em
578
624
private class State <TYPE , RESULT >(
579
625
val recursionTreeDepth : Int = 1 ,
580
626
val cache : MutableMap <TYPE , List <Seed <TYPE , RESULT >>>,
581
- val iterations : Int = -1
627
+ val missedTypes : MissedSeed <TYPE , RESULT >,
628
+ val iterations : Int = -1 ,
582
629
)
583
630
584
631
/* *
0 commit comments