1
1
@file:JvmName(" FuzzingApi" )
2
2
package org.utbot.fuzzing
3
3
4
- import kotlinx.coroutines.yield
4
+ import kotlinx.coroutines.*
5
5
import mu.KotlinLogging
6
6
import org.utbot.fuzzing.seeds.KnownValue
7
7
import org.utbot.fuzzing.utils.MissedSeed
@@ -47,24 +47,31 @@ interface Fuzzing<TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feed
47
47
suspend fun handle (description : DESCRIPTION , values : List <RESULT >): FEEDBACK
48
48
49
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 .
50
+ * Starts fuzzing with new description but with copy of [Statistic] .
51
+ */
52
+ suspend fun fork ( description : DESCRIPTION , statistics : Statistic < TYPE , RESULT >) {
53
+ fuzz(description, StatisticImpl (statistics))
54
+ }
55
+
56
+ /* *
57
+ * Checks whether the fuzzer should stop .
58
58
*/
59
- suspend fun update (description : DESCRIPTION , statistic : Statistic <TYPE , RESULT >, configuration : Configuration ) {}
59
+ suspend fun isCancelled (description : DESCRIPTION , stats : Statistic <TYPE , RESULT >): Boolean {
60
+ return description.parameters.isEmpty()
61
+ }
62
+
63
+ suspend fun beforeIteration (description : DESCRIPTION , statistics : Statistic <TYPE , RESULT >) { }
64
+ suspend fun afterIteration (description : DESCRIPTION , statistics : Statistic <TYPE , RESULT >) { }
60
65
}
61
66
62
67
/* *
63
68
* Some description of current fuzzing run. Usually, it contains the name of the target method and its parameter list.
64
69
*/
65
70
open class Description <TYPE >(
66
- val parameters : List <TYPE >
67
- )
71
+ parameters : List <TYPE >
72
+ ) {
73
+ val parameters: List <TYPE > = parameters.toList()
74
+ }
68
75
69
76
/* *
70
77
* Input value that fuzzing knows how to build and use them.
@@ -115,7 +122,7 @@ sealed interface Seed<TYPE, RESULT> {
115
122
* Routine is a task that is used to build a value.
116
123
*
117
124
* There are several types of a routine, which all are generally only functions.
118
- * These function accepts some data and generates target value.
125
+ * These functions accept some data and generate target value.
119
126
*/
120
127
sealed class Routine <T , R >(val types : List <T >) : Iterable<T> by types {
121
128
@@ -211,13 +218,6 @@ enum class Control {
211
218
*/
212
219
CONTINUE ,
213
220
214
- /* *
215
- * Reset type cache and continue.
216
- *
217
- * Current seed and result will be analysed and cached.
218
- */
219
- RESET_TYPE_CACHE_AND_CONTINUE ,
220
-
221
221
/* *
222
222
* Do not process this feedback and just start next value generation.
223
223
*/
@@ -253,81 +253,71 @@ class NoSeedValueException internal constructor(
253
253
val type : Any?
254
254
) : Exception(" No seed candidates generated for type: $type " )
255
255
256
+ suspend fun <T , R , D : Description <T >, F : Feedback <T , R >> Fuzzing <T , R , D , F >.fuzz (
257
+ description : D ,
258
+ random : Random = Random (0),
259
+ configuration : Configuration = Configuration ()
260
+ ) {
261
+ fuzz(description, StatisticImpl (random = random, configuration = configuration))
262
+ }
263
+
256
264
/* *
257
265
* Starts fuzzing for this [Fuzzing] object.
258
266
*
259
267
* This is an entry point for every fuzzing.
260
268
*/
261
- suspend fun <T , R , D : Description <T >, F : Feedback <T , R >> Fuzzing <T , R , D , F >.fuzz (
269
+ private suspend fun <T , R , D : Description <T >, F : Feedback <T , R >> Fuzzing <T , R , D , F >.fuzz (
262
270
description : D ,
263
- random : Random = Random (0),
264
- configuration : Configuration = Configuration ()
271
+ statistic : StatisticImpl <T , R , F >,
265
272
) {
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 ()
273
+ val random = statistic.random
274
+ val configuration = statistic.configuration
275
275
val fuzzing = this
276
276
val typeCache = hashMapOf<T , List <Seed <T , R >>>()
277
- fun fuzzOne (): Node <T , R > = fuzz(
278
- parameters = description. parameters,
277
+ fun fuzzOne (parameters : List < T > ): Node <T , R > = fuzz(
278
+ parameters = parameters,
279
279
fuzzing = fuzzing,
280
280
description = description,
281
281
random = random,
282
282
configuration = configuration,
283
283
builder = PassRoutine (" Main Routine" ),
284
- state = State (1 , typeCache, userStatistic .missedTypes),
284
+ state = State (1 , typeCache, statistic .missedTypes),
285
285
)
286
- val dynamicallyGenerated = mutableListOf<Node <T , R >>()
287
- val seeds = Statistics <T , R , F >()
288
- run breaking@ {
289
- sequence {
290
- while (description.parameters.isNotEmpty()) {
291
- if (dynamicallyGenerated.isNotEmpty()) {
292
- yield (dynamicallyGenerated.removeFirst())
293
- } else {
294
- val fuzzOne = fuzzOne()
295
- // fuzz one value, seems to be bad, when have only a few and simple values
296
- yield (fuzzOne)
297
-
298
- val randomSeed = seeds.getRandomSeed(random, configuration)
299
- if (randomSeed != null ) {
300
- dynamicallyGenerated + = mutate(
301
- randomSeed,
302
- fuzzing,
303
- random,
304
- configuration,
305
- State (1 , typeCache, userStatistic.missedTypes)
306
- )
307
- }
308
- }
286
+
287
+ while (! fuzzing.isCancelled(description, statistic)) {
288
+ beforeIteration(description, statistic)
289
+ val seed = if (statistic.isNotEmpty() && random.flipCoin(configuration.probSeedRetrievingInsteadGenerating)) {
290
+ statistic.getRandomSeed(random, configuration)
291
+ } else {
292
+ val actualParameters = description.parameters
293
+ // fuzz one value, seems to be bad, when have only a few and simple values
294
+ fuzzOne(actualParameters)
295
+ }
296
+ val values = mutate(
297
+ seed,
298
+ fuzzing,
299
+ random,
300
+ configuration,
301
+ State (1 , typeCache, statistic.missedTypes)
302
+ )
303
+ afterIteration(description, statistic)
304
+
305
+ yield ()
306
+ statistic.apply {
307
+ totalRuns++
308
+ }
309
+ check(values.parameters.size == values.result.size) { " Cannot create value for ${values.parameters} " }
310
+ val valuesCache = mutableMapOf<Result <T , R >, R > ()
311
+ val result = values.result.map { valuesCache.computeIfAbsent(it) { r -> create(r) } }
312
+ val feedback = fuzzing.handle(description, result)
313
+ when (feedback.control) {
314
+ Control .CONTINUE -> {
315
+ statistic.put(random, configuration, feedback, values)
309
316
}
310
- }.forEach execution@ { values ->
311
- yield ()
312
- fuzzing.update(description, userStatistic.apply {
313
- totalRuns++
314
- }, configuration)
315
- check(values.parameters.size == values.result.size) { " Cannot create value for ${values.parameters} " }
316
- val valuesCache = mutableMapOf<Result <T , R >, R > ()
317
- val result = values.result.map { valuesCache.computeIfAbsent(it) { r -> create(r) } }
318
- val feedback = fuzzing.handle(description, result)
319
- when (feedback.control) {
320
- Control .CONTINUE -> {
321
- seeds.put(random, configuration, feedback, values)
322
- }
323
- Control .RESET_TYPE_CACHE_AND_CONTINUE -> {
324
- dynamicallyGenerated.clear()
325
- typeCache.clear()
326
- seeds.put(random, configuration, feedback, values)
327
- }
328
- Control .STOP -> { return @breaking }
329
- Control .PASS -> {}
317
+ Control .STOP -> {
318
+ break
330
319
}
320
+ Control .PASS -> {}
331
321
}
332
322
}
333
323
}
@@ -678,8 +668,24 @@ private class Node<TYPE, RESULT>(
678
668
val builder : Routine <TYPE , RESULT >,
679
669
)
680
670
671
+ private class StatisticImpl <TYPE , RESULT , FEEDBACK : Feedback <TYPE , RESULT >>(
672
+ override var totalRuns : Long = 0 ,
673
+ override val startTime : Long = System .nanoTime(),
674
+ override var missedTypes : MissedSeed <TYPE , RESULT > = MissedSeed (),
675
+ override val random : Random ,
676
+ override val configuration : Configuration ,
677
+ ) : Statistic<TYPE, RESULT> {
678
+
679
+ constructor (source: Statistic <TYPE , RESULT >) : this (
680
+ totalRuns = source.totalRuns,
681
+ startTime = source.startTime,
682
+ missedTypes = source.missedTypes,
683
+ random = source.random,
684
+ configuration = source.configuration.copy(),
685
+ )
681
686
682
- private class Statistics <TYPE , RESULT , FEEDBACK : Feedback <TYPE , RESULT >> {
687
+ override val elapsedTime: Long
688
+ get() = System .nanoTime() - startTime
683
689
private val seeds = linkedMapOf<FEEDBACK , Node <TYPE , RESULT >>()
684
690
private val count = linkedMapOf<FEEDBACK , Long >()
685
691
@@ -692,8 +698,8 @@ private class Statistics<TYPE, RESULT, FEEDBACK : Feedback<TYPE, RESULT>> {
692
698
count[feedback] = count.getOrDefault(feedback, 0L ) + 1L
693
699
}
694
700
695
- fun getRandomSeed (random : Random , configuration : Configuration ): Node <TYPE , RESULT >? {
696
- if (seeds.isEmpty()) return null
701
+ fun getRandomSeed (random : Random , configuration : Configuration ): Node <TYPE , RESULT > {
702
+ if (seeds.isEmpty()) error( " Call `isNotEmpty` before getting the seed " )
697
703
val entries = seeds.entries.toList()
698
704
val frequencies = DoubleArray (seeds.size).also { f ->
699
705
entries.forEachIndexed { index, (key, _) ->
@@ -703,6 +709,7 @@ private class Statistics<TYPE, RESULT, FEEDBACK : Feedback<TYPE, RESULT>> {
703
709
val index = random.chooseOne(frequencies)
704
710
return entries[index].value
705
711
}
706
- }
707
712
708
- // /endregion
713
+ fun isNotEmpty () = seeds.isNotEmpty()
714
+ }
715
+ // /endregion
0 commit comments