Skip to content

Commit 5c73f71

Browse files
simonreschflorianGla
authored andcommitted
feat: sometimes interpret char[] mutations as single bytes
When mutating char[] randomly interpret the bytes from libFuzzer as individual (single byte) chars. This helps to make use of libFuzzers table of recent compare entries (encoded as CESU8) if the char[] is used as a String inside the fuzz test.
1 parent 761cc11 commit 5c73f71

File tree

3 files changed

+69
-4
lines changed

3 files changed

+69
-4
lines changed

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

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.lang.reflect.AnnotatedType;
4444
import java.nio.ByteBuffer;
4545
import java.util.Optional;
46+
import java.util.function.BiFunction;
4647
import java.util.function.Function;
4748
import java.util.function.Predicate;
4849

@@ -81,6 +82,7 @@ public static final class PrimitiveArrayMutator<T> extends SerializingMutator<T>
8182
private final SerializingMutator<byte[]> innerMutator;
8283
private final Function<byte[], T> toPrimitive;
8384
private final Function<T, byte[]> toBytes;
85+
private final BiFunction<byte[], PseudoRandom, T> toPrimitiveAfterMutate;
8486

8587
@SuppressWarnings("unchecked")
8688
public PrimitiveArrayMutator(AnnotatedType type) {
@@ -92,6 +94,8 @@ public PrimitiveArrayMutator(AnnotatedType type) {
9294
innerMutator =
9395
(SerializingMutator<byte[]>) LibFuzzerMutatorFactory.tryCreate(innerByteArray).get();
9496
toPrimitive = (Function<byte[], T>) makeBytesToPrimitiveArrayConverter(elementType);
97+
toPrimitiveAfterMutate =
98+
(BiFunction<byte[], PseudoRandom, T>) makeBytesToPrimitiveArrayAfterMutate(elementType);
9599
toBytes = (Function<T, byte[]>) makePrimitiveArrayToBytesConverter(elementType);
96100
}
97101

@@ -128,14 +132,13 @@ public T init(PseudoRandom prng) {
128132

129133
@Override
130134
public T mutate(T value, PseudoRandom prng) {
131-
return (T) toPrimitive.apply(innerMutator.mutate(toBytes.apply(value), prng));
135+
return toPrimitiveAfterMutate.apply(innerMutator.mutate(toBytes.apply(value), prng), prng);
132136
}
133137

134138
@Override
135139
public T crossOver(T value, T otherValue, PseudoRandom prng) {
136-
return (T)
137-
toPrimitive.apply(
138-
innerMutator.crossOver(toBytes.apply(value), toBytes.apply(otherValue), prng));
140+
return toPrimitive.apply(
141+
innerMutator.crossOver(toBytes.apply(value), toBytes.apply(otherValue), prng));
139142
}
140143

141144
private void extractRange(AnnotatedType type) {
@@ -250,6 +253,29 @@ private static AnnotatedType convertWithLength(AnnotatedType type, AnnotatedType
250253
}
251254
}
252255

256+
// Randomly maps the byte array from libFuzzer directly onto char[] or converts each byte into a
257+
// 2 byte char. This helps in cases where a String is constructed out of char[] and libFuzzer
258+
// inserts CESU8 encoded bytes into the byte[].
259+
public char[] postMutateChars(byte[] bytes, PseudoRandom prng) {
260+
if (prng.choice()) {
261+
return (char[]) toPrimitive.apply(bytes);
262+
} else {
263+
char[] chars = new char[bytes.length];
264+
for (int i = 0; i < chars.length; i++) {
265+
chars[i] = (char) bytes[i];
266+
}
267+
return chars;
268+
}
269+
}
270+
271+
public BiFunction<byte[], PseudoRandom, ?> makeBytesToPrimitiveArrayAfterMutate(
272+
AnnotatedType type) {
273+
if (type.getType().getTypeName().equals("char")) {
274+
return this::postMutateChars;
275+
}
276+
return (bytes, ignored) -> makeBytesToPrimitiveArrayConverter(type).apply(bytes);
277+
}
278+
253279
public static Function<?, byte[]> makePrimitiveArrayToBytesConverter(AnnotatedType type) {
254280
switch (type.getType().getTypeName()) {
255281
case "int":

tests/BUILD.bazel

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,16 @@ java_fuzz_target_test(
11071107
],
11081108
)
11091109

1110+
java_fuzz_target_test(
1111+
name = "CharArrayFuzzer",
1112+
srcs = [
1113+
"src/test/java/com/example/CharArrayFuzzer.java",
1114+
],
1115+
allowed_findings = ["java.lang.RuntimeException"],
1116+
target_class = "com.example.CharArrayFuzzer",
1117+
verify_crash_reproducer = False,
1118+
)
1119+
11101120
filegroup(
11111121
name = "fuzz_test_lister_classes",
11121122
srcs = ["src/test/data/fuzz_test_lister_test"],
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2025 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+
public class CharArrayFuzzer {
20+
public static void fuzzerTestOneInput(char[] data) {
21+
if (data == null) {
22+
return;
23+
}
24+
String expression = new String(data);
25+
if (expression.contains("jazzer")) {
26+
throw new RuntimeException("found jazzer");
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)