Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/mutation-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ package.
| Annotation | Applies To | Notes |
|-------------------|----------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------|
| `@Ascii` | `java.lang.String` | `String` should only contain ASCII characters |
| `@InRange` | `byte`, `Byte`, `short`, `Short`, `int`, `Integer`, `long`, `Long` | Specifies `min` and `max` values of generated integrals |
| `@InRange` | `byte`, `Byte`, `char`, `Character`, short`, `Short`, `int`, `Integer`, `long`, `Long` | Specifies `min` and `max` values of generated integrals |
| `@FloatInRange` | `float`, `Float` | Specifies `min` and `max` values of generated floats |
| `@DoubleInRange` | `double`, `Double` | Specifies `min` and `max` values of generated doubles |
| `@Positive` | `byte`, `Byte`, `short`, `Short`, `int`, `Integer`, `long`, `Long`, `float`, `Float`, `double`, `Double` | Specifies that only positive values are generated |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
Byte.class,
short.class,
Short.class,
char.class,
Character.class,
int.class,
Integer.class,
long.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,39 @@ public void write(Short value, DataOutputStream out) throws IOException {
out.writeShort(value);
}
});
} else if (clazz == char.class || clazz == Character.class) {
return Optional.of(
new AbstractIntegralMutator<Character>(type, Character.MIN_VALUE, Character.MAX_VALUE) {
@Override
protected long mutateWithLibFuzzer(long value) {
return LibFuzzerMutate.mutateDefault((char) value, this, 0);
}

@Override
public Character init(PseudoRandom prng) {
return (char) initImpl(prng);
}

@Override
public Character mutate(Character value, PseudoRandom prng) {
return (char) mutateImpl(value, prng);
}

@Override
public Character crossOver(Character value, Character otherValue, PseudoRandom prng) {
return (char) crossOverImpl(value, otherValue, prng);
}

@Override
public Character read(DataInputStream in) throws IOException {
return (char) forceInRange(in.readChar());
}

@Override
public void write(Character value, DataOutputStream out) throws IOException {
out.writeShort(value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, why not use writeChar here for consistency with the readChar call above?

(though DataOutput defines both writeChar and writeShort to be implemented in the same way, so this might not matter)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea behind using writeShort here was sloppy copy & paste. Nice catch, thanks!

}
});
} else if (clazz == int.class || clazz == Integer.class) {
return Optional.of(
new AbstractIntegralMutator<Integer>(type, Integer.MIN_VALUE, Integer.MAX_VALUE) {
Expand Down Expand Up @@ -189,7 +222,7 @@ public void write(Long value, DataOutputStream out) throws IOException {
// Copyright 2022 Google LLC
//
// Visible for testing.
abstract static class AbstractIntegralMutator<T extends Number> extends SerializingMutator<T> {
abstract static class AbstractIntegralMutator<T> extends SerializingMutator<T> {
private static final long RANDOM_WALK_RANGE = 5;
private final long minValue;
private final long maxValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,22 @@ void singleParam(short parameter) {}
// only passes with ~90% of the optimal parameters.
expectedNumberOfDistinctElements(
1 << Short.SIZE, NUM_INITS * NUM_MUTATE_PER_INIT * 9 / 10)),
arguments(
new ParameterHolder() {
void singleParam(char parameter) {}
}.annotatedType(),
"Character",
true,
// init is heavily biased towards special values and only returns a uniformly random
// value in 1 out of 5 calls.
all(
expectedNumberOfDistinctElements(1 << Character.SIZE, boundHits(NUM_INITS, 0.2)),
contains((char) 1, Character.MIN_VALUE, Character.MAX_VALUE)),
// The integral type mutator does not always return uniformly random values and the
// random walk it uses is more likely to produce non-distinct elements, hence the test
// only passes with ~90% of the optimal parameters.
expectedNumberOfDistinctElements(
1 << Character.SIZE, NUM_INITS * NUM_MUTATE_PER_INIT * 9 / 10)),
arguments(
new ParameterHolder() {
void singleParam(int parameter) {}
Expand Down
10 changes: 10 additions & 0 deletions tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ java_fuzz_target_test(
verify_crash_input = False,
)

java_fuzz_target_test(
name = "CharFuzzer",
srcs = [
"src/test/java/com/example/CharFuzzer.java",
],
allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"],
target_class = "com.example.CharFuzzer",
verify_crash_reproducer = False,
)

java_fuzz_target_test(
name = "JpegImageParserAutofuzz",
allowed_findings = ["java.lang.NegativeArraySizeException"],
Expand Down
28 changes: 28 additions & 0 deletions tests/src/test/java/com/example/CharFuzzer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2024 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;

import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow;
import com.code_intelligence.jazzer.mutation.annotation.InRange;

public class CharFuzzer {
public static void fuzzerTestOneInput(@InRange(min = '\u0000', max = '\uFFFF') char data) {
if (data == '中') {
throw new FuzzerSecurityIssueLow("Found the 'secret' char!");
}
}
}