Skip to content

Commit 1cfba07

Browse files
committed
feat: add char and Character mutator
1 parent f504ad7 commit 1cfba07

File tree

6 files changed

+98
-3
lines changed

6 files changed

+98
-3
lines changed

docs/mutation-framework.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Currently supported types are:
5656
| Mutator | Type(s) | Notes |
5757
|--------------------------------|--------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
5858
| Boolean | `boolean`, `Boolean` | |
59-
| Integral | `byte`, `Byte`, `short`, `Short`, `int`, `Integer`, `long`, `Long` | |
59+
| Integral | `byte`, `Byte`, `char`, `Character`, `short`, `Short`, `int`, `Integer`, `long`, `Long` | |
6060
| Floating point | `float`, `Float`, `double`, `Double` | |
6161
| String | `java.lang.String` | |
6262
| Enum | `java.lang.Enum` | |
@@ -90,7 +90,7 @@ package.
9090
| Annotation | Applies To | Notes |
9191
|-------------------|----------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------|
9292
| `@Ascii` | `java.lang.String` | `String` should only contain ASCII characters |
93-
| `@InRange` | `byte`, `Byte`, `short`, `Short`, `int`, `Integer`, `long`, `Long` | Specifies `min` and `max` values of generated integrals |
93+
| `@InRange` | `byte`, `Byte`, `char`, `Character`, `short`, `Short`, `int`, `Integer`, `long`, `Long` | Specifies `min` and `max` values of generated integrals |
9494
| `@FloatInRange` | `float`, `Float` | Specifies `min` and `max` values of generated floats |
9595
| `@DoubleInRange` | `double`, `Double` | Specifies `min` and `max` values of generated doubles |
9696
| `@Positive` | `byte`, `Byte`, `short`, `Short`, `int`, `Integer`, `long`, `Long`, `float`, `Float`, `double`, `Double` | Specifies that only positive values are generated |

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
Byte.class,
3939
short.class,
4040
Short.class,
41+
char.class,
42+
Character.class,
4143
int.class,
4244
Integer.class,
4345
long.class,

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,39 @@ public void write(Short value, DataOutputStream out) throws IOException {
112112
out.writeShort(value);
113113
}
114114
});
115+
} else if (clazz == char.class || clazz == Character.class) {
116+
return Optional.of(
117+
new AbstractIntegralMutator<Character>(type, Character.MIN_VALUE, Character.MAX_VALUE) {
118+
@Override
119+
protected long mutateWithLibFuzzer(long value) {
120+
return LibFuzzerMutate.mutateDefault((char) value, this, 0);
121+
}
122+
123+
@Override
124+
public Character init(PseudoRandom prng) {
125+
return (char) initImpl(prng);
126+
}
127+
128+
@Override
129+
public Character mutate(Character value, PseudoRandom prng) {
130+
return (char) mutateImpl(value, prng);
131+
}
132+
133+
@Override
134+
public Character crossOver(Character value, Character otherValue, PseudoRandom prng) {
135+
return (char) crossOverImpl(value, otherValue, prng);
136+
}
137+
138+
@Override
139+
public Character read(DataInputStream in) throws IOException {
140+
return (char) forceInRange(in.readChar());
141+
}
142+
143+
@Override
144+
public void write(Character value, DataOutputStream out) throws IOException {
145+
out.writeChar(value);
146+
}
147+
});
115148
} else if (clazz == int.class || clazz == Integer.class) {
116149
return Optional.of(
117150
new AbstractIntegralMutator<Integer>(type, Integer.MIN_VALUE, Integer.MAX_VALUE) {
@@ -189,7 +222,7 @@ public void write(Long value, DataOutputStream out) throws IOException {
189222
// Copyright 2022 Google LLC
190223
//
191224
// Visible for testing.
192-
abstract static class AbstractIntegralMutator<T extends Number> extends SerializingMutator<T> {
225+
abstract static class AbstractIntegralMutator<T> extends SerializingMutator<T> {
193226
private static final long RANDOM_WALK_RANGE = 5;
194227
private final long minValue;
195228
private final long maxValue;

src/test/java/com/code_intelligence/jazzer/mutation/mutator/StressTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,22 @@ void singleParam(short parameter) {}
775775
// only passes with ~90% of the optimal parameters.
776776
expectedNumberOfDistinctElements(
777777
1 << Short.SIZE, NUM_INITS * NUM_MUTATE_PER_INIT * 9 / 10)),
778+
arguments(
779+
new ParameterHolder() {
780+
void singleParam(char parameter) {}
781+
}.annotatedType(),
782+
"Character",
783+
true,
784+
// init is heavily biased towards special values and only returns a uniformly random
785+
// value in 1 out of 5 calls.
786+
all(
787+
expectedNumberOfDistinctElements(1 << Character.SIZE, boundHits(NUM_INITS, 0.2)),
788+
contains((char) 1, Character.MIN_VALUE, Character.MAX_VALUE)),
789+
// The integral type mutator does not always return uniformly random values and the
790+
// random walk it uses is more likely to produce non-distinct elements, hence the test
791+
// only passes with ~90% of the optimal parameters.
792+
expectedNumberOfDistinctElements(
793+
1 << Character.SIZE, NUM_INITS * NUM_MUTATE_PER_INIT * 9 / 10)),
778794
arguments(
779795
new ParameterHolder() {
780796
void singleParam(int parameter) {}

tests/BUILD.bazel

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ java_fuzz_target_test(
2020
verify_crash_input = False,
2121
)
2222

23+
java_fuzz_target_test(
24+
name = "CharFuzzer",
25+
srcs = [
26+
"src/test/java/com/example/CharFuzzer.java",
27+
],
28+
allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"],
29+
target_class = "com.example.CharFuzzer",
30+
verify_crash_reproducer = False,
31+
)
32+
2333
java_fuzz_target_test(
2434
name = "JpegImageParserAutofuzz",
2535
allowed_findings = ["java.lang.NegativeArraySizeException"],
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2024 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example;
18+
19+
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow;
20+
import com.code_intelligence.jazzer.mutation.annotation.InRange;
21+
22+
public class CharFuzzer {
23+
private static final char min = '中' - 10;
24+
private static final char max = '中' + 10;
25+
26+
public static void fuzzerTestOneInput(@InRange(min = min, max = max) char data) {
27+
if (data < min || data > max) {
28+
throw new RuntimeException("Char out of range: " + (int) data);
29+
}
30+
if (data == '中') {
31+
throw new FuzzerSecurityIssueLow("Found the 'secret' char!");
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)