diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java index dd321c72d..2ecc5512a 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java @@ -42,7 +42,9 @@ import java.lang.reflect.AnnotatedArrayType; import java.lang.reflect.AnnotatedType; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.Optional; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; @@ -81,6 +83,7 @@ public static final class PrimitiveArrayMutator extends SerializingMutator private final SerializingMutator innerMutator; private final Function toPrimitive; private final Function toBytes; + private final BiFunction toPrimitiveAfterMutate; @SuppressWarnings("unchecked") public PrimitiveArrayMutator(AnnotatedType type) { @@ -92,6 +95,8 @@ public PrimitiveArrayMutator(AnnotatedType type) { innerMutator = (SerializingMutator) LibFuzzerMutatorFactory.tryCreate(innerByteArray).get(); toPrimitive = (Function) makeBytesToPrimitiveArrayConverter(elementType); + toPrimitiveAfterMutate = + (BiFunction) makeBytesToPrimitiveArrayAfterMutate(elementType); toBytes = (Function) makePrimitiveArrayToBytesConverter(elementType); } @@ -128,14 +133,13 @@ public T init(PseudoRandom prng) { @Override public T mutate(T value, PseudoRandom prng) { - return (T) toPrimitive.apply(innerMutator.mutate(toBytes.apply(value), prng)); + return toPrimitiveAfterMutate.apply(innerMutator.mutate(toBytes.apply(value), prng), prng); } @Override public T crossOver(T value, T otherValue, PseudoRandom prng) { - return (T) - toPrimitive.apply( - innerMutator.crossOver(toBytes.apply(value), toBytes.apply(otherValue), prng)); + return toPrimitive.apply( + innerMutator.crossOver(toBytes.apply(value), toBytes.apply(otherValue), prng)); } private void extractRange(AnnotatedType type) { @@ -250,6 +254,29 @@ private static AnnotatedType convertWithLength(AnnotatedType type, AnnotatedType } } + // The strings we pass to native callbacks to trace data flow are CESU-8 encoded. + // As a result, libFuzzer's TORC contains CESU-8 encoded strings. + // Therefore, in 50% of times we decode the byte array as a CESU-8 string. + public char[] postMutateChars(byte[] bytes, PseudoRandom prng) { + if (prng.choice()) { + return (char[]) toPrimitive.apply(bytes); + } else { + char[] chars = new String(bytes, Charset.forName("CESU-8")).toCharArray(); + for (int i = 0; i < chars.length; i++) { + chars[i] = (char) forceInRange(chars[i], minRange, maxRange); + } + return chars; + } + } + + public BiFunction makeBytesToPrimitiveArrayAfterMutate( + AnnotatedType type) { + if (type.getType().getTypeName().equals("char")) { + return this::postMutateChars; + } + return (bytes, ignored) -> makeBytesToPrimitiveArrayConverter(type).apply(bytes); + } + public static Function makePrimitiveArrayToBytesConverter(AnnotatedType type) { switch (type.getType().getTypeName()) { case "int": diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 7289f9d3a..88df62574 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -1136,6 +1136,16 @@ java_fuzz_target_test( ], ) +java_fuzz_target_test( + name = "CharArrayFuzzer", + srcs = [ + "src/test/java/com/example/CharArrayFuzzer.java", + ], + allowed_findings = ["java.lang.RuntimeException"], + target_class = "com.example.CharArrayFuzzer", + verify_crash_reproducer = False, +) + filegroup( name = "fuzz_test_lister_classes", srcs = ["src/test/data/fuzz_test_lister_test"], diff --git a/tests/src/test/java/com/example/CharArrayFuzzer.java b/tests/src/test/java/com/example/CharArrayFuzzer.java new file mode 100644 index 000000000..8a63800c1 --- /dev/null +++ b/tests/src/test/java/com/example/CharArrayFuzzer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2025 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example; + +public class CharArrayFuzzer { + public static void fuzzerTestOneInput(char[] data) { + if (data == null) { + return; + } + String expression = new String(data); + if (expression.equals("中 Bös3r \uD801\uDC00 C0d3 中")) { + throw new RuntimeException("Found evil code"); + } + } +}