Skip to content

Commit 78f0914

Browse files
committed
feat: add configurable max number of mutations for ValuePools
1 parent 68fb0c7 commit 78f0914

4 files changed

Lines changed: 105 additions & 11 deletions

File tree

src/main/java/com/code_intelligence/jazzer/mutation/annotation/ValuePool.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@
104104
*/
105105
double p() default 0.1;
106106

107+
/**
108+
* If the mutator selects a value from this {@code ValuePool}, it will perform up to {@code
109+
* maxMutations} additional mutations on the selected value.
110+
*/
111+
int maxMutations() default 1;
112+
107113
/**
108114
* Defines the scope of the annotation. Possible values are defined in {@link
109115
* com.code_intelligence.jazzer.mutation.utils.PropertyConstraint}. By default, it's {@code

src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/ValuePoolMutatorFactory.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,17 @@ private static final class ValuePoolMutator<T> extends SerializingMutator<T> {
7474
private final SerializingMutator<T> mutator;
7575
private final List<T> userValues;
7676
private final double poolUsageProbability;
77+
private final int maxMutations;
7778

7879
ValuePoolMutator(
79-
SerializingMutator<T> mutator, List<T> userValues, double poolUsageProbability) {
80+
SerializingMutator<T> mutator,
81+
List<T> userValues,
82+
double poolUsageProbability,
83+
int maxMutations) {
8084
this.mutator = mutator;
8185
this.userValues = userValues;
8286
this.poolUsageProbability = poolUsageProbability;
87+
this.maxMutations = maxMutations;
8388
}
8489

8590
@SuppressWarnings("unchecked")
@@ -106,7 +111,8 @@ static <T> SerializingMutator<T> wrapIfValuesExist(
106111
}
107112

108113
double p = valuePoolRegistry.extractFirstProbability(type);
109-
return new ValuePoolMutator<>(mutator, userValues, p);
114+
int maxMutations = valuePoolRegistry.extractFirstMaxMutations(type);
115+
return new ValuePoolMutator<>(mutator, userValues, p, maxMutations);
110116
}
111117

112118
/**
@@ -138,8 +144,8 @@ private static <T> boolean isSerializationStable(SerializingMutator<T> mutator,
138144
@Override
139145
public String toDebugString(Predicate<Debuggable> isInCycle) {
140146
return String.format(
141-
"%s (values: %d p: %.2f)",
142-
mutator.toDebugString(isInCycle), userValues.size(), poolUsageProbability);
147+
"%s (values: %d p: %.2f, maxMutations: %d)",
148+
mutator.toDebugString(isInCycle), userValues.size(), poolUsageProbability, maxMutations);
143149
}
144150

145151
@Override
@@ -174,19 +180,30 @@ public T init(PseudoRandom prng) {
174180
@Override
175181
public T mutate(T value, PseudoRandom prng) {
176182
if (prng.closedRange(0.0, 1.0) < poolUsageProbability) {
177-
if (prng.choice()) {
178-
return prng.pickIn(userValues);
179-
} else {
180-
// treat the value from valuePool as a starting point for mutation
181-
return mutator.mutate(prng.pickIn(userValues), prng);
183+
value = prng.pickIn(userValues);
184+
// Treat the user value as a starting point for mutation
185+
int mutations = prng.closedRange(0, maxMutations);
186+
for (int i = 0; i < mutations; i++) {
187+
value = mutator.mutate(value, prng);
182188
}
189+
return value;
183190
}
184191
return mutator.mutate(value, prng);
185192
}
186193

187194
@Override
188195
public T crossOver(T value, T otherValue, PseudoRandom prng) {
189-
return mutator.crossOver(value, otherValue, prng);
196+
if (prng.closedRange(0.0, 1.0) < poolUsageProbability) {
197+
value = prng.pickIn(userValues);
198+
// Treat the user value as a starting point for crossOver
199+
int mutations = prng.closedRange(0, maxMutations);
200+
for (int i = 0; i < mutations; i++) {
201+
value = mutator.crossOver(value, prng.pickIn(userValues), prng);
202+
}
203+
return value;
204+
} else {
205+
return mutator.crossOver(value, otherValue, prng);
206+
}
190207
}
191208
}
192209
}

src/main/java/com/code_intelligence/jazzer/mutation/support/ValuePoolRegistry.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@ public double extractFirstProbability(AnnotatedType type) {
7979
return p;
8080
}
8181

82+
public int extractFirstMaxMutations(AnnotatedType type) {
83+
ValuePool[] valuePoolAnnotations = type.getAnnotationsByType(ValuePool.class);
84+
if (valuePoolAnnotations.length == 0) {
85+
// If we are here, it's a bug in the caller.
86+
throw new IllegalStateException("Expected to find @ValuePool, but found none.");
87+
}
88+
int maxMutations = valuePoolAnnotations[0].maxMutations();
89+
require(maxMutations >= 0, "@ValuePool maxMutations must be >= 0, but was " + maxMutations);
90+
return maxMutations;
91+
}
92+
8293
public Stream<?> extractUserValues(AnnotatedType type) {
8394
Stream<?> valuesFromSourceMethods =
8495
getValuePoolAnnotations(type).stream()

src/test/java/com/code_intelligence/jazzer/mutation/support/ValuePoolsTest.java

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.withExtraAnnotations;
2323
import static com.code_intelligence.jazzer.mutation.utils.PropertyConstraint.DECLARATION;
2424
import static com.google.common.truth.Truth.assertThat;
25+
import static org.junit.jupiter.api.Assertions.assertThrows;
2526

2627
import com.code_intelligence.jazzer.mutation.annotation.ValuePool;
2728
import java.io.IOException;
@@ -89,6 +90,45 @@ void testExtractFirstProbability_TwoWithLastUsed() {
8990
assertThat(p).isEqualTo(0.2);
9091
}
9192

93+
@Test
94+
void testExtractFirstMaxMutations_Default() {
95+
AnnotatedType type = new TypeHolder<@ValuePool("myPool") String>() {}.annotatedType();
96+
int maxMutations = valuePools.extractFirstMaxMutations(type);
97+
assertThat(maxMutations).isEqualTo(1);
98+
}
99+
100+
@Test
101+
void testExtractFirstMaxMutations_OneUserDefined() {
102+
AnnotatedType type =
103+
new TypeHolder<
104+
@ValuePool(value = "myPool2", maxMutations = 10) String>() {}.annotatedType();
105+
int maxMutations = valuePools.extractFirstMaxMutations(type);
106+
assertThat(maxMutations).isEqualTo(10);
107+
}
108+
109+
@Test
110+
void testExtractMaxMutations_TwoWithLastUsed() {
111+
AnnotatedType type =
112+
withExtraAnnotations(
113+
new TypeHolder<
114+
@ValuePool(value = "myPool", maxMutations = 2) String>() {}.annotatedType(),
115+
new ValuePoolBuilder().value("myPool2").maxMutations(10).build());
116+
int maxMutations = valuePools.extractFirstMaxMutations(type);
117+
assertThat(maxMutations).isEqualTo(2);
118+
}
119+
120+
@Test
121+
void testExtractFirstMaxMutations_Negative() {
122+
AnnotatedType type =
123+
new TypeHolder<
124+
@ValuePool(value = "myPool2", maxMutations = -1) String>() {}.annotatedType();
125+
assertThat(
126+
assertThrows(
127+
IllegalArgumentException.class, () -> valuePools.extractFirstMaxMutations(type)))
128+
.hasMessageThat()
129+
.contains("@ValuePool maxMutations must be >= 0");
130+
}
131+
92132
@Test
93133
void testExtractRawValues_OneAnnotation() {
94134
AnnotatedType type = new TypeHolder<@ValuePool("myPool") String>() {}.annotatedType();
@@ -353,13 +393,15 @@ private static class ValuePoolBuilder {
353393
private String[] value;
354394
private String[] files;
355395
private double p;
396+
private int maxMutations;
356397
private String constraint;
357398

358399
public ValuePoolBuilder() {
359400
try {
360401
value = (String[]) getDefault("value");
361402
files = (String[]) getDefault("files");
362403
p = (double) getDefault("p");
404+
maxMutations = (int) getDefault("maxMutations");
363405
constraint = (String) getDefault("constraint");
364406
} catch (NoSuchMethodException e) {
365407
throw new RuntimeException("Could not load ValuePool defaults", e);
@@ -385,6 +427,11 @@ public ValuePoolBuilder p(double p) {
385427
return this;
386428
}
387429

430+
public ValuePoolBuilder maxMutations(int maxMutations) {
431+
this.maxMutations = maxMutations;
432+
return this;
433+
}
434+
388435
public ValuePoolBuilder constraint(String constraint) {
389436
this.constraint = constraint;
390437
return this;
@@ -393,6 +440,7 @@ public ValuePoolBuilder constraint(String constraint) {
393440
public ValuePool build() {
394441
final String[] value = this.value;
395442
final double p = this.p;
443+
final int maxMutations = this.maxMutations;
396444
final String constraint = this.constraint;
397445

398446
return new ValuePool() {
@@ -416,6 +464,11 @@ public double p() {
416464
return p;
417465
}
418466

467+
@Override
468+
public int maxMutations() {
469+
return maxMutations;
470+
}
471+
419472
@Override
420473
public String constraint() {
421474
return constraint;
@@ -430,13 +483,18 @@ public boolean equals(Object o) {
430483
return Arrays.equals(this.value(), other.value())
431484
&& Arrays.equals(this.files(), other.files())
432485
&& this.p() == other.p()
486+
&& this.maxMutations() == other.maxMutations()
433487
&& this.constraint().equals(other.constraint());
434488
}
435489

436490
@Override
437491
public int hashCode() {
438492
return Objects.hash(
439-
Arrays.hashCode(value()), Arrays.hashCode(files()), p(), constraint());
493+
Arrays.hashCode(value()),
494+
Arrays.hashCode(files()),
495+
p(),
496+
maxMutations(),
497+
constraint());
440498
}
441499

442500
@Override
@@ -449,6 +507,8 @@ public String toString() {
449507
+ String.join(", ", files())
450508
+ "}, p="
451509
+ p()
510+
+ ", maxMutations="
511+
+ maxMutations()
452512
+ ", constraint="
453513
+ constraint()
454514
+ ")";

0 commit comments

Comments
 (0)