Skip to content

Commit bc039df

Browse files
authored
Improve mutation API in Fuzzing Platform (#2296)
1 parent fa1cf9f commit bc039df

File tree

16 files changed

+491
-252
lines changed

16 files changed

+491
-252
lines changed

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

Lines changed: 92 additions & 131 deletions
Large diffs are not rendered by default.

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

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,22 @@ data class Configuration(
3737
/**
3838
* Probability of creating shifted array values instead of generating new values for modification.
3939
*/
40-
var probCollectionMutationInsteadCreateNew: Int = 50,
40+
var probCollectionDuplicationInsteadCreateNew: Int = 10,
41+
42+
/**
43+
* Probability of creating empty collections
44+
*/
45+
var probEmptyCollectionCreation: Int = 1,
4146

4247
/**
4348
* Probability to prefer change constructor instead of modification.
4449
*/
45-
var probConstructorMutationInsteadModificationMutation: Int = 90,
50+
var probConstructorMutationInsteadModificationMutation: Int = 30,
4651

4752
/**
48-
* Probability to shuffle modification list of the recursive object
53+
* Probability to a shuffle modification list of the recursive object
4954
*/
50-
var probShuffleAndCutRecursiveObjectModificationMutation: Int = 10,
55+
var probShuffleAndCutRecursiveObjectModificationMutation: Int = 30,
5156

5257
/**
5358
* Probability to prefer create rectangle collections instead of creating saw-like one.
@@ -62,17 +67,7 @@ data class Configuration(
6267
/**
6368
* When mutating StringValue a new string will not exceed this value.
6469
*/
65-
var maxStringLengthWhenMutated: Int = 64,
66-
67-
/**
68-
* Probability of adding a new character when mutating StringValue
69-
*/
70-
var probStringAddCharacter: Int = 50,
71-
72-
/**
73-
* Probability of removing an old character from StringValue when mutating
74-
*/
75-
var probStringRemoveCharacter: Int = 50,
70+
var maxStringLengthWhenMutated: Int = 128,
7671

7772
/**
7873
* Probability of reusing same generated value when 2 or more parameters have the same type.

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

Lines changed: 268 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,105 @@ package org.utbot.fuzzing
44

55
import org.utbot.fuzzing.seeds.BitVectorValue
66
import org.utbot.fuzzing.seeds.IEEE754Value
7+
import org.utbot.fuzzing.seeds.KnownValue
8+
import org.utbot.fuzzing.seeds.StringValue
9+
import org.utbot.fuzzing.utils.chooseOne
10+
import org.utbot.fuzzing.utils.flipCoin
711
import kotlin.random.Random
812

13+
class MutationFactory<TYPE, RESULT> {
14+
15+
fun mutate(node: Node<TYPE, RESULT>, random: Random, configuration: Configuration): Node<TYPE, RESULT> {
16+
if (node.result.isEmpty()) return node
17+
val indexOfMutatedResult = random.chooseOne(node.result.map(::rate).toDoubleArray())
18+
val recursive: NodeMutation<TYPE, RESULT> = NodeMutation { n, r, c ->
19+
mutate(n, r, c)
20+
}
21+
val mutated = when (val resultToMutate = node.result[indexOfMutatedResult]) {
22+
is Result.Simple<TYPE, RESULT> -> Result.Simple(resultToMutate.mutation(resultToMutate.result, random), resultToMutate.mutation)
23+
is Result.Known<TYPE, RESULT, *> -> {
24+
val mutations = resultToMutate.value.mutations()
25+
if (mutations.isNotEmpty()) {
26+
resultToMutate.mutate(mutations.random(random), random, configuration)
27+
} else {
28+
resultToMutate
29+
}
30+
}
31+
is Result.Recursive<TYPE, RESULT> -> {
32+
when {
33+
resultToMutate.modify.isEmpty() || random.flipCoin(configuration.probConstructorMutationInsteadModificationMutation) ->
34+
RecursiveMutations.Constructor<TYPE, RESULT>()
35+
random.flipCoin(configuration.probShuffleAndCutRecursiveObjectModificationMutation) ->
36+
RecursiveMutations.ShuffleAndCutModifications()
37+
else ->
38+
RecursiveMutations.Mutate()
39+
}.mutate(resultToMutate, recursive, random, configuration)
40+
}
41+
is Result.Collection<TYPE, RESULT> -> if (resultToMutate.modify.isNotEmpty()) {
42+
when {
43+
random.flipCoin(100 - configuration.probCollectionShuffleInsteadResultMutation) ->
44+
CollectionMutations.Mutate()
45+
else ->
46+
CollectionMutations.Shuffle<TYPE, RESULT>()
47+
}.mutate(resultToMutate, recursive, random, configuration)
48+
} else {
49+
resultToMutate
50+
}
51+
is Result.Empty -> resultToMutate
52+
}
53+
return Node(node.result.toMutableList().apply {
54+
set(indexOfMutatedResult, mutated)
55+
}, node.parameters, node.builder)
56+
}
57+
58+
/**
59+
* Rates somehow the result.
60+
*
61+
* For example, fuzzing should not try to mutate some empty structures, like empty collections or objects.
62+
*/
63+
private fun <TYPE, RESULT> rate(result: Result<TYPE, RESULT>): Double {
64+
if (!canMutate(result)) {
65+
return ALMOST_ZERO
66+
}
67+
return when (result) {
68+
is Result.Recursive<TYPE, RESULT> -> if (result.construct.parameters.isEmpty() and result.modify.isEmpty()) ALMOST_ZERO else 0.5
69+
is Result.Collection<TYPE, RESULT> -> if (result.iterations == 0) return ALMOST_ZERO else 0.7
70+
is StringValue -> 2.0
71+
is Result.Known<TYPE, RESULT, *> -> 1.2
72+
is Result.Simple<TYPE, RESULT> -> 2.0
73+
is Result.Empty -> ALMOST_ZERO
74+
}
75+
}
76+
77+
private fun <TYPE, RESULT> canMutate(node: Result<TYPE, RESULT>): Boolean {
78+
return when (node) {
79+
is Result.Simple<TYPE, RESULT> -> node.mutation === emptyMutation<RESULT>()
80+
is Result.Known<TYPE, RESULT, *> -> node.value.mutations().isNotEmpty()
81+
is Result.Recursive<TYPE, RESULT> -> node.modify.isNotEmpty()
82+
is Result.Collection<TYPE, RESULT> -> node.modify.isNotEmpty() && node.iterations > 0
83+
is Result.Empty<TYPE, RESULT> -> false
84+
}
85+
}
86+
87+
@Suppress("UNCHECKED_CAST")
88+
private fun <TYPE, RESULT, T : KnownValue<T>> Result.Known<TYPE, RESULT, *>.mutate(mutation: Mutation<T>, random: Random, configuration: Configuration): Result.Known<TYPE, RESULT, T> {
89+
val source: T = value as T
90+
val mutate = mutation.mutate(source, random, configuration)
91+
return Result.Known(
92+
mutate,
93+
build as (T) -> RESULT
94+
)
95+
}
96+
}
97+
98+
private const val ALMOST_ZERO = 1E-7
99+
private val IDENTITY_MUTATION: (Any, random: Random) -> Any = { f, _ -> f }
100+
101+
fun <RESULT> emptyMutation(): (RESULT, random: Random) -> RESULT {
102+
@Suppress("UNCHECKED_CAST")
103+
return IDENTITY_MUTATION as (RESULT, random: Random) -> RESULT
104+
}
105+
9106
/**
10107
* Mutations is an object which applies some changes to the source object
11108
* and then returns a new object (or old one without changes).
@@ -14,20 +111,14 @@ fun interface Mutation<T> {
14111
fun mutate(source: T, random: Random, configuration: Configuration): T
15112
}
16113

17-
inline fun <reified T, reified F : T> Mutation<F>.adapt(): Mutation<T> {
18-
return Mutation { s, r, c ->
19-
if (s is F) return@Mutation mutate(s, r, c) else s
20-
}
21-
}
22-
23114
sealed class BitVectorMutations : Mutation<BitVectorValue> {
24115

25116
abstract fun rangeOfMutation(source: BitVectorValue): IntRange
26117

27118
override fun mutate(source: BitVectorValue, random: Random, configuration: Configuration): BitVectorValue {
28119
with (rangeOfMutation(source)) {
29120
val firstBits = random.nextInt(start, endInclusive.coerceAtLeast(1))
30-
return BitVectorValue(source).apply { this[firstBits] = !this[firstBits] }
121+
return BitVectorValue(source, this@BitVectorMutations).apply { this[firstBits] = !this[firstBits] }
31122
}
32123
}
33124

@@ -44,31 +135,194 @@ sealed class BitVectorMutations : Mutation<BitVectorValue> {
44135
}
45136
}
46137

47-
sealed class IEEE754Mutations : Mutation<IEEE754Value> {
138+
sealed interface IEEE754Mutations : Mutation<IEEE754Value> {
48139

49-
object ChangeSign : IEEE754Mutations() {
140+
object ChangeSign : IEEE754Mutations {
50141
override fun mutate(source: IEEE754Value, random: Random, configuration: Configuration): IEEE754Value {
51-
return IEEE754Value(source).apply {
142+
return IEEE754Value(source, this).apply {
52143
setRaw(0, !getRaw(0))
53144
}
54145
}
55146
}
56147

57-
object Mantissa : IEEE754Mutations() {
148+
object Mantissa : IEEE754Mutations {
58149
override fun mutate(source: IEEE754Value, random: Random, configuration: Configuration): IEEE754Value {
59150
val i = random.nextInt(0, source.mantissaSize)
60-
return IEEE754Value(source).apply {
151+
return IEEE754Value(source, this).apply {
61152
setRaw(1 + exponentSize + i, !getRaw(1 + exponentSize + i))
62153
}
63154
}
64155
}
65156

66-
object Exponent : IEEE754Mutations() {
157+
object Exponent : IEEE754Mutations {
67158
override fun mutate(source: IEEE754Value, random: Random, configuration: Configuration): IEEE754Value {
68159
val i = random.nextInt(0, source.exponentSize)
69-
return IEEE754Value(source).apply {
160+
return IEEE754Value(source, this).apply {
70161
setRaw(1 + i, !getRaw(1 + i))
71162
}
72163
}
73164
}
165+
}
166+
167+
sealed interface StringMutations : Mutation<StringValue> {
168+
169+
object AddCharacter : StringMutations {
170+
override fun mutate(source: StringValue, random: Random, configuration: Configuration): StringValue {
171+
val value = source.value
172+
if (value.length >= configuration.maxStringLengthWhenMutated) {
173+
return source
174+
}
175+
val position = random.nextInt(value.length + 1)
176+
val charToMutate = if (value.isNotEmpty()) {
177+
value.random(random)
178+
} else {
179+
// use any meaningful character from the ascii table
180+
random.nextInt(33, 127).toChar()
181+
}
182+
val newString = buildString {
183+
append(value.substring(0, position))
184+
// try to change char to some that is close enough to origin char
185+
val charTableSpread = 64
186+
if (random.nextBoolean()) {
187+
append(charToMutate - random.nextInt(1, charTableSpread))
188+
} else {
189+
append(charToMutate + random.nextInt(1, charTableSpread))
190+
}
191+
append(value.substring(position, value.length))
192+
}
193+
return StringValue(newString, lastMutation = this)
194+
}
195+
}
196+
197+
object RemoveCharacter : StringMutations {
198+
override fun mutate(source: StringValue, random: Random, configuration: Configuration): StringValue {
199+
val value = source.value
200+
val position = random.nextInt(value.length + 1)
201+
if (position >= value.length) return source
202+
val toRemove = random.nextInt(value.length)
203+
val newString = buildString {
204+
append(value.substring(0, toRemove))
205+
append(value.substring(toRemove + 1, value.length))
206+
}
207+
return StringValue(newString, this)
208+
}
209+
}
210+
}
211+
212+
fun interface NodeMutation<TYPE, RESULT> : Mutation<Node<TYPE, RESULT>>
213+
214+
sealed interface CollectionMutations<TYPE, RESULT> : Mutation<Pair<Result.Collection<TYPE, RESULT>, NodeMutation<TYPE, RESULT>>> {
215+
216+
override fun mutate(
217+
source: Pair<Result.Collection<TYPE, RESULT>, NodeMutation<TYPE, RESULT>>,
218+
random: Random,
219+
configuration: Configuration
220+
): Pair<Result.Collection<TYPE, RESULT>, NodeMutation<TYPE, RESULT>> {
221+
return mutate(source.first, source.second, random, configuration) to source.second
222+
}
223+
224+
fun mutate(
225+
source: Result.Collection<TYPE, RESULT>,
226+
recursive: NodeMutation<TYPE, RESULT>,
227+
random: Random,
228+
configuration: Configuration
229+
) : Result.Collection<TYPE, RESULT>
230+
231+
class Shuffle<TYPE, RESULT> : CollectionMutations<TYPE, RESULT> {
232+
override fun mutate(
233+
source: Result.Collection<TYPE, RESULT>,
234+
recursive: NodeMutation<TYPE, RESULT>,
235+
random: Random,
236+
configuration: Configuration
237+
): Result.Collection<TYPE, RESULT> {
238+
return Result.Collection(
239+
construct = source.construct,
240+
modify = source.modify.toMutableList().shuffled(random),
241+
iterations = source.iterations
242+
)
243+
}
244+
}
245+
246+
class Mutate<TYPE, RESULT> : CollectionMutations<TYPE, RESULT> {
247+
override fun mutate(
248+
source: Result.Collection<TYPE, RESULT>,
249+
recursive: NodeMutation<TYPE, RESULT>,
250+
random: Random,
251+
configuration: Configuration
252+
): Result.Collection<TYPE, RESULT> {
253+
return Result.Collection(
254+
construct = source.construct,
255+
modify = source.modify.toMutableList().apply {
256+
val i = random.nextInt(0, source.modify.size)
257+
set(i, recursive.mutate(source.modify[i], random, configuration))
258+
},
259+
iterations = source.iterations
260+
)
261+
}
262+
}
263+
}
264+
265+
sealed interface RecursiveMutations<TYPE, RESULT> : Mutation<Pair<Result.Recursive<TYPE, RESULT>, NodeMutation<TYPE, RESULT>>> {
266+
267+
override fun mutate(
268+
source: Pair<Result.Recursive<TYPE, RESULT>, NodeMutation<TYPE, RESULT>>,
269+
random: Random,
270+
configuration: Configuration
271+
): Pair<Result.Recursive<TYPE, RESULT>, NodeMutation<TYPE, RESULT>> {
272+
return mutate(source.first, source.second, random, configuration) to source.second
273+
}
274+
275+
fun mutate(
276+
source: Result.Recursive<TYPE, RESULT>,
277+
recursive: NodeMutation<TYPE, RESULT>,
278+
random: Random,
279+
configuration: Configuration
280+
) : Result.Recursive<TYPE, RESULT>
281+
282+
283+
class Constructor<TYPE, RESULT> : RecursiveMutations<TYPE, RESULT> {
284+
override fun mutate(
285+
source: Result.Recursive<TYPE, RESULT>,
286+
recursive: NodeMutation<TYPE, RESULT>,
287+
random: Random,
288+
configuration: Configuration
289+
): Result.Recursive<TYPE, RESULT> {
290+
return Result.Recursive(
291+
construct = recursive.mutate(source.construct,random, configuration),
292+
modify = source.modify
293+
)
294+
}
295+
}
296+
297+
class ShuffleAndCutModifications<TYPE, RESULT> : RecursiveMutations<TYPE, RESULT> {
298+
override fun mutate(
299+
source: Result.Recursive<TYPE, RESULT>,
300+
recursive: NodeMutation<TYPE, RESULT>,
301+
random: Random,
302+
configuration: Configuration
303+
): Result.Recursive<TYPE, RESULT> {
304+
return Result.Recursive(
305+
construct = source.construct,
306+
modify = source.modify.shuffled(random).take(random.nextInt(source.modify.size + 1).coerceAtLeast(1))
307+
)
308+
}
309+
}
310+
311+
class Mutate<TYPE, RESULT> : RecursiveMutations<TYPE, RESULT> {
312+
override fun mutate(
313+
source: Result.Recursive<TYPE, RESULT>,
314+
recursive: NodeMutation<TYPE, RESULT>,
315+
random: Random,
316+
configuration: Configuration
317+
): Result.Recursive<TYPE, RESULT> {
318+
return Result.Recursive(
319+
construct = source.construct,
320+
modify = source.modify.toMutableList().apply {
321+
val i = random.nextInt(0, source.modify.size)
322+
set(i, recursive.mutate(source.modify[i], random, configuration))
323+
}
324+
)
325+
}
326+
327+
}
74328
}

0 commit comments

Comments
 (0)