Skip to content

Commit 5069fb3

Browse files
committed
feat: Integral mutators now use @DictionaryProvider
1 parent fde2a49 commit 5069fb3

File tree

1 file changed

+71
-10
lines changed

1 file changed

+71
-10
lines changed

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

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@
2424
import com.code_intelligence.jazzer.mutation.api.MutatorFactory;
2525
import com.code_intelligence.jazzer.mutation.api.PseudoRandom;
2626
import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
27+
import com.code_intelligence.jazzer.mutation.combinator.SamplingUtils;
28+
import com.code_intelligence.jazzer.mutation.combinator.SamplingUtils.WeightedValue;
2729
import com.code_intelligence.jazzer.mutation.mutator.libfuzzer.LibFuzzerMutate;
2830
import com.code_intelligence.jazzer.mutation.runtime.MutatorRuntime;
31+
import com.code_intelligence.jazzer.mutation.support.DictionaryProviderSupport;
2932
import com.code_intelligence.jazzer.mutation.support.RangeSupport;
3033
import com.code_intelligence.jazzer.mutation.support.RangeSupport.LongRange;
3134
import com.google.errorprone.annotations.ForOverride;
@@ -34,7 +37,10 @@
3437
import java.io.IOException;
3538
import java.lang.reflect.AnnotatedType;
3639
import java.lang.reflect.ParameterizedType;
40+
import java.util.ArrayList;
41+
import java.util.List;
3742
import java.util.Optional;
43+
import java.util.function.Function;
3844
import java.util.function.Predicate;
3945
import java.util.stream.LongStream;
4046

@@ -49,7 +55,7 @@ public Optional<SerializingMutator<?>> tryCreate(
4955

5056
if (clazz == byte.class || clazz == Byte.class) {
5157
return Optional.of(
52-
new AbstractIntegralMutator<Byte>(type, Byte.MIN_VALUE, Byte.MAX_VALUE) {
58+
new AbstractIntegralMutator<Byte>(runtime, type, Byte.MIN_VALUE, Byte.MAX_VALUE) {
5359
@Override
5460
protected long mutateWithLibFuzzer(long value) {
5561
return LibFuzzerMutate.mutateDefault((byte) value, this, 0);
@@ -82,7 +88,7 @@ public void write(Byte value, DataOutputStream out) throws IOException {
8288
});
8389
} else if (clazz == short.class || clazz == Short.class) {
8490
return Optional.of(
85-
new AbstractIntegralMutator<Short>(type, Short.MIN_VALUE, Short.MAX_VALUE) {
91+
new AbstractIntegralMutator<Short>(runtime, type, Short.MIN_VALUE, Short.MAX_VALUE) {
8692
@Override
8793
protected long mutateWithLibFuzzer(long value) {
8894
return LibFuzzerMutate.mutateDefault((short) value, this, 0);
@@ -115,7 +121,8 @@ public void write(Short value, DataOutputStream out) throws IOException {
115121
});
116122
} else if (clazz == int.class || clazz == Integer.class) {
117123
return Optional.of(
118-
new AbstractIntegralMutator<Integer>(type, Integer.MIN_VALUE, Integer.MAX_VALUE) {
124+
new AbstractIntegralMutator<Integer>(
125+
runtime, type, Integer.MIN_VALUE, Integer.MAX_VALUE) {
119126
@Override
120127
protected long mutateWithLibFuzzer(long value) {
121128
return LibFuzzerMutate.mutateDefault((int) value, this, 0);
@@ -148,7 +155,7 @@ public void write(Integer value, DataOutputStream out) throws IOException {
148155
});
149156
} else if (clazz == long.class || clazz == Long.class) {
150157
return Optional.of(
151-
new AbstractIntegralMutator<Long>(type, Long.MIN_VALUE, Long.MAX_VALUE) {
158+
new AbstractIntegralMutator<Long>(runtime, type, Long.MIN_VALUE, Long.MAX_VALUE) {
152159
@Override
153160
protected long mutateWithLibFuzzer(long value) {
154161
return LibFuzzerMutate.mutateDefault(value, this, 0);
@@ -197,9 +204,14 @@ abstract static class AbstractIntegralMutator<T extends Number> extends Serializ
197204
private final int largestMutableBitNegative;
198205
private final int largestMutableBitPositive;
199206
private final long[] specialValues;
207+
private final long[] dictionaryValues;
208+
private final Function<PseudoRandom, MutationFunction> mutationFunctionSampler;
200209

201210
AbstractIntegralMutator(
202-
AnnotatedType type, long defaultMinValueForType, long defaultMaxValueForType) {
211+
MutatorRuntime runtime,
212+
AnnotatedType type,
213+
long defaultMinValueForType,
214+
long defaultMaxValueForType) {
203215
LongRange resolved =
204216
RangeSupport.resolveIntegralRange(type, defaultMinValueForType, defaultMaxValueForType);
205217
long minValue = resolved.min;
@@ -232,6 +244,50 @@ abstract static class AbstractIntegralMutator<T extends Number> extends Serializ
232244
largestMutableBitPositive = bitWidth(maxValue);
233245
}
234246
this.specialValues = collectSpecialValues(minValue, maxValue);
247+
248+
this.dictionaryValues =
249+
DictionaryProviderSupport.extractRawValues(runtime, type)
250+
.map(
251+
stream ->
252+
stream
253+
.filter(v -> v instanceof Number)
254+
.map(v -> ((Number) v).longValue())
255+
.filter(v -> v >= minValue)
256+
.filter(v -> v <= maxValue)
257+
.sorted()
258+
.mapToLong(Long::longValue)
259+
.toArray())
260+
.orElse(null);
261+
List<WeightedValue<MutationFunction>> f = new ArrayList<>();
262+
f.add(new WeightedValue<>(1.0, MutationFunction.BIT_FLIP));
263+
f.add(new WeightedValue<>(1.0, MutationFunction.RANDOM_WALK));
264+
f.add(new WeightedValue<>(1.0, MutationFunction.RANDOM_VALUE));
265+
f.add(new WeightedValue<>(1.0, MutationFunction.LIBFUZZER));
266+
if (dictionaryValues != null && dictionaryValues.length > 0) {
267+
// Since weights here are relative, we need to adjust the weight of user dictionary mutator
268+
// so that it is taken proportionate the inverse probability specified in the annotation.
269+
// Basically, we need to scale up the weight for pInv:
270+
// 1/p --- x?
271+
// 1- 1/p --- totalFuncWeights
272+
// x = (1/p * totalFuncWeights) / (1 - 1/p)
273+
// = totalFuncWeights / (p - 1)
274+
double totalFuncWeights = 0.0;
275+
for (WeightedValue<MutationFunction> wf : f) {
276+
totalFuncWeights += wf.weight;
277+
}
278+
int invProbability = DictionaryProviderSupport.extractFirstInvProbability(type);
279+
double perValueWeight = totalFuncWeights / (invProbability - 1);
280+
f.add(new WeightedValue<>(perValueWeight, MutationFunction.DICTIONARY_VALUE));
281+
}
282+
this.mutationFunctionSampler = SamplingUtils.weightedSampler(f);
283+
}
284+
285+
private enum MutationFunction {
286+
BIT_FLIP,
287+
DICTIONARY_VALUE,
288+
LIBFUZZER,
289+
RANDOM_VALUE,
290+
RANDOM_WALK,
235291
}
236292

237293
private static long[] collectSpecialValues(long minValue, long maxValue) {
@@ -263,20 +319,25 @@ protected final long mutateImpl(long value, PseudoRandom prng) {
263319
final long previousValue = value;
264320
// Mutate in a loop to verify that we really mutated.
265321
do {
266-
switch (prng.indexIn(4)) {
267-
case 0:
322+
switch (mutationFunctionSampler.apply(prng)) {
323+
case BIT_FLIP:
268324
value = bitFlip(value, prng);
269325
break;
270-
case 1:
326+
case RANDOM_WALK:
271327
value = randomWalk(value, prng);
272328
break;
273-
case 2:
329+
case RANDOM_VALUE:
274330
value = prng.closedRange(minValue, maxValue);
275331
break;
276-
case 3:
332+
case LIBFUZZER:
277333
// TODO: Replace this with a structure-aware dictionary/TORC search similar to fuzztest.
278334
value = forceInRange(mutateWithLibFuzzer(value));
279335
break;
336+
case DICTIONARY_VALUE:
337+
value = dictionaryValues[prng.indexIn(dictionaryValues.length)];
338+
break;
339+
default:
340+
throw new AssertionError("Invalid mutation function.");
280341
}
281342
} while (value == previousValue);
282343
return value;

0 commit comments

Comments
 (0)