Skip to content

Commit 8aedbbf

Browse files
authored
Change regex generator in fuzzing (#2318)
* Change regex generator library * Introduce an option to choose a ratio between generation and mutation
1 parent 630142e commit 8aedbbf

File tree

10 files changed

+64
-47
lines changed

10 files changed

+64
-47
lines changed

utbot-fuzzing/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ val rgxgenVersion: String by rootProject
33

44
dependencies {
55
implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion)
6-
implementation(group = "com.github.curious-odd-man", name = "rgxgen", version = rgxgenVersion)
6+
implementation(group = "org.cornutum.regexp", name = "regexp-gen", version = "2.0.1")
77
}

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -324,18 +324,21 @@ private suspend fun <T, R, D : Description<T>, F : Feedback<T, R>> Fuzzing<T, R,
324324

325325
while (!fuzzing.isCancelled(description, statistic)) {
326326
beforeIteration(description, statistic)
327-
var values = if (statistic.isNotEmpty() && random.flipCoin(configuration.probSeedRetrievingInsteadGenerating)) {
328-
statistic.getRandomSeed(random, configuration)
327+
val values = if (statistic.isNotEmpty() && random.flipCoin(configuration.probSeedRetrievingInsteadGenerating)) {
328+
statistic.getRandomSeed(random, configuration).let {
329+
mutationFactory.mutate(it, random, configuration)
330+
}
329331
} else {
330332
val actualParameters = description.parameters
331333
// fuzz one value, seems to be bad, when have only a few and simple values
332-
fuzzOne(actualParameters)
334+
fuzzOne(actualParameters).let {
335+
if (random.flipCoin(configuration.probMutationRate)) {
336+
mutationFactory.mutate(it, random, configuration)
337+
} else {
338+
it
339+
}
340+
}
333341
}
334-
values = mutationFactory.mutate(
335-
values,
336-
random,
337-
configuration
338-
)
339342
afterIteration(description, statistic)
340343

341344
yield()

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ data class Configuration(
1212
*/
1313
var probSeedRetrievingInsteadGenerating: Int = 70,
1414

15+
/**
16+
* Choose between generation and mutation.
17+
*/
18+
var probMutationRate: Int = 99,
19+
1520
/**
1621
* Fuzzer creates a tree of object for generating values. At some point this recursion should be stopped.
1722
*

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
package org.utbot.fuzzing
44

5-
import org.utbot.fuzzing.seeds.BitVectorValue
6-
import org.utbot.fuzzing.seeds.IEEE754Value
7-
import org.utbot.fuzzing.seeds.KnownValue
8-
import org.utbot.fuzzing.seeds.StringValue
5+
import org.utbot.fuzzing.seeds.*
96
import org.utbot.fuzzing.utils.chooseOne
107
import org.utbot.fuzzing.utils.flipCoin
118
import kotlin.random.Random
@@ -190,7 +187,7 @@ sealed interface StringMutations : Mutation<StringValue> {
190187
}
191188
append(value.substring(position, value.length))
192189
}
193-
return StringValue(newString, lastMutation = this)
190+
return StringValue(newString, lastMutation = this, mutatedFrom = source)
194191
}
195192
}
196193

@@ -207,6 +204,15 @@ sealed interface StringMutations : Mutation<StringValue> {
207204
return StringValue(newString, this)
208205
}
209206
}
207+
208+
object ShuffleCharacters : StringMutations {
209+
override fun mutate(source: StringValue, random: Random, configuration: Configuration): StringValue {
210+
return StringValue(
211+
value = String(source.value.toCharArray().apply { shuffle(random) }),
212+
lastMutation = this
213+
)
214+
}
215+
}
210216
}
211217

212218
fun interface NodeMutation<TYPE, RESULT> : Mutation<Node<TYPE, RESULT>>
Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package org.utbot.fuzzing.seeds
22

3-
import com.github.curiousoddman.rgxgen.RgxGen
4-
import com.github.curiousoddman.rgxgen.config.RgxGenOption
5-
import com.github.curiousoddman.rgxgen.config.RgxGenProperties
3+
import org.cornutum.regexpgen.js.Provider
4+
import org.cornutum.regexpgen.random.RandomBoundsGen
65
import org.utbot.fuzzing.Mutation
76
import kotlin.random.Random
87
import kotlin.random.asJavaRandom
98

10-
class RegexValue(val pattern: String, val random: Random) : StringValue(
9+
class RegexValue(
10+
val pattern: String,
11+
val random: Random,
12+
val maxLength: Int = listOf(16, 256, 2048).random(random)
13+
) : StringValue(
1114
valueProvider = {
12-
RgxGen(pattern).apply {
13-
setProperties(rgxGenProperties)
14-
}.generate(random.asJavaRandom())
15+
val matchingExact = Provider.forEcmaScript().matchingExact(pattern)
16+
matchingExact.generate(RandomBoundsGen(random.asJavaRandom()), 1, maxLength)
1517
}
1618
) {
1719

@@ -22,6 +24,12 @@ class RegexValue(val pattern: String, val random: Random) : StringValue(
2224
}
2325
}
2426

25-
private val rgxGenProperties = RgxGenProperties().apply {
26-
setProperty(RgxGenOption.INFINITE_PATTERN_REPETITION.key, "100")
27+
fun String.isSupportedPattern(): Boolean {
28+
if (isEmpty()) return false
29+
return try {
30+
Provider.forEcmaScript().matchingExact(this)
31+
true
32+
} catch (_: Throwable) {
33+
false
34+
}
2735
}

utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/StringValue.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,23 @@ import org.utbot.fuzzing.StringMutations
55

66
open class StringValue(
77
val valueProvider: () -> String,
8-
override val lastMutation: Mutation<out StringValue>? = null
8+
override val lastMutation: Mutation<out StringValue>? = null,
9+
override val mutatedFrom: StringValue? = null,
910
) : KnownValue<StringValue> {
1011

11-
constructor(value: String, lastMutation: Mutation<out StringValue>? = null) : this(valueProvider = { value }, lastMutation)
12+
constructor(
13+
value: String,
14+
lastMutation: Mutation<out StringValue>? = null,
15+
mutatedFrom: StringValue? = null
16+
) : this(valueProvider = { value }, lastMutation, mutatedFrom)
1217

1318
val value by lazy { valueProvider() }
1419

1520
override fun mutations(): List<Mutation<out StringValue>> {
1621
return listOf(
1722
StringMutations.AddCharacter,
1823
StringMutations.RemoveCharacter,
24+
StringMutations.ShuffleCharacters,
1925
)
2026
}
2127
}

utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/RandomExtensions.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ fun Random.chooseOne(frequencies: DoubleArray): Int {
2727
* If a random value is less than [probability] returns true.
2828
*/
2929
fun Random.flipCoin(probability: Int): Boolean {
30+
if (probability == 0) return false
31+
if (probability == 100) return true
3032
check(probability in 0 .. 100) { "probability must in range [0, 100] but $probability is provided" }
3133
return nextInt(1, 101) <= probability
3234
}

utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/FuzzerSmokeTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import kotlin.reflect.KClass
1313
class FuzzerSmokeTest {
1414

1515
@Test
16-
fun `fuzzing doesn't run with empty parameters`() {
16+
fun `fuzzing runs with empty parameters`() {
1717
runBlocking {
1818
var count = 0
1919
runFuzzing<Unit, Unit, Description<Unit>, BaseFeedback<Unit, Unit, Unit>>(
@@ -23,7 +23,7 @@ class FuzzerSmokeTest {
2323
count += 1
2424
BaseFeedback(Unit, Control.STOP)
2525
}
26-
Assertions.assertEquals(0, count)
26+
Assertions.assertEquals(1, count)
2727
}
2828
}
2929

utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Primitives.kt

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ abstract class PrimitiveValueProvider(
6565
else -> toString()
6666
}
6767
}
68-
is StringValue -> {
69-
return "'${value.substringToLength(10, "...")}'"
70-
}
7168
is RegexValue -> {
7269
return "'${value.substringToLength(10, "...")}' from $pattern"
7370
}
71+
is StringValue -> {
72+
return "'${value.substringToLength(10, "...")}'"
73+
}
7474
else -> return toString()
7575
}
7676
}
@@ -221,14 +221,8 @@ object StringValueProvider : PrimitiveValueProvider(stringClassId, java.lang.Cha
221221
.filter { it.fuzzedContext.isPatterMatchingContext() }
222222
.map { it.value as String }
223223
.distinct()
224-
.filter { it.isNotBlank() }
225-
.filter {
226-
try {
227-
Pattern.compile(it); true
228-
} catch (_: PatternSyntaxException) {
229-
false
230-
}
231-
}.forEach {
224+
.filter(String::isSupportedPattern)
225+
.forEach {
232226
yieldKnown(RegexValue(it, Random(0)), StringValue::value)
233227
}
234228
}

utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/StringUtils.kt

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.utbot.python.fuzzing.provider.utils
22

3-
import java.util.regex.Pattern
4-
import java.util.regex.PatternSyntaxException
3+
import org.utbot.fuzzing.seeds.isSupportedPattern
54

65
fun String.transformQuotationMarks(): String {
76

@@ -39,12 +38,6 @@ fun String.isRawString(): Boolean {
3938
fun String.isPattern(): Boolean {
4039
return if (this.isRawString()) {
4140
val stringContent = this.transformRawString()
42-
if (stringContent.isNotBlank()) {
43-
try {
44-
Pattern.compile(stringContent); true
45-
} catch (_: PatternSyntaxException) {
46-
false
47-
}
48-
} else false
41+
return stringContent.isSupportedPattern()
4942
} else false
5043
}

0 commit comments

Comments
 (0)