Skip to content

Commit ce896cf

Browse files
committed
feat: provide dictionaries directly to types with @DictionaryProvider
1 parent d5b177a commit ce896cf

File tree

4 files changed

+185
-1
lines changed

4 files changed

+185
-1
lines changed

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

Lines changed: 10 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.MutatorRuntime;
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;
@@ -99,12 +101,19 @@ public static Optional<ArgumentsMutator> forMethod(
99101
throw validationError;
100102
}
101103
MutatorRuntime runtime = MutatorRuntime.forFuzzTestMethod(method);
104+
DictionaryProvider[] typeDictionaries = method.getAnnotationsByType(DictionaryProvider.class);
102105
return toArrayOrEmpty(
103106
stream(method.getAnnotatedParameterTypes())
104107
.map(
105108
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+
}
106115
Optional<SerializingMutator<?>> mutator =
107-
mutatorFactory.tryCreate(runtime, type);
116+
mutatorFactory.tryCreate(runtime, t);
108117
if (!mutator.isPresent()) {
109118
Log.error(
110119
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: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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.PropertyConstraint;
24+
import java.lang.annotation.ElementType;
25+
import java.lang.annotation.Retention;
26+
import java.lang.annotation.Target;
27+
28+
/**
29+
* Specifies a custom dictionary provider for the annotated method or parameter. The specified
30+
* dictionary provider class must implement the {@link DictionaryProvider} interface.
31+
*
32+
* <p>The annotation can be applied to methods and parameters of any type. When applied to complex
33+
* types (e.g. custom classes), the annotation is expected to apply to all nested fields unless
34+
* specified otherwise via the {@link #constraint()} attribute.
35+
*
36+
* <p>Example usage:
37+
*
38+
* <pre>{@code
39+
* public class MyFuzzTarget {
40+
*
41+
* public Stream<?> provide() {
42+
* return Stream.of("example1", "example2", "example3", 1232187321, -182371);
43+
* }
44+
*
45+
* @DictionaryProvider("provide")
46+
* @FuzzTest
47+
* public void fuzzerTestOneInput(String input) {
48+
* // Fuzzing logic here
49+
* }
50+
* }
51+
* }</pre>
52+
*/
53+
@Target({ElementType.METHOD, TYPE_USE})
54+
@Retention(RUNTIME)
55+
@PropertyConstraint
56+
public @interface DictionaryProvider {
57+
String[] value() default {""};
58+
59+
/*
60+
* This {@code DictionaryProvider} will be used with probability {@code 1/p} by the mutator responsible for
61+
* fitting types. Not all mutators respect this probability.
62+
*/
63+
int pInv() default 10;
64+
65+
/**
66+
* Defines the scope of the annotation. Possible values are defined in {@link
67+
* com.code_intelligence.jazzer.mutation.utils.PropertyConstraint}. It is convenient to use {@code
68+
* RECURSIVE} as the default value here, as dictionary objects are typically used for complex
69+
* types (e.g. custom classes) where the annotation is placed directly on the method or parameter
70+
* and is expected to apply to all nested fields.
71+
*/
72+
String constraint() default RECURSIVE;
73+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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.MutatorRuntime;
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 last {@code DictionaryProvider} annotation on the given
37+
* type.
38+
*/
39+
public static int extractLastInvProbability(AnnotatedType type) {
40+
DictionaryProvider[] dictObj = type.getAnnotationsByType(DictionaryProvider.class);
41+
int pInv =
42+
Arrays.stream(dictObj)
43+
.map(DictionaryProvider::pInv)
44+
.reduce((first, second) -> second)
45+
.orElseThrow(() -> new IllegalStateException("No DictionaryProvider annotation found"));
46+
require(pInv >= 2, "@DictionaryProvider.pInv must be at least 2");
47+
return pInv;
48+
}
49+
50+
/** Extract the provider streams using MutatorRuntime */
51+
public static Optional<Stream<?>> extractProviderStreams(
52+
MutatorRuntime runtime, AnnotatedType type) {
53+
DictionaryProvider[] providers = type.getAnnotationsByType(DictionaryProvider.class);
54+
if (providers.length == 0) {
55+
return Optional.empty();
56+
}
57+
HashSet<String> providerMethodNames =
58+
Arrays.stream(providers)
59+
.map(DictionaryProvider::value) // Stream<String[]>
60+
.flatMap(Arrays::stream)
61+
.filter(name -> !name.isEmpty())
62+
.collect(Collectors.toCollection(HashSet::new));
63+
if (providerMethodNames.isEmpty()) {
64+
return Optional.empty();
65+
}
66+
Map<String, Method> fuzzTestMethods =
67+
Arrays.stream(runtime.fuzzTestMethod.getDeclaringClass().getDeclaredMethods())
68+
.filter(m -> m.getParameterCount() == 0)
69+
.filter(m -> m.getReturnType().equals(Stream.class))
70+
.filter(
71+
m ->
72+
(m.getModifiers()
73+
& (java.lang.reflect.Modifier.PUBLIC
74+
| java.lang.reflect.Modifier.STATIC))
75+
== (java.lang.reflect.Modifier.PUBLIC | java.lang.reflect.Modifier.STATIC))
76+
.collect(Collectors.toMap(Method::getName, m -> m));
77+
78+
return Optional.ofNullable(
79+
providerMethodNames.stream()
80+
.flatMap(
81+
name -> {
82+
Method m = fuzzTestMethods.get(name);
83+
if (m == null) {
84+
throw new IllegalStateException(
85+
"No method named "
86+
+ name
87+
+ " with signature 'public static Stream<?> "
88+
+ name
89+
+ "()' found in class "
90+
+ runtime.fuzzTestMethod.getDeclaringClass().getName());
91+
}
92+
try {
93+
return (Stream<?>) m.invoke(null);
94+
} catch (IllegalAccessException e) {
95+
throw new RuntimeException(e);
96+
} catch (InvocationTargetException e) {
97+
throw new RuntimeException(e);
98+
}
99+
}));
100+
}
101+
}

0 commit comments

Comments
 (0)