Skip to content

Commit 25fc246

Browse files
committed
Fuzzer fails to generate an empty list for type list[Any]
1 parent 1cdc000 commit 25fc246

File tree

6 files changed

+138
-30
lines changed

6 files changed

+138
-30
lines changed

utbot-fuzzers/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import org.utbot.fuzzer.*
99
import org.utbot.fuzzing.providers.*
1010
import org.utbot.fuzzing.utils.Trie
1111
import java.lang.reflect.*
12+
import java.util.concurrent.CancellationException
1213
import java.util.concurrent.TimeUnit
13-
import kotlin.system.measureNanoTime
1414
import kotlin.random.Random
1515

1616
private val logger = KotlinLogging.logger {}
@@ -105,23 +105,31 @@ suspend fun runJavaFuzzing(
105105
val tracer = Trie(Instruction::id)
106106
val descriptionWithOptionalThisInstance = FuzzedDescription(createFuzzedMethodDescription(thisInstance), tracer, typeCache, random)
107107
val descriptionWithOnlyParameters = FuzzedDescription(createFuzzedMethodDescription(null), tracer, typeCache, random)
108+
val start = System.nanoTime()
108109
try {
109110
logger.info { "Starting fuzzing for method: $methodUnderTest" }
110111
logger.info { "\tuse thisInstance = ${thisInstance != null}" }
111112
logger.info { "\tparameters = $parameters" }
112113
var totalExecutionCalled = 0
113-
val totalFuzzingTime = measureNanoTime {
114-
runFuzzing(ValueProvider.of(providers), descriptionWithOptionalThisInstance, random) { _, t ->
115-
totalExecutionCalled++
116-
if (thisInstance == null) {
117-
exec(null, descriptionWithOnlyParameters, t)
118-
} else {
119-
exec(t.first(), descriptionWithOnlyParameters, t.drop(1))
120-
}
114+
runFuzzing(
115+
provider = ValueProvider.of(providers),
116+
description = descriptionWithOptionalThisInstance, random,
117+
configuration = Configuration()
118+
) { _, t ->
119+
totalExecutionCalled++
120+
if (thisInstance == null) {
121+
exec(null, descriptionWithOnlyParameters, t)
122+
} else {
123+
exec(t.first(), descriptionWithOnlyParameters, t.drop(1))
121124
}
122125
}
126+
val totalFuzzingTime = System.nanoTime() - start
123127
logger.info { "Finishing fuzzing for method: $methodUnderTest in ${TimeUnit.NANOSECONDS.toMillis(totalFuzzingTime)} ms" }
124128
logger.info { "\tTotal execution called: $totalExecutionCalled" }
129+
} catch (ce: CancellationException) {
130+
val totalFuzzingTime = System.nanoTime() - start
131+
logger.info { "Fuzzing is stopped because of timeout. Total execution time: ${TimeUnit.NANOSECONDS.toMillis(totalFuzzingTime)} ms" }
132+
logger.debug(ce) { "\tStacktrace:" }
125133
} catch (t: Throwable) {
126134
logger.info(t) { "Fuzzing is stopped because of an error" }
127135
}

utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package org.utbot.fuzzing
44
import kotlinx.coroutines.yield
55
import mu.KotlinLogging
66
import org.utbot.fuzzing.seeds.KnownValue
7+
import org.utbot.fuzzing.utils.Multiset
78
import org.utbot.fuzzing.utils.chooseOne
89
import org.utbot.fuzzing.utils.flipCoin
910
import org.utbot.fuzzing.utils.transformIfNotEmpty
@@ -36,18 +37,34 @@ interface Fuzzing<TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feed
3637
*
3738
* Fuzzing combines, randomize and mutates values using the seeds.
3839
* 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
4142
* use [BaseFeedback] to generate key based feedback. In this case, the key is used to analyse what value should be next.
4243
*
4344
* @param description contains user-defined information about current run. Can be used as a state of the run.
4445
* @param values current values to process.
4546
*/
4647
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+
}
4764
}
4865

4966
/**
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.
5168
*/
5269
open class Description<TYPE>(
5370
val parameters: List<TYPE>
@@ -235,6 +252,11 @@ private object EmptyFeedback : Feedback<Nothing, Nothing> {
235252
}
236253
}
237254

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+
238260
/**
239261
* Starts fuzzing for this [Fuzzing] object.
240262
*
@@ -245,6 +267,15 @@ suspend fun <T, R, D : Description<T>, F : Feedback<T, R>> Fuzzing<T, R, D, F>.f
245267
random: Random = Random(0),
246268
configuration: Configuration = Configuration()
247269
) {
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()
248279
val fuzzing = this
249280
val typeCache = hashMapOf<T, List<Seed<T, R>>>()
250281
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
254285
random = random,
255286
configuration = configuration,
256287
builder = PassRoutine("Main Routine"),
257-
state = State(1, typeCache),
288+
state = State(1, typeCache, userStatistic.missedTypes),
258289
)
259290
val dynamicallyGenerated = mutableListOf<Node<T, R>>()
260291
val seeds = Statistics<T, R, F>()
261292
run breaking@ {
262293
sequence {
263294
while (description.parameters.isNotEmpty()) {
295+
fuzzing.update(description, userStatistic, configuration)
264296
if (dynamicallyGenerated.isNotEmpty()) {
265297
yield(dynamicallyGenerated.removeFirst())
266298
} 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+
}
268306
// fuzz one value, seems to be bad, when have only a few and simple values
269307
yield(fuzzOne)
270308

@@ -275,7 +313,7 @@ suspend fun <T, R, D : Description<T>, F : Feedback<T, R>> Fuzzing<T, R, D, F>.f
275313
fuzzing,
276314
random,
277315
configuration,
278-
State(1, typeCache)
316+
State(1, typeCache, userStatistic.missedTypes)
279317
)
280318
}
281319
}
@@ -318,6 +356,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
318356
val result = parameters.map { type ->
319357
val results = typeCache.computeIfAbsent(type) { mutableListOf() }
320358
if (results.isNotEmpty() && random.flipCoin(configuration.probReuseGeneratedValueForSameType)) {
359+
// we need to check cases when one value is passed for different arguments
321360
results.random(random)
322361
} else {
323362
produce(type, fuzzing, description, random, configuration, state).also {
@@ -347,7 +386,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
347386
}
348387
}
349388
if (candidates.isEmpty()) {
350-
error("No seed candidates generated for type: $type")
389+
throw NoSeedValueException(type)
351390
}
352391
return candidates.random(random)
353392
}
@@ -367,10 +406,10 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
367406
return if (state.recursionTreeDepth > configuration.recursionTreeDepth) {
368407
Result.Empty { task.construct.builder(0) }
369408
} 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)
374413
}
375414
Result.Collection(
376415
construct = fuzz(
@@ -380,18 +419,18 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
380419
random,
381420
configuration,
382421
task.construct,
383-
State(state.recursionTreeDepth + 1, state.cache, iterations)
422+
State(state.recursionTreeDepth + 1, state.cache, state.missedTypes, iterations)
384423
),
385424
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))
387426
arrayListOf(result).apply {
388427
(1 until iterations).forEach { _ ->
389428
add(mutate(result, fuzzing, random, configuration, state))
390429
}
391430
}
392431
} else {
393432
(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))
395434
}
396435
},
397436
iterations = iterations
@@ -422,7 +461,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
422461
random,
423462
configuration,
424463
task.construct,
425-
State(state.recursionTreeDepth + 1, state.cache)
464+
State(state.recursionTreeDepth + 1, state.cache, state.missedTypes)
426465
),
427466
modify = task.modify
428467
.shuffled(random)
@@ -434,7 +473,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
434473
random,
435474
configuration,
436475
routine,
437-
State(state.recursionTreeDepth + 1, state.cache)
476+
State(state.recursionTreeDepth + 1, state.cache, state.missedTypes)
438477
)
439478
}.transformIfNotEmpty {
440479
take(random.nextInt(size + 1).coerceAtLeast(1))
@@ -472,7 +511,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
472511
is Result.Recursive<TYPE, RESULT> -> {
473512
if (resultToMutate.modify.isEmpty() || random.flipCoin(configuration.probConstructorMutationInsteadModificationMutation)) {
474513
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)),
476515
modify = resultToMutate.modify
477516
)
478517
} else if (random.flipCoin(configuration.probShuffleAndCutRecursiveObjectModificationMutation)) {
@@ -485,7 +524,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
485524
construct = resultToMutate.construct,
486525
modify = resultToMutate.modify.toMutableList().apply {
487526
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)))
489528
}
490529
)
491530
}
@@ -496,7 +535,7 @@ private fun <TYPE, RESULT, DESCRIPTION : Description<TYPE>, FEEDBACK : Feedback<
496535
if (isNotEmpty()) {
497536
if (random.flipCoin(100 - configuration.probCollectionShuffleInsteadResultMutation)) {
498537
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)))
500539
} else {
501540
shuffle(random)
502541
}
@@ -578,7 +617,8 @@ private data class PassRoutine<T, R>(val description: String) : Routine<T, R>(em
578617
private class State<TYPE, RESULT>(
579618
val recursionTreeDepth: Int = 1,
580619
val cache: MutableMap<TYPE, List<Seed<TYPE, RESULT>>>,
581-
val iterations: Int = -1
620+
val missedTypes: Multiset<TYPE>,
621+
val iterations: Int = -1,
582622
)
583623

584624
/**

utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,11 @@ class Configuration(
7171
/**
7272
* Probability of reusing same generated value when 2 or more parameters have the same type.
7373
*/
74-
var probReuseGeneratedValueForSameType: Int = 1
74+
var probReuseGeneratedValueForSameType: Int = 1,
75+
76+
/**
77+
* When true any [Seed.Collection] will not try
78+
* to generate modification if a current type is already known to fail to generate values.
79+
*/
80+
var generateEmptyListForMissedTypes: Boolean = true,
7581
)

utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Providers.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class BaseFuzzing<T, R, D : Description<T>, F : Feedback<T, R>>(
2929
val exec: suspend (description: D, values: List<R>) -> F
3030
) : Fuzzing<T, R, D, F> {
3131

32+
var update: (D, Statistic<T>, Configuration) -> Unit = { d, s, c -> super.update(d, s, c) }
33+
3234
constructor(vararg providers: ValueProvider<T, R, D>, exec: suspend (description: D, values: List<R>) -> F) : this(providers.toList(), exec)
3335

3436
override fun generate(description: D, type: T): Sequence<Seed<T, R>> {
@@ -49,6 +51,10 @@ class BaseFuzzing<T, R, D : Description<T>, F : Feedback<T, R>>(
4951
override suspend fun handle(description: D, values: List<R>): F {
5052
return exec(description, values)
5153
}
54+
55+
override fun update(description: D, statistic: Statistic<T>, configuration: Configuration) {
56+
update.invoke(description, statistic, configuration)
57+
}
5258
}
5359

5460
/**
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.utbot.fuzzing
2+
3+
import org.utbot.fuzzing.utils.Multiset
4+
5+
/**
6+
* User class that holds data about current fuzzing running.
7+
*
8+
* Concrete implementation is passed to the [Fuzzing.update].
9+
*/
10+
interface Statistic<TYPE> {
11+
val totalRuns: Long
12+
val elapsedTime: Long
13+
val missedTypes: Multiset<TYPE>
14+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.utbot.fuzzing.utils
2+
3+
/**
4+
* Simple implementation of multiset.
5+
*/
6+
class Multiset<T> : Iterable<T> {
7+
8+
private val values = hashMapOf<T, Long>()
9+
10+
fun add(value: T): Long {
11+
val result = values.getOrDefault(value, 0L) + 1
12+
values[value] = result
13+
return result
14+
}
15+
16+
operator fun get(value: T): Long {
17+
return values.getOrDefault(value, 0L)
18+
}
19+
20+
fun isEmpty(): Boolean {
21+
return values.size == 0
22+
}
23+
24+
fun isNotEmpty(): Boolean = !isEmpty()
25+
26+
fun removeAll(value: T) {
27+
values.remove(value)
28+
}
29+
30+
override fun iterator(): Iterator<T> {
31+
return values.keys.iterator()
32+
}
33+
34+
}

0 commit comments

Comments
 (0)