Skip to content

Mutate values to improve code coverage #213 #713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 24, 2022

Conversation

Markoutte
Copy link
Collaborator

Description

This commit adds a mutation mechanism to the fuzzing for improving code coverage by applying random mutations for numbers and strings.

Fixes #213

Type of Change

  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

How Has This Been Tested?

Manual Scenario

Turn on only fuzzing mode. These examples can find (usually do for default utbot settings):

  1. Should find the string that starts with prefix "bad!"
public static void test(String s) {
    if (s.charAt(0) == "b".charAt(0)) {
        if (s.charAt(1) == "a".charAt(0)) {
            if (s.charAt(2) == "d".charAt(0)) {
                if (s.charAt(3) == "!".charAt(0)) {
                    throw new IllegalArgumentException();
                }
            }
        }
    }
}
  1. Should find a string that has "!" in the 5-th position, but can totally change the other part.
public static void testStrRem(String str) {
    if (!"world???".equals(str) && str.charAt(5) == '!' && str.length() == 6) {
        throw new RuntimeException();
    }
}
  1. Should find the value that will return 2 for the method:
public int floatToInt(float x) {
    if (x < 0) {
        if ((int) x < 0) {
            return 1;
        }
        return 2; // smth small to int zero
    }
    return 3;
}

If results are failed try to increase test generation time.

Checklist:

  • The change followed the style guidelines of the UTBot project
  • Self-review of the code is passed
  • The change contains enough commentaries, particularly in hard-to-understand areas
  • New documentation is provided or existed one is altered
  • No new warnings
  • New tests have been added
  • All tests pass locally with my changes

@Markoutte Markoutte added the comp-fuzzing Issue is related to the fuzzing label Aug 11, 2022
@@ -436,7 +439,34 @@ class UtBotSymbolicEngine(
limitValuesCreatedByFieldAccessors = 500
})
}
fuzzedValues.forEach { values ->

val frequencyRandom = FrequencyRandom(Random(221))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be a configurable constant?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't because its a random seed for a Random. No difference to fuzzing if value is changed.

fuzzedValues.forEach { values ->

val frequencyRandom = FrequencyRandom(Random(221))
val factor = maxOf(0, UtSettings.fuzzingRandomMutationsFactor)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'd prefer to see coerceAtLeast function here since it shows the semantics better

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me it hides the value when variable name is long:

var onlyPositiveValue = theRandomValuedRetrievedFromFile.coearceAtLeast(0)

and

var onlyPositiveValue = maxOf(0, theRandomValuedRetrievedFromFile)

But I don't have any preferable way here.

val fvi = fuzzedValues.iterator()
while (fvi.hasNext()) {
yield(fvi.next())
// if we stuck switch to "next + several mutations" mode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, specify what stuck means and how you detect it. Is it connected with (attempts + 1) % (factor / 100) == 0 below? It's not an obvious condition for me as well

while (fvi.hasNext()) {
yield(fvi.next())
// if we stuck switch to "next + several mutations" mode
if ((attempts + 1) % (factor / 100) == 0 && coveredInstructionValues.isNotEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

factor / 100 can be zero, can`t it?

return@flow
}
// Update the seeded values sometimes
// This is necessary because some values cannot do a good values in mutation in any case
if (frequencyRandom.random.nextInt(1, 101) <= 50) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't is just nextBoolean()?

return buildString {
append(value.substring(0, position))
if (random.nextBoolean()) {
append(charToMutate - random.nextInt(1, 128))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What 128 means here?

}

private fun tryRemoveChar(random: Random, value: String, position: Int): String? {
if (value.isEmpty() || position >= value.length) return null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be just position >= value.length

@@ -148,3 +149,5 @@ fun objectModelProviders(idGenerator: IdentityPreservingIdGenerator<Int>): Model
PrimitiveWrapperModelProvider,
)
}

fun defaultMutators(): List<ModelMutator> = listOf(StringRandomMutator(50), NumberRandomMutator(50))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constants without names

parameters: List<FuzzedValue>,
random: Random,
) : List<FuzzedValue> {
return parameters.mapIndexed { index, fuzzedValue ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here and in other functions expression body might be used

value: FuzzedValue,
random: Random
) : FuzzedValue? {
return if (random.nextInt(1, 101) < probability) value else null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

random.nextInt(1, 101) < probability used in many places. May you create a function for it?

@Markoutte Markoutte force-pushed the pelevin/213_Mutate_values_to_improve_code_coverage branch from 48d6e4b to a2aa85c Compare August 22, 2022 10:50
@Markoutte Markoutte requested a review from CaelmBleidd August 22, 2022 13:21
Copy link
Member

@CaelmBleidd CaelmBleidd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall lgtm

* Wraps sequence of values, iterates through them and mutates.
*
* Mutation when possible is generated after every value of source sequence and then supplies values until it needed.
* [statistics] should is not updated by this method, but can be changed by caller.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not be updated?

yield(fvi.next())
// Fuzzing can generate values that doesn't recover new paths.
// So, fuzzing tries to mutate values on each loop
// if there are too much attempts to find new paths without mutations.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that don't recover and too many

newValues[index] = value
}
}
return newValues.takeIf { it != values }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many values do we have here? Wouldn't it be too expensive to check the equality of the two lists? Maybe we can store some flag that a mutation has happened?

Comment on lines 23 to 28
.mapIndexed { index, fuzzedValue ->
mutate(description, index, fuzzedValue, random)?.let { mutated ->
FuzzedParameter(index, mutated)
}
}
.filterNotNull()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapIndexedNotNull

/**
* Stores information that can be useful for fuzzing such as coverage, run count, etc.
*/
interface FuzzerStatistics<K> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misspell in the file name

* @return the index of the chosen item.
*/
fun Random.chooseOne(frequencies: DoubleArray): Int {
val total = frequencies.sum()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps, is frequencies could be a big array, we can create a custom collection for such frequencies that knowns information about its elements, such as total sum, statistics and other


private fun tryAddChar(random: Random, value: String, position: Int): String {
val charToMutate = if (value.isNotEmpty()) {
value[random.nextInt(value.length)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value.random(random)?

@Markoutte Markoutte merged commit 1c44797 into main Aug 24, 2022
@Markoutte Markoutte deleted the pelevin/213_Mutate_values_to_improve_code_coverage branch August 24, 2022 06:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
comp-fuzzing Issue is related to the fuzzing
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Mutate values to improve code coverage
2 participants