Skip to content

Commit 8e419fa

Browse files
committed
feat: @DictionaryProvider - propagate values to type mutators
This is just the enabling work. Methods and types annotated by @DictionaryProvider recursively propagate this annotation down the type hierarchy by default (can set to be for the annotated type only). Any mutator can now be adapted to use the user-provided values this annotation points to.
1 parent 770e8f8 commit 8e419fa

File tree

7 files changed

+402
-1
lines changed

7 files changed

+402
-1
lines changed

src/main/java/com/code_intelligence/jazzer/junit/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ java_library(
3838
"//examples/junit/src/test/java/com/example:__pkg__",
3939
"//selffuzz/src/test/java/com/code_intelligence/selffuzz:__subpackages__",
4040
"//src/test/java/com/code_intelligence/jazzer/junit:__pkg__",
41+
"//src/test/java/com/code_intelligence/jazzer/mutation/support:__pkg__",
4142
],
4243
exports = [
4344
":lifecycle",

src/main/java/com/code_intelligence/jazzer/mutation/ArgumentsMutator.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static java.util.Arrays.stream;
2424
import static java.util.stream.Collectors.joining;
2525

26+
import com.code_intelligence.jazzer.mutation.annotation.DictionaryProvider;
2627
import com.code_intelligence.jazzer.mutation.api.ExtendedMutatorFactory;
2728
import com.code_intelligence.jazzer.mutation.api.PseudoRandom;
2829
import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
@@ -32,6 +33,7 @@
3233
import com.code_intelligence.jazzer.mutation.mutator.Mutators;
3334
import com.code_intelligence.jazzer.mutation.runtime.MutationRuntime;
3435
import com.code_intelligence.jazzer.mutation.support.Preconditions;
36+
import com.code_intelligence.jazzer.mutation.support.TypeSupport;
3537
import com.code_intelligence.jazzer.utils.Log;
3638
import java.io.ByteArrayInputStream;
3739
import java.io.IOException;
@@ -104,7 +106,13 @@ public static Optional<ArgumentsMutator> forMethod(
104106
stream(method.getAnnotatedParameterTypes())
105107
.map(
106108
type -> {
107-
Optional<SerializingMutator<?>> mutator = mutatorFactory.tryCreate(type);
109+
// Forward all DictionaryProvider annotations of the fuzz test method to each
110+
// arg.
111+
AnnotatedType t = type;
112+
for (DictionaryProvider dict : typeDictionaries) {
113+
t = TypeSupport.withExtraAnnotations(t, dict);
114+
}
115+
Optional<SerializingMutator<?>> mutator = mutatorFactory.tryCreate(t);
108116
if (!mutator.isPresent()) {
109117
Log.error(
110118
String.format(

src/main/java/com/code_intelligence/jazzer/mutation/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ java_library(
1313
"//src/main/java/com/code_intelligence/jazzer/mutation/mutator",
1414
"//src/main/java/com/code_intelligence/jazzer/mutation/runtime",
1515
"//src/main/java/com/code_intelligence/jazzer/mutation/support",
16+
"//src/main/java/com/code_intelligence/jazzer/mutation/utils",
1617
"//src/main/java/com/code_intelligence/jazzer/utils:log",
1718
],
1819
)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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.code_intelligence.jazzer.mutation.annotation;
18+
19+
import static com.code_intelligence.jazzer.mutation.utils.PropertyConstraint.RECURSIVE;
20+
import static java.lang.annotation.ElementType.TYPE_USE;
21+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
22+
23+
import com.code_intelligence.jazzer.mutation.utils.IgnoreRecursiveConflicts;
24+
import com.code_intelligence.jazzer.mutation.utils.PropertyConstraint;
25+
import java.lang.annotation.ElementType;
26+
import java.lang.annotation.Retention;
27+
import java.lang.annotation.Target;
28+
29+
/**
30+
* Provides dictionary values to user-selected mutator types. Currently supported mutators are:
31+
*
32+
* <ul>
33+
* <li>String mutator
34+
* <li>Integral mutators (byte, short, int, long)
35+
* </ul>
36+
*
37+
* <p>This annotation can be applied to fuzz test methods and any parameter type or subtype. By
38+
* default, this annotation is propagated to all nested subtypes unless specified otherwise via the
39+
* {@link #constraint()} attribute.
40+
*
41+
* <p>Example usage:
42+
*
43+
* <pre>{@code
44+
* public class MyFuzzTarget {
45+
*
46+
* static Stream<?> dictionaryVisibleByAllArgumentMutators() {
47+
* return Stream.of("example1", "example2", "example3", 1232187321, -182371);
48+
* }
49+
*
50+
* static Stream<?> dictionaryVisibleOnlyByAnotherInput() {
51+
* return Stream.of("code-intelligence.com", "secret.url.1082h3u21ibsdsazuvbsa.com");
52+
* }
53+
*
54+
* @DictionaryProvider("dictionaryVisibleByAllArgumentMutators")
55+
* @FuzzTest
56+
* public void fuzzerTestOneInput(String input, @DictionaryProvider("dictionaryVisibleOnlyByAnotherInput") String anotherInput) {
57+
* // Fuzzing logic here
58+
* }
59+
* }
60+
* }</pre>
61+
*
62+
* In this example, the mutator for the String parameter {@code input} of the fuzz test method
63+
* {@code fuzzerTestOneInput} will be using the values returned by {@code provide} method during
64+
* mutation, while the mutator for String {@code anotherInput} will use values from both methods:
65+
* from the method-level {@code DictionaryProvider} annotation that uses {@code provide} and the
66+
* parameter-level {@code DictionaryProvider} annotation that uses {@code provideSomethingElse}.
67+
*/
68+
@Target({ElementType.METHOD, TYPE_USE})
69+
@Retention(RUNTIME)
70+
@IgnoreRecursiveConflicts
71+
@PropertyConstraint
72+
public @interface DictionaryProvider {
73+
/**
74+
* Specifies supplier methods that generate dictionary values for fuzzing the annotated method or
75+
* type. The specified supplier methods must be static and return a {@code Stream <?>} of values.
76+
* The values don't need to match the type of the annotated method or parameter exactly. The
77+
* mutation framework will extract only the values that are compatible with the target type.
78+
*/
79+
String[] value() default {""};
80+
81+
/**
82+
* This {@code DictionaryProvider} will be used with probability {@code 1/p} by the mutator
83+
* responsible for fitting types. Not all mutators respect this probability.
84+
*/
85+
int pInv() default 10;
86+
87+
/**
88+
* Defines the scope of the annotation. Possible values are defined in {@link
89+
* com.code_intelligence.jazzer.mutation.utils.PropertyConstraint}. It is convenient to use {@code
90+
* RECURSIVE} as the default value here, as dictionary objects are typically used for complex
91+
* types (e.g. custom classes) where the annotation is placed directly on the method or parameter
92+
* and is expected to apply to all nested fields.
93+
*/
94+
String constraint() default RECURSIVE;
95+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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.code_intelligence.jazzer.mutation.support;
18+
19+
import static com.code_intelligence.jazzer.mutation.support.Preconditions.require;
20+
21+
import com.code_intelligence.jazzer.mutation.annotation.DictionaryProvider;
22+
import com.code_intelligence.jazzer.mutation.runtime.MutationRuntime;
23+
import java.lang.reflect.AnnotatedType;
24+
import java.lang.reflect.InvocationTargetException;
25+
import java.lang.reflect.Method;
26+
import java.util.Arrays;
27+
import java.util.HashSet;
28+
import java.util.Map;
29+
import java.util.Optional;
30+
import java.util.stream.Collectors;
31+
import java.util.stream.Stream;
32+
33+
public class DictionaryProviderSupport {
34+
35+
/**
36+
* Extract inverse probability of the very first {@code DictionaryProvider} annotation on the
37+
* given type. The {@code @DictionaryProvider} annotation directly on the type is preferred; if
38+
* there is none, the first one appended because of {@code PropertyConstraint.RECURSIVE} is used.
39+
* Any further {@code @DictionaryProvider} annotations appended later to this type because of
40+
* {@code PropertyConstraint.RECURSIVE}, are ignored. Callers should ensure that at least one
41+
* {@code @DictionaryProvider} annotation is present on the type.
42+
*/
43+
public static int extractFirstInvProbability(AnnotatedType type) {
44+
// If there are several @DictionaryProvider annotations on the type, this will take the most
45+
// immediate one, because @DictionaryProvider is not repeatable.
46+
DictionaryProvider[] dictObj = type.getAnnotationsByType(DictionaryProvider.class);
47+
if (dictObj.length == 0) {
48+
// If we are here, it's a bug in the caller.
49+
throw new IllegalStateException("Expected to find @DictionaryProvider, but found none.");
50+
}
51+
int pInv = dictObj[0].pInv();
52+
require(pInv >= 2, "@DictionaryProvider.pInv must be at least 2");
53+
return pInv;
54+
}
55+
56+
/** Extract the provider streams using MutatorRuntime */
57+
public static Optional<Stream<?>> extractRawValues(AnnotatedType type) {
58+
DictionaryProvider[] providers =
59+
Arrays.stream(type.getAnnotations())
60+
.filter(a -> a instanceof DictionaryProvider)
61+
.map(a -> (DictionaryProvider) a)
62+
.toArray(DictionaryProvider[]::new);
63+
if (providers.length == 0) {
64+
return Optional.empty();
65+
}
66+
HashSet<String> providerMethodNames =
67+
Arrays.stream(providers)
68+
.map(DictionaryProvider::value)
69+
.flatMap(Arrays::stream)
70+
.filter(name -> !name.isEmpty())
71+
.collect(Collectors.toCollection(HashSet::new));
72+
if (providerMethodNames.isEmpty()) {
73+
return Optional.empty();
74+
}
75+
Map<String, Method> fuzzTestMethods =
76+
Arrays.stream(MutationRuntime.fuzzTestMethod.getDeclaringClass().getDeclaredMethods())
77+
.filter(m -> m.getParameterCount() == 0)
78+
.filter(m -> m.getReturnType().equals(Stream.class))
79+
.filter(
80+
m ->
81+
(m.getModifiers() & java.lang.reflect.Modifier.STATIC)
82+
== java.lang.reflect.Modifier.STATIC)
83+
.collect(Collectors.toMap(Method::getName, m -> m));
84+
return Optional.ofNullable(
85+
providerMethodNames.stream()
86+
.flatMap(
87+
name -> {
88+
Method m = fuzzTestMethods.get(name);
89+
if (m == null) {
90+
throw new IllegalStateException(
91+
"Found no static supplier method 'Stream<?> "
92+
+ name
93+
+ "()' in class "
94+
+ MutationRuntime.fuzzTestMethod.getDeclaringClass().getName());
95+
}
96+
try {
97+
m.setAccessible(true);
98+
return (Stream<?>) m.invoke(null);
99+
} catch (IllegalAccessException e) {
100+
throw new IllegalStateException("Cannot access method " + name, e);
101+
} catch (InvocationTargetException e) {
102+
throw new RuntimeException(
103+
"Supplier method " + name + " threw an exception", e.getCause());
104+
}
105+
})
106+
.distinct());
107+
}
108+
}

src/test/java/com/code_intelligence/jazzer/mutation/support/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ java_test_suite(
2929
runner = "junit5",
3030
deps = [
3131
":test_support",
32+
"//deploy:jazzer-project",
33+
"//src/main/java/com/code_intelligence/jazzer/junit:fuzz_test",
3234
"//src/main/java/com/code_intelligence/jazzer/mutation/annotation",
3335
"//src/main/java/com/code_intelligence/jazzer/mutation/support",
3436
"//src/main/java/com/code_intelligence/jazzer/mutation/utils",

0 commit comments

Comments
 (0)