Skip to content

Commit 04eefa5

Browse files
committed
feat: add configurable max number of mutations for ValuePools
1 parent b2b717f commit 04eefa5

File tree

4 files changed

+99
-11
lines changed

4 files changed

+99
-11
lines changed

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
@@ -101,6 +101,12 @@
101101
*/
102102
double p() default 0.1;
103103

104+
/**
105+
* If the mutator selects a value from this {@code ValuePool}, it will perform up to {@code
106+
* maxMutations} additional mutations on the selected value.
107+
*/
108+
int maxMutations() default 1;
109+
104110
/**
105111
* Defines the scope of the annotation. Possible values are defined in {@link
106112
* 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: 25 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,28 @@ 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+
for (int i = 0; i < prng.closedRange(0, maxMutations); i++) {
186+
value = mutator.mutate(value, prng);
182187
}
188+
return value;
183189
}
184190
return mutator.mutate(value, prng);
185191
}
186192

187193
@Override
188194
public T crossOver(T value, T otherValue, PseudoRandom prng) {
189-
return mutator.crossOver(value, otherValue, prng);
195+
if (prng.closedRange(0.0, 1.0) < poolUsageProbability) {
196+
value = prng.pickIn(userValues);
197+
// Treat the user value as a starting point for crossOver
198+
for (int i = 0; i < prng.closedRange(0, maxMutations); i++) {
199+
value = mutator.crossOver(value, prng.pickIn(userValues), prng);
200+
}
201+
return value;
202+
} else {
203+
return mutator.crossOver(value, otherValue, prng);
204+
}
190205
}
191206
}
192207
}

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
@@ -87,6 +87,17 @@ public double extractFirstProbability(AnnotatedType type) {
8787
return p;
8888
}
8989

90+
public int extractFirstMaxMutations(AnnotatedType type) {
91+
ValuePool[] valuePoolAnnotations = type.getAnnotationsByType(ValuePool.class);
92+
if (valuePoolAnnotations.length == 0) {
93+
// If we are here, it's a bug in the caller.
94+
throw new IllegalStateException("Expected to find @ValuePool, but found none.");
95+
}
96+
int maxMutations = valuePoolAnnotations[0].maxMutations();
97+
require(maxMutations >= 0, "@ValuePool maxMutations must be >= 0, but was " + maxMutations);
98+
return maxMutations;
99+
}
100+
90101
public Stream<?> extractUserValues(AnnotatedType type) {
91102
Stream<?> valuesFromSourceMethods =
92103
getValuePoolAnnotations(type).stream()

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

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,46 @@ void testExtractFirstProbability_TwoWithLastUsed() {
8585
assertThat(p).isEqualTo(0.2);
8686
}
8787

88+
@Test
89+
void testExtractFirstMaxMutations_Default() {
90+
AnnotatedType type = new TypeHolder<@ValuePool("myPool") String>() {}.annotatedType();
91+
int maxMutations = valuePools.extractFirstMaxMutations(type);
92+
assertThat(maxMutations).isEqualTo(1);
93+
}
94+
95+
@Test
96+
void testExtractFirstMaxMutations_OneUserDefined() {
97+
AnnotatedType type =
98+
new TypeHolder<
99+
@ValuePool(value = "myPool2", maxMutations = 10) String>() {}.annotatedType();
100+
int maxMutations = valuePools.extractFirstMaxMutations(type);
101+
assertThat(maxMutations).isEqualTo(10);
102+
}
103+
104+
@Test
105+
void testExtractMaxMutations_TwoWithLastUsed() {
106+
AnnotatedType type =
107+
withExtraAnnotations(
108+
new TypeHolder<
109+
@ValuePool(value = "myPool", maxMutations = 2) String>() {}.annotatedType(),
110+
new ValuePoolBuilder().value("myPool2").maxMutations(10).build());
111+
int maxMutations = valuePools.extractFirstMaxMutations(type);
112+
assertThat(maxMutations).isEqualTo(2);
113+
}
114+
115+
// assert that maxMutatiosn throws when negative
116+
@Test
117+
void testExtractFirstMaxMutations_Negative() {
118+
AnnotatedType type =
119+
new TypeHolder<
120+
@ValuePool(value = "myPool2", maxMutations = -1) String>() {}.annotatedType();
121+
try {
122+
valuePools.extractFirstMaxMutations(type);
123+
} catch (IllegalArgumentException e) {
124+
assertThat(e.getMessage()).contains("@ValuePool maxMutations must be >= 0");
125+
}
126+
}
127+
88128
@Test
89129
void testExtractRawValues_OneAnnotation() {
90130
AnnotatedType type = new TypeHolder<@ValuePool("myPool") String>() {}.annotatedType();
@@ -385,13 +425,15 @@ private static class ValuePoolBuilder {
385425
private String[] value;
386426
private String[] files;
387427
private double p;
428+
private int maxMutations;
388429
private String constraint;
389430

390431
public ValuePoolBuilder() {
391432
try {
392433
value = (String[]) getDefault("value");
393434
files = (String[]) getDefault("files");
394435
p = (double) getDefault("p");
436+
maxMutations = (int) getDefault("maxMutations");
395437
constraint = (String) getDefault("constraint");
396438
} catch (NoSuchMethodException e) {
397439
throw new RuntimeException("Could not load ValuePool defaults", e);
@@ -412,6 +454,11 @@ public ValuePoolBuilder p(double p) {
412454
return this;
413455
}
414456

457+
public ValuePoolBuilder maxMutations(int maxMutations) {
458+
this.maxMutations = maxMutations;
459+
return this;
460+
}
461+
415462
public ValuePoolBuilder constraint(String constraint) {
416463
this.constraint = constraint;
417464
return this;
@@ -426,6 +473,7 @@ public ValuePool build() {
426473
final String[] value = this.value;
427474
final String[] files = this.files;
428475
final double p = this.p;
476+
final int maxMutations = this.maxMutations;
429477
final String constraint = this.constraint;
430478

431479
return new ValuePool() {
@@ -449,6 +497,11 @@ public double p() {
449497
return p;
450498
}
451499

500+
@Override
501+
public int maxMutations() {
502+
return maxMutations;
503+
}
504+
452505
@Override
453506
public String constraint() {
454507
return constraint;
@@ -472,7 +525,8 @@ public int hashCode() {
472525
hash += Arrays.hashCode(value()) * 127;
473526
hash += Arrays.hashCode(files()) * 31 * 127;
474527
hash += Double.hashCode(p()) * 31 * 31 * 127;
475-
hash += constraint().hashCode() * 31 * 31 * 31 * 127;
528+
hash += Integer.hashCode(maxMutations()) * 31 * 31 * 31 * 127;
529+
hash += constraint().hashCode() * 31 * 31 * 31 * 31 * 127;
476530
return hash;
477531
}
478532

@@ -486,6 +540,8 @@ public String toString() {
486540
+ String.join(", ", files())
487541
+ "}, p="
488542
+ p()
543+
+ ", maxMutations="
544+
+ maxMutations()
489545
+ ", constraint="
490546
+ constraint()
491547
+ ")";

0 commit comments

Comments
 (0)