diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt index fef1a3cdee..57840fc727 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt @@ -7,7 +7,6 @@ import org.utbot.fuzzing.seeds.KnownValue import org.utbot.fuzzing.utils.MissedSeed import org.utbot.fuzzing.utils.chooseOne import org.utbot.fuzzing.utils.flipCoin -import org.utbot.fuzzing.utils.transformIfNotEmpty import kotlin.random.Random private val logger by lazy { KotlinLogging.logger {} } @@ -48,9 +47,9 @@ interface Fuzzing, FEEDBACK : Feed * Then it generates values and runs them with this method. This method should provide some feedback, * which is the most important part for a good fuzzing result. [emptyFeedback] can be provided only for test * or infinite loops. Consider implementing own implementation of [Feedback] to provide more correct data or - * use [BaseFeedback] to generate key based feedback. In this case, the key is used to analyse what value should be next. + * use [BaseFeedback] to generate key based feedback. In this case, the key is used to analyze what value should be next. * - * @param description contains user-defined information about current run. Can be used as a state of the run. + * @param description contains user-defined information about the current run. Can be used as a state of the run. * @param values current values to process. */ suspend fun handle(description: DESCRIPTION, values: List): FEEDBACK @@ -74,7 +73,7 @@ interface Fuzzing, FEEDBACK : Feed } /** - * Some description of current fuzzing run. Usually, it contains the name of the target method and its parameter list. + * Some description of the current fuzzing run. Usually, it contains the name of the target method and its parameter list. */ open class Description( parameters: List @@ -126,12 +125,12 @@ sealed interface Seed { * Known value is a typical value that can be manipulated by fuzzing without knowledge about object structure * in concrete language. For example, integer can be represented as a bit vector of n-bits. * - * List of the known to fuzzing values are: + * The list of the known to fuzzing values are: * * 1. BitVectorValue represents a vector of bits. * 2. ... */ - class Known(val value: V, val build: (V) -> RESULT): Seed + class Known>(val value: V, val build: (V) -> RESULT): Seed /** * Recursive value defines an object with typically has a constructor and list of modifications. @@ -187,7 +186,7 @@ sealed class Routine(val types: List) : Iterable by types { } /** - * Creates a collection of concrete size. + * Creates a collection of concrete sizes. */ class Collection( val builder: (size: Int) -> R, @@ -196,7 +195,7 @@ sealed class Routine(val types: List) : Iterable by types { } /** - * Is called for collection with index of iterations. + * Is called for a collection with index of iterations. */ class ForEach( types: List, @@ -240,7 +239,7 @@ interface Feedback : AsKey { * Base implementation of [Feedback]. * * NB! [VALUE] type must implement [equals] and [hashCode] due to the fact it uses as a key in map. - * If it doesn't implement those method, [OutOfMemoryError] is possible. + * If it doesn't implement those methods, [OutOfMemoryError] is possible. */ data class BaseFeedback( val result: VALUE, @@ -252,12 +251,12 @@ data class BaseFeedback( */ enum class Control { /** - * Analyse feedback and continue. + * Analyze feedback and continue. */ CONTINUE, /** - * Do not process this feedback and just start next value generation. + * Do not process this feedback and just start the next value generation. */ PASS, @@ -312,6 +311,7 @@ private suspend fun , F : Feedback> Fuzzing>>() + val mutationFactory = MutationFactory() fun fuzzOne(parameters: List): Node = fuzz( parameters = parameters, fuzzing = fuzzing, @@ -319,24 +319,22 @@ private suspend fun , F : Feedback> Fuzzing, FEEDBACK : Feedback< // we need to check cases when one value is passed for different arguments results.random(random) } else { - produce(type, fuzzing, description, random, configuration, State( - state.recursionTreeDepth, - state.cache, - state.missedTypes, - state.iterations, - index - )).also { + produce(type, fuzzing, description, random, configuration, state.copy { + parameterIndex = index + }).also { results += it } } @@ -418,10 +412,9 @@ private fun , FEEDBACK : Feedback< throw NoSeedValueException(type) } val candidates = seeds.map { - @Suppress("UNCHECKED_CAST") when (it) { is Seed.Simple -> Result.Simple(it.value, it.mutation) - is Seed.Known -> Result.Known(it.value, it.build as KnownValue.() -> RESULT) + is Seed.Known -> it.asResult() is Seed.Recursive -> reduce(it, fuzzing, description, random, configuration, state) is Seed.Collection -> reduce(it, fuzzing, description, random, configuration, state) } @@ -446,7 +439,8 @@ private fun , FEEDBACK : Feedback< } else try { val iterations = when { state.iterations >= 0 && random.flipCoin(configuration.probCreateRectangleCollectionInsteadSawLike) -> state.iterations - else -> random.nextInt(0, configuration.collectionIterations + 1) + random.flipCoin(configuration.probEmptyCollectionCreation) -> 0 + else -> random.nextInt(1, configuration.collectionIterations + 1) } Result.Collection( construct = fuzz( @@ -456,18 +450,23 @@ private fun , FEEDBACK : Feedback< random, configuration, task.construct, - State(state.recursionTreeDepth + 1, state.cache, state.missedTypes, iterations) - ), - modify = if (random.flipCoin(configuration.probCollectionMutationInsteadCreateNew)) { - val result = fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, State(state.recursionTreeDepth + 1, state.cache, state.missedTypes, iterations)) - arrayListOf(result).apply { - (1 until iterations).forEach { _ -> - add(mutate(result, fuzzing, random, configuration, state)) - } + state.copy { + recursionTreeDepth++ } + ), + modify = if (random.flipCoin(configuration.probCollectionDuplicationInsteadCreateNew)) { + val result = fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, state.copy { + recursionTreeDepth++ + this.iterations = iterations + parameterIndex = -1 + }) + List(iterations) { result } } else { (0 until iterations).map { - fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, State(state.recursionTreeDepth + 1, state.cache, state.missedTypes, iterations)) + fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, state.copy { + recursionTreeDepth++ + this.iterations = iterations + }) } }, iterations = iterations @@ -506,14 +505,18 @@ private fun , FEEDBACK : Feedback< random, configuration, task.construct, - State(state.recursionTreeDepth + 1, state.cache, state.missedTypes) + state.copy { + recursionTreeDepth++ + iterations = -1 + parameterIndex = -1 + } ), modify = task.modify - .toMutableList() - .transformIfNotEmpty { - shuffle(random) - take(random.nextInt(size + 1)) - } +// .toMutableList() +// .transformIfNotEmpty { +// shuffle(random) +// take(random.nextInt(size + 1)) +// } .mapTo(arrayListOf()) { routine -> fuzz( routine.types, @@ -522,7 +525,11 @@ private fun , FEEDBACK : Feedback< random, configuration, routine, - State(state.recursionTreeDepth + 1, state.cache, state.missedTypes) + state.copy { + recursionTreeDepth++ + iterations = -1 + parameterIndex = -1 + } ) } ) @@ -537,86 +544,6 @@ private fun , FEEDBACK : Feedback< } } -/** - * Starts mutations of some seeds from the object tree. - */ -@Suppress("UNCHECKED_CAST") -private fun , FEEDBACK : Feedback> mutate( - node: Node, - fuzzing: Fuzzing, - random: Random, - configuration: Configuration, - state: State, -): Node { - if (node.result.isEmpty()) return node - val indexOfMutatedResult = random.chooseOne(node.result.map(::rate).toDoubleArray()) - val mutated = when (val resultToMutate = node.result[indexOfMutatedResult]) { - is Result.Simple -> Result.Simple(resultToMutate.mutation(resultToMutate.result, random), resultToMutate.mutation) - is Result.Known -> { - val mutations = resultToMutate.value.mutations() - if (mutations.isNotEmpty()) { - Result.Known( - mutations.random(random).mutate(resultToMutate.value, random, configuration), - resultToMutate.build as KnownValue.() -> RESULT - ) - } else { - resultToMutate - } - } - is Result.Recursive -> { - if (resultToMutate.modify.isEmpty() || random.flipCoin(configuration.probConstructorMutationInsteadModificationMutation)) { - Result.Recursive( - construct = mutate(resultToMutate.construct, fuzzing, random, configuration, State(state.recursionTreeDepth + 1, state.cache, state.missedTypes)), - modify = resultToMutate.modify - ) - } else if (random.flipCoin(configuration.probShuffleAndCutRecursiveObjectModificationMutation)) { - Result.Recursive( - construct = resultToMutate.construct, - modify = resultToMutate.modify.shuffled(random).take(random.nextInt(resultToMutate.modify.size + 1).coerceAtLeast(1)) - ) - } else { - Result.Recursive( - construct = resultToMutate.construct, - modify = resultToMutate.modify.toMutableList().apply { - val i = random.nextInt(0, resultToMutate.modify.size) - set(i, mutate(resultToMutate.modify[i], fuzzing, random, configuration, State(state.recursionTreeDepth + 1, state.cache, state.missedTypes))) - } - ) - } - } - is Result.Collection -> Result.Collection( - construct = resultToMutate.construct, - modify = resultToMutate.modify.toMutableList().apply { - if (isNotEmpty()) { - if (random.flipCoin(100 - configuration.probCollectionShuffleInsteadResultMutation)) { - val i = random.nextInt(0, resultToMutate.modify.size) - set(i, mutate(resultToMutate.modify[i], fuzzing, random, configuration, State(state.recursionTreeDepth + 1, state.cache, state.missedTypes))) - } else { - shuffle(random) - } - } - }, - iterations = resultToMutate.iterations - ) - is Result.Empty -> resultToMutate - } - return Node(node.result.toMutableList().apply { - set(indexOfMutatedResult, mutated) - }, node.parameters, node.builder) -} - -/** - * Rates somehow the result. - * - * For example, fuzzing should not try to mutate some empty structures, like empty collections or objects. - */ -private fun rate(result: Result): Double { - return when (result) { - is Result.Recursive -> if (result.construct.parameters.isEmpty() and result.modify.isEmpty()) 1E-7 else 0.5 - is Result.Collection -> if (result.iterations == 0) return 0.01 else 0.7 - else -> 1.0 - } -} /** * Creates a real result. @@ -626,7 +553,7 @@ private fun rate(result: Result): Double { @Suppress("UNCHECKED_CAST") private fun create(result: Result): R = when(result) { is Result.Simple -> result.result - is Result.Known -> (result.build as KnownValue.() -> R)(result.value) + is Result.Known -> (result.build as KnownValue<*>.() -> R)(result.value) is Result.Recursive -> with(result) { val obj: R = when (val c = construct.builder) { is Routine.Create -> c(construct.result.map { create(it) }) @@ -670,27 +597,52 @@ private data class PassRoutine(val description: String) : Routine(em * Internal state for one fuzzing run. */ private class State( - val recursionTreeDepth: Int = 1, val cache: MutableMap>>, val missedTypes: MissedSeed, + val recursionTreeDepth: Int = 1, val iterations: Int = -1, val parameterIndex: Int = -1, -) +) { + + fun copy(block: Builder.() -> Unit): State { + return Builder(this).apply(block).build() + } + + class Builder( + state: State + ) { + var recursionTreeDepth: Int = state.recursionTreeDepth + var cache: MutableMap>> = state.cache + var missedTypes: MissedSeed = state.missedTypes + var iterations: Int = state.iterations + var parameterIndex: Int = state.parameterIndex + + fun build(): State { + return State( + cache, + missedTypes, + recursionTreeDepth, + iterations, + parameterIndex, + ) + } + } +} /** * The result of producing real values for the language. */ -private sealed interface Result { +sealed interface Result { /** * Simple result as is. */ - class Simple(val result: RESULT, val mutation: (RESULT, random: Random) -> RESULT = { f, _ -> f }) : Result + class Simple(val result: RESULT, val mutation: (RESULT, random: Random) -> RESULT = emptyMutation()) : Result /** * Known value. */ - class Known(val value: V, val build: (V) -> RESULT) : Result + class Known>(val value: V, val build: (V) -> RESULT) : Result /** * A tree of object that has constructor and some modifications. */ @@ -719,7 +671,7 @@ private sealed interface Result { /** * Temporary object to storage information about partly calculated values tree. */ -private class Node( +class Node( val result: List>, val parameters: List, val builder: Routine, @@ -770,3 +722,12 @@ private class StatisticImpl>( fun isNotEmpty() = seeds.isNotEmpty() } ///endregion + + +///region Utilities +@Suppress("UNCHECKED_CAST") +private fun > Seed.Known.asResult(): Result.Known { + val value: T = value as T + return Result.Known(value, build as KnownValue.() -> RESULT) +} +///endregion diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt index 6f43fadbc2..8ff0e59b4e 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt @@ -37,17 +37,22 @@ data class Configuration( /** * Probability of creating shifted array values instead of generating new values for modification. */ - var probCollectionMutationInsteadCreateNew: Int = 50, + var probCollectionDuplicationInsteadCreateNew: Int = 10, + + /** + * Probability of creating empty collections + */ + var probEmptyCollectionCreation: Int = 1, /** * Probability to prefer change constructor instead of modification. */ - var probConstructorMutationInsteadModificationMutation: Int = 90, + var probConstructorMutationInsteadModificationMutation: Int = 30, /** - * Probability to shuffle modification list of the recursive object + * Probability to a shuffle modification list of the recursive object */ - var probShuffleAndCutRecursiveObjectModificationMutation: Int = 10, + var probShuffleAndCutRecursiveObjectModificationMutation: Int = 30, /** * Probability to prefer create rectangle collections instead of creating saw-like one. @@ -62,17 +67,7 @@ data class Configuration( /** * When mutating StringValue a new string will not exceed this value. */ - var maxStringLengthWhenMutated: Int = 64, - - /** - * Probability of adding a new character when mutating StringValue - */ - var probStringAddCharacter: Int = 50, - - /** - * Probability of removing an old character from StringValue when mutating - */ - var probStringRemoveCharacter: Int = 50, + var maxStringLengthWhenMutated: Int = 128, /** * Probability of reusing same generated value when 2 or more parameters have the same type. diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt index 77fae48428..7d5f004592 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt @@ -4,8 +4,105 @@ package org.utbot.fuzzing import org.utbot.fuzzing.seeds.BitVectorValue import org.utbot.fuzzing.seeds.IEEE754Value +import org.utbot.fuzzing.seeds.KnownValue +import org.utbot.fuzzing.seeds.StringValue +import org.utbot.fuzzing.utils.chooseOne +import org.utbot.fuzzing.utils.flipCoin import kotlin.random.Random +class MutationFactory { + + fun mutate(node: Node, random: Random, configuration: Configuration): Node { + if (node.result.isEmpty()) return node + val indexOfMutatedResult = random.chooseOne(node.result.map(::rate).toDoubleArray()) + val recursive: NodeMutation = NodeMutation { n, r, c -> + mutate(n, r, c) + } + val mutated = when (val resultToMutate = node.result[indexOfMutatedResult]) { + is Result.Simple -> Result.Simple(resultToMutate.mutation(resultToMutate.result, random), resultToMutate.mutation) + is Result.Known -> { + val mutations = resultToMutate.value.mutations() + if (mutations.isNotEmpty()) { + resultToMutate.mutate(mutations.random(random), random, configuration) + } else { + resultToMutate + } + } + is Result.Recursive -> { + when { + resultToMutate.modify.isEmpty() || random.flipCoin(configuration.probConstructorMutationInsteadModificationMutation) -> + RecursiveMutations.Constructor() + random.flipCoin(configuration.probShuffleAndCutRecursiveObjectModificationMutation) -> + RecursiveMutations.ShuffleAndCutModifications() + else -> + RecursiveMutations.Mutate() + }.mutate(resultToMutate, recursive, random, configuration) + } + is Result.Collection -> if (resultToMutate.modify.isNotEmpty()) { + when { + random.flipCoin(100 - configuration.probCollectionShuffleInsteadResultMutation) -> + CollectionMutations.Mutate() + else -> + CollectionMutations.Shuffle() + }.mutate(resultToMutate, recursive, random, configuration) + } else { + resultToMutate + } + is Result.Empty -> resultToMutate + } + return Node(node.result.toMutableList().apply { + set(indexOfMutatedResult, mutated) + }, node.parameters, node.builder) + } + + /** + * Rates somehow the result. + * + * For example, fuzzing should not try to mutate some empty structures, like empty collections or objects. + */ + private fun rate(result: Result): Double { + if (!canMutate(result)) { + return ALMOST_ZERO + } + return when (result) { + is Result.Recursive -> if (result.construct.parameters.isEmpty() and result.modify.isEmpty()) ALMOST_ZERO else 0.5 + is Result.Collection -> if (result.iterations == 0) return ALMOST_ZERO else 0.7 + is StringValue -> 2.0 + is Result.Known -> 1.2 + is Result.Simple -> 2.0 + is Result.Empty -> ALMOST_ZERO + } + } + + private fun canMutate(node: Result): Boolean { + return when (node) { + is Result.Simple -> node.mutation === emptyMutation() + is Result.Known -> node.value.mutations().isNotEmpty() + is Result.Recursive -> node.modify.isNotEmpty() + is Result.Collection -> node.modify.isNotEmpty() && node.iterations > 0 + is Result.Empty -> false + } + } + + @Suppress("UNCHECKED_CAST") + private fun > Result.Known.mutate(mutation: Mutation, random: Random, configuration: Configuration): Result.Known { + val source: T = value as T + val mutate = mutation.mutate(source, random, configuration) + return Result.Known( + mutate, + build as (T) -> RESULT + ) + } +} + +private const val ALMOST_ZERO = 1E-7 +private val IDENTITY_MUTATION: (Any, random: Random) -> Any = { f, _ -> f } + +fun emptyMutation(): (RESULT, random: Random) -> RESULT { + @Suppress("UNCHECKED_CAST") + return IDENTITY_MUTATION as (RESULT, random: Random) -> RESULT +} + /** * Mutations is an object which applies some changes to the source object * and then returns a new object (or old one without changes). @@ -14,12 +111,6 @@ fun interface Mutation { fun mutate(source: T, random: Random, configuration: Configuration): T } -inline fun Mutation.adapt(): Mutation { - return Mutation { s, r, c -> - if (s is F) return@Mutation mutate(s, r, c) else s - } -} - sealed class BitVectorMutations : Mutation { abstract fun rangeOfMutation(source: BitVectorValue): IntRange @@ -27,7 +118,7 @@ sealed class BitVectorMutations : Mutation { override fun mutate(source: BitVectorValue, random: Random, configuration: Configuration): BitVectorValue { with (rangeOfMutation(source)) { val firstBits = random.nextInt(start, endInclusive.coerceAtLeast(1)) - return BitVectorValue(source).apply { this[firstBits] = !this[firstBits] } + return BitVectorValue(source, this@BitVectorMutations).apply { this[firstBits] = !this[firstBits] } } } @@ -44,31 +135,194 @@ sealed class BitVectorMutations : Mutation { } } -sealed class IEEE754Mutations : Mutation { +sealed interface IEEE754Mutations : Mutation { - object ChangeSign : IEEE754Mutations() { + object ChangeSign : IEEE754Mutations { override fun mutate(source: IEEE754Value, random: Random, configuration: Configuration): IEEE754Value { - return IEEE754Value(source).apply { + return IEEE754Value(source, this).apply { setRaw(0, !getRaw(0)) } } } - object Mantissa : IEEE754Mutations() { + object Mantissa : IEEE754Mutations { override fun mutate(source: IEEE754Value, random: Random, configuration: Configuration): IEEE754Value { val i = random.nextInt(0, source.mantissaSize) - return IEEE754Value(source).apply { + return IEEE754Value(source, this).apply { setRaw(1 + exponentSize + i, !getRaw(1 + exponentSize + i)) } } } - object Exponent : IEEE754Mutations() { + object Exponent : IEEE754Mutations { override fun mutate(source: IEEE754Value, random: Random, configuration: Configuration): IEEE754Value { val i = random.nextInt(0, source.exponentSize) - return IEEE754Value(source).apply { + return IEEE754Value(source, this).apply { setRaw(1 + i, !getRaw(1 + i)) } } } +} + +sealed interface StringMutations : Mutation { + + object AddCharacter : StringMutations { + override fun mutate(source: StringValue, random: Random, configuration: Configuration): StringValue { + val value = source.value + if (value.length >= configuration.maxStringLengthWhenMutated) { + return source + } + val position = random.nextInt(value.length + 1) + val charToMutate = if (value.isNotEmpty()) { + value.random(random) + } else { + // use any meaningful character from the ascii table + random.nextInt(33, 127).toChar() + } + val newString = buildString { + append(value.substring(0, position)) + // try to change char to some that is close enough to origin char + val charTableSpread = 64 + if (random.nextBoolean()) { + append(charToMutate - random.nextInt(1, charTableSpread)) + } else { + append(charToMutate + random.nextInt(1, charTableSpread)) + } + append(value.substring(position, value.length)) + } + return StringValue(newString, lastMutation = this) + } + } + + object RemoveCharacter : StringMutations { + override fun mutate(source: StringValue, random: Random, configuration: Configuration): StringValue { + val value = source.value + val position = random.nextInt(value.length + 1) + if (position >= value.length) return source + val toRemove = random.nextInt(value.length) + val newString = buildString { + append(value.substring(0, toRemove)) + append(value.substring(toRemove + 1, value.length)) + } + return StringValue(newString, this) + } + } +} + +fun interface NodeMutation : Mutation> + +sealed interface CollectionMutations : Mutation, NodeMutation>> { + + override fun mutate( + source: Pair, NodeMutation>, + random: Random, + configuration: Configuration + ): Pair, NodeMutation> { + return mutate(source.first, source.second, random, configuration) to source.second + } + + fun mutate( + source: Result.Collection, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ) : Result.Collection + + class Shuffle : CollectionMutations { + override fun mutate( + source: Result.Collection, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ): Result.Collection { + return Result.Collection( + construct = source.construct, + modify = source.modify.toMutableList().shuffled(random), + iterations = source.iterations + ) + } + } + + class Mutate : CollectionMutations { + override fun mutate( + source: Result.Collection, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ): Result.Collection { + return Result.Collection( + construct = source.construct, + modify = source.modify.toMutableList().apply { + val i = random.nextInt(0, source.modify.size) + set(i, recursive.mutate(source.modify[i], random, configuration)) + }, + iterations = source.iterations + ) + } + } +} + +sealed interface RecursiveMutations : Mutation, NodeMutation>> { + + override fun mutate( + source: Pair, NodeMutation>, + random: Random, + configuration: Configuration + ): Pair, NodeMutation> { + return mutate(source.first, source.second, random, configuration) to source.second + } + + fun mutate( + source: Result.Recursive, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ) : Result.Recursive + + + class Constructor : RecursiveMutations { + override fun mutate( + source: Result.Recursive, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ): Result.Recursive { + return Result.Recursive( + construct = recursive.mutate(source.construct,random, configuration), + modify = source.modify + ) + } + } + + class ShuffleAndCutModifications : RecursiveMutations { + override fun mutate( + source: Result.Recursive, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ): Result.Recursive { + return Result.Recursive( + construct = source.construct, + modify = source.modify.shuffled(random).take(random.nextInt(source.modify.size + 1).coerceAtLeast(1)) + ) + } + } + + class Mutate : RecursiveMutations { + override fun mutate( + source: Result.Recursive, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ): Result.Recursive { + return Result.Recursive( + construct = source.construct, + modify = source.modify.toMutableList().apply { + val i = random.nextInt(0, source.modify.size) + set(i, recursive.mutate(source.modify[i], random, configuration)) + } + ) + } + + } } \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/HttpRequest.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/HttpRequest.kt new file mode 100644 index 0000000000..844336aad1 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/HttpRequest.kt @@ -0,0 +1,60 @@ +package org.utbot.fuzzing.demo + +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import org.utbot.fuzzing.* +import java.util.concurrent.TimeUnit + +fun main() = runBlocking { + withTimeout(TimeUnit.SECONDS.toMillis(10)) { + object : Fuzzing, Feedback> { + override fun generate(description: Description, type: String) = sequence> { + when (type) { + "url" -> yield(Seed.Recursive( + construct = Routine.Create( + listOf("protocol", "host", "port", "path") + ) { + val (protocol, host, port, path) = it + "$protocol://$host${if (port.isNotBlank()) ":$port" else ""}/$path" + }, + empty = Routine.Empty { error("error") } + )) + "protocol" -> { + yield(Seed.Simple("http")) + yield(Seed.Simple("https")) + yield(Seed.Simple("ftp")) + } + "host" -> { + yield(Seed.Simple("localhost")) + yield(Seed.Simple("127.0.0.1")) + } + "port" -> { + yield(Seed.Simple("8080")) + yield(Seed.Simple("")) + } + "path" -> yield(Seed.Recursive( + construct = Routine.Create(listOf("page", "id")) { + it.joinToString("/") + }, + empty = Routine.Empty { error("error") } + )) + "page" -> { + yield(Seed.Simple("owners")) + yield(Seed.Simple("users")) + yield(Seed.Simple("admins")) + } + "id" -> (0..1000).forEach { yield(Seed.Simple(it.toString())) } + else -> error("unknown type '$type'") + } + } + + override suspend fun handle( + description: Description, + values: List + ): Feedback { + println(values[0]) + return BaseFeedback(values[0], Control.PASS) + } + }.fuzz(Description(listOf("url"))) + } +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/JsonFuzzing.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/JsonFuzzing.kt index ce51986901..80863e4053 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/JsonFuzzing.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/JsonFuzzing.kt @@ -30,7 +30,6 @@ private class JsonBuilder( * * Also, some utility class such as [BaseFuzzing] and [TypeProvider] are used to start fuzzing without class inheritance. */ -@Suppress("RemoveExplicitTypeArguments") suspend fun main() { var count = 0 val set = mutableMapOf() @@ -85,7 +84,7 @@ suspend fun main() { println(result) set.computeIfAbsent(result) { AtomicLong() }.incrementAndGet() if (++count < 10000) emptyFeedback() else { - println("Duplication ratio:" + set.size / count.toDouble()) + println("Unique ratio:" + set.size / count.toDouble()) error("Forced from the example") } }.fuzz( diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/BitVectorValue.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/BitVectorValue.kt index 6b4e15669d..895c677a95 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/BitVectorValue.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/BitVectorValue.kt @@ -5,7 +5,7 @@ import org.utbot.fuzzing.utils.Endian import java.math.BigInteger import java.util.BitSet -class BitVectorValue : KnownValue { +class BitVectorValue : KnownValue { /** * Vector of value bits. @@ -15,8 +15,8 @@ class BitVectorValue : KnownValue { */ private val vector: BitSet val size: Int - override val lastMutation: Mutation? - override val mutatedFrom: KnownValue? + override val lastMutation: Mutation? + override val mutatedFrom: BitVectorValue? constructor(bits: Int, bound: Bound) { vector = BitSet(bits).also { @@ -29,7 +29,7 @@ class BitVectorValue : KnownValue { mutatedFrom = null } - constructor(other: BitVectorValue, mutation: Mutation? = null) { + constructor(other: BitVectorValue, mutation: Mutation? = null) { vector = other.vector.clone() as BitSet size = other.size lastMutation = mutation @@ -80,10 +80,10 @@ class BitVectorValue : KnownValue { return !carry && shift == size } - override fun mutations() = listOf>( - BitVectorMutations.SlightDifferent.adapt(), - BitVectorMutations.DifferentWithSameSign.adapt(), - BitVectorMutations.ChangeSign.adapt() + override fun mutations() = listOf>( + BitVectorMutations.SlightDifferent, + BitVectorMutations.DifferentWithSameSign, + BitVectorMutations.ChangeSign ) override fun equals(other: Any?): Boolean { @@ -106,6 +106,7 @@ class BitVectorValue : KnownValue { override fun toString() = toString(10) + @Suppress("unused") internal fun toBinaryString(endian: Endian) = buildString { for (i in endian.range(0, size - 1)) { append(if (this@BitVectorValue[i]) '1' else '0') diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/IEEE754Value.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/IEEE754Value.kt index 128341f0d7..0e44449925 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/IEEE754Value.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/IEEE754Value.kt @@ -12,7 +12,7 @@ import kotlin.math.pow //val DOUBLE_POSITIVE_INFINITY = DefaultFloatBound.POSITIVE_INFINITY(52, 11) //val DOUBLE_NEGATIVE_INFINITY = DefaultFloatBound.NEGATIVE_INFINITY(52, 11) -class IEEE754Value : KnownValue { +class IEEE754Value : KnownValue { val isPositive: Boolean get() = !vector[0] @@ -34,8 +34,8 @@ class IEEE754Value : KnownValue { val mantissaSize: Int val exponentSize: Int - override val lastMutation: Mutation? - override val mutatedFrom: KnownValue? + override val lastMutation: Mutation? + override val mutatedFrom: IEEE754Value? private val vector: BitVectorValue @@ -56,7 +56,7 @@ class IEEE754Value : KnownValue { mutatedFrom = null } - constructor(value: IEEE754Value, mutation: Mutation? = null) { + constructor(value: IEEE754Value, mutation: Mutation? = null) { this.vector = BitVectorValue(value.vector) this.mantissaSize = value.mantissaSize this.exponentSize = value.exponentSize @@ -127,10 +127,10 @@ class IEEE754Value : KnownValue { } } - override fun mutations() = listOf>( - IEEE754Mutations.ChangeSign.adapt(), - IEEE754Mutations.Mantissa.adapt(), - IEEE754Mutations.Exponent.adapt() + override fun mutations() = listOf>( + IEEE754Mutations.ChangeSign, + IEEE754Mutations.Mantissa, + IEEE754Mutations.Exponent, ) override fun equals(other: Any?): Boolean { diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/KnownValue.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/KnownValue.kt index 6a550480ee..19c5d45aab 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/KnownValue.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/KnownValue.kt @@ -2,12 +2,14 @@ package org.utbot.fuzzing.seeds import org.utbot.fuzzing.Mutation -interface KnownValue { - val lastMutation: Mutation? +interface KnownValue> { + val lastMutation: Mutation? get() = null - val mutatedFrom: KnownValue? + val mutatedFrom: T? get() = null - fun mutations(): List> + fun mutations(): List> { + return emptyList() + } } \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/RegexValue.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/RegexValue.kt index 9091f54b9b..222c1fe326 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/RegexValue.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/RegexValue.kt @@ -7,18 +7,21 @@ import org.utbot.fuzzing.Mutation import kotlin.random.Random import kotlin.random.asJavaRandom -class RegexValue(val pattern: String, val random: Random) : KnownValue { +class RegexValue(val pattern: String, val random: Random) : StringValue( + valueProvider = { + RgxGen(pattern).apply { + setProperties(rgxGenProperties) + }.generate(random.asJavaRandom()) + } +) { - val value: String by lazy { RgxGen(pattern).apply { - setProperties(rgxGenProperties) - }.generate(random.asJavaRandom()) } - - override fun mutations() = listOf(Mutation { source, random, _ -> - require(this === source) - RegexValue(pattern, random) - }) + override fun mutations(): List> { + return super.mutations() + Mutation { source, random, _ -> + RegexValue(source.pattern, random) + } + } } private val rgxGenProperties = RgxGenProperties().apply { - setProperty(RgxGenOption.INFINITE_PATTERN_REPETITION.key, "5") + setProperty(RgxGenOption.INFINITE_PATTERN_REPETITION.key, "100") } \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/StringValue.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/StringValue.kt index 37bb8f9eed..48fc38848f 100644 --- a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/StringValue.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/StringValue.kt @@ -1,53 +1,21 @@ package org.utbot.fuzzing.seeds import org.utbot.fuzzing.Mutation -import org.utbot.fuzzing.utils.flipCoin -import kotlin.random.Random +import org.utbot.fuzzing.StringMutations -class StringValue(val value: String) : KnownValue { +open class StringValue( + val valueProvider: () -> String, + override val lastMutation: Mutation? = null +) : KnownValue { - override fun mutations(): List> { - return listOf(Mutation { source, random, configuration -> - require(source == this) - // we can miss some mutation for a purpose - val position = random.nextInt(value.length + 1) - var result: String = value - if (random.flipCoin(configuration.probStringRemoveCharacter)) { - result = tryRemoveChar(random, result, position) ?: value - } - if (result.length < configuration.maxStringLengthWhenMutated && random.flipCoin(configuration.probStringAddCharacter)) { - result = tryAddChar(random, result, position) - } - StringValue(result) - }) - } + constructor(value: String, lastMutation: Mutation? = null) : this(valueProvider = { value }, lastMutation) - private fun tryAddChar(random: Random, value: String, position: Int): String { - val charToMutate = if (value.isNotEmpty()) { - value.random(random) - } else { - // use any meaningful character from the ascii table - random.nextInt(33, 127).toChar() - } - return buildString { - append(value.substring(0, position)) - // try to change char to some that is close enough to origin char - val charTableSpread = 64 - if (random.nextBoolean()) { - append(charToMutate - random.nextInt(1, charTableSpread)) - } else { - append(charToMutate + random.nextInt(1, charTableSpread)) - } - append(value.substring(position, value.length)) - } - } + val value by lazy { valueProvider() } - private fun tryRemoveChar(random: Random, value: String, position: Int): String? { - if (position >= value.length) return null - val toRemove = random.nextInt(value.length) - return buildString { - append(value.substring(0, toRemove)) - append(value.substring(toRemove + 1, value.length)) - } + override fun mutations(): List> { + return listOf( + StringMutations.AddCharacter, + StringMutations.RemoveCharacter, + ) } } \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Primitives.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Primitives.kt index e81d644d47..7259e490c4 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Primitives.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Primitives.kt @@ -21,7 +21,7 @@ abstract class PrimitiveValueProvider( final override fun accept(type: FuzzedType) = type.classId in acceptableTypes - protected suspend fun SequenceScope>.yieldKnown( + protected suspend fun > SequenceScope>.yieldKnown( value: T, toValue: T.() -> Any ) { @@ -37,7 +37,7 @@ abstract class PrimitiveValueProvider( }) } - private fun T.valueToString(): String { + private fun > T.valueToString(): String { when (this) { is BitVectorValue -> { for (defaultBound in Signed.values()) { @@ -126,7 +126,7 @@ object IntegerValueProvider : PrimitiveValueProvider( private val configurationStubWithNoUsage = Configuration() private fun BitVectorValue.change(func: BitVectorValue.() -> Unit): BitVectorValue { - return Mutation { _, _, _ -> + return Mutation> { _, _, _ -> BitVectorValue(this).apply { func() } }.mutate(this, randomStubWithNoUsage, configurationStubWithNoUsage) as BitVectorValue } @@ -206,7 +206,7 @@ object FloatValueProvider : PrimitiveValueProvider( } } -object StringValueProvider : PrimitiveValueProvider(stringClassId) { +object StringValueProvider : PrimitiveValueProvider(stringClassId, java.lang.CharSequence::class.java.id) { override fun generate( description: FuzzedDescription, type: FuzzedType @@ -216,7 +216,7 @@ object StringValueProvider : PrimitiveValueProvider(stringClassId) { val values = constants .mapNotNull { it.value as? String } + sequenceOf("", "abc", "\n\t\r") - values.forEach { yieldKnown(StringValue(it)) { value } } + values.forEach { yieldKnown(StringValue(it), StringValue::value) } constants .filter { it.fuzzedContext.isPatterMatchingContext() } .map { it.value as String } @@ -229,7 +229,7 @@ object StringValueProvider : PrimitiveValueProvider(stringClassId) { false } }.forEach { - yieldKnown(RegexValue(it, Random(0))) { value } + yieldKnown(RegexValue(it, Random(0)), StringValue::value) } } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BoolValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BoolValueProvider.kt index 865100d58d..e3b8c8f1e5 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BoolValueProvider.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BoolValueProvider.kt @@ -23,7 +23,7 @@ object BoolValueProvider : ValueProvider SequenceScope>.yieldBool(value: T, block: T.() -> Boolean) { + private suspend fun > SequenceScope>.yieldBool(value: T, block: T.() -> Boolean) { yield(Seed.Known(value) { PythonFuzzedValue( PythonTree.fromBool(block(it)), diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/IntValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/IntValueProvider.kt index 97b634e92d..5eb26d1838 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/IntValueProvider.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/IntValueProvider.kt @@ -28,7 +28,7 @@ object IntValueProvider : ValueProvider Unit): BitVectorValue { - return Mutation { _, _, _ -> + return Mutation> { _, _, _ -> BitVectorValue(this).apply { func() } }.mutate(this, randomStubWithNoUsage, configurationStubWithNoUsage) as BitVectorValue } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/StrValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/StrValueProvider.kt index 5175cb005d..54887c65d0 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/StrValueProvider.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/StrValueProvider.kt @@ -37,7 +37,7 @@ object StrValueProvider : ValueProvider SequenceScope>.yieldStrings(value: T, block: T.() -> Any) { + private suspend fun > SequenceScope>.yieldStrings(value: T, block: T.() -> Any) { yield(Seed.Known(value) { PythonFuzzedValue( PythonTree.fromString(block(it).toString()), diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/SummaryGeneration.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/SummaryGeneration.kt index aaff31ea5b..80475457e8 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/SummaryGeneration.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/SummaryGeneration.kt @@ -7,7 +7,7 @@ import org.utbot.fuzzing.seeds.KnownValue import org.utbot.fuzzing.seeds.Signed import org.utbot.fuzzing.seeds.StringValue -fun T.valueToString(): String { +fun > T.valueToString(): String { when (this) { is BitVectorValue -> { for (defaultBound in Signed.values()) { @@ -43,7 +43,7 @@ fun T.valueToString(): String { } } -fun T.generateSummary(): String { +fun > T.generateSummary(): String { return buildString { append("%var% = ${valueToString()}") if (mutatedFrom != null) { diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/UndefValue.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/UndefValue.kt index 5b56911c0b..73d61c594b 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/UndefValue.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/UndefValue.kt @@ -3,8 +3,4 @@ package org.utbot.python.fuzzing.value import org.utbot.fuzzing.Mutation import org.utbot.fuzzing.seeds.KnownValue -class ObjectValue : KnownValue { - override fun mutations(): List> { - return emptyList() - } -} +class ObjectValue : KnownValue