Skip to content

Commit db73ba1

Browse files
committed
feat: add Autofuzz constructor excludes option
1 parent 50a0e8f commit db73ba1

11 files changed

Lines changed: 505 additions & 7 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ java_library(
44
"AccessibleObjectLookup.java",
55
"AutofuzzCodegenVisitor.java",
66
"AutofuzzError.java",
7+
"ConstructorExclusions.java",
78
"FuzzTarget.java",
89
"Meta.java",
910
"YourAverageJavaClass.java",
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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.autofuzz;
18+
19+
import com.code_intelligence.jazzer.utils.Utils;
20+
import java.lang.reflect.Constructor;
21+
import java.util.Arrays;
22+
import java.util.Collections;
23+
import java.util.LinkedHashSet;
24+
import java.util.List;
25+
import java.util.Set;
26+
import java.util.stream.Collectors;
27+
28+
final class ConstructorExclusions {
29+
private static final String CONSTRUCTOR_REFERENCE = "::new";
30+
private static final ConstructorExclusions EMPTY =
31+
new ConstructorExclusions(Collections.emptySet());
32+
33+
static ConstructorExclusions empty() {
34+
return EMPTY;
35+
}
36+
37+
static ConstructorExclusions from(
38+
List<String> constructorReferences, AccessibleObjectLookup lookup) {
39+
return constructorReferences.isEmpty()
40+
? EMPTY
41+
: new ConstructorExclusions(
42+
constructorReferences.stream()
43+
.map(String::trim)
44+
.filter(reference -> !reference.isEmpty())
45+
.map(reference -> resolveConstructorReference(reference, lookup))
46+
.collect(
47+
Collectors.collectingAndThen(
48+
Collectors.toCollection(LinkedHashSet::new),
49+
Collections::unmodifiableSet)));
50+
}
51+
52+
private final Set<String> excludedConstructorReferences;
53+
54+
private ConstructorExclusions(Set<String> excludedConstructorReferences) {
55+
this.excludedConstructorReferences = excludedConstructorReferences;
56+
}
57+
58+
boolean isExcluded(Constructor<?> constructor) {
59+
return !excludedConstructorReferences.isEmpty()
60+
&& excludedConstructorReferences.contains(toConstructorReference(constructor));
61+
}
62+
63+
private static String toConstructorReference(Constructor<?> constructor) {
64+
return constructor.getDeclaringClass().getName()
65+
+ CONSTRUCTOR_REFERENCE
66+
+ Utils.getReadableDescriptor(constructor);
67+
}
68+
69+
private static String resolveConstructorReference(
70+
String reference, AccessibleObjectLookup lookup) {
71+
int separator = reference.indexOf(CONSTRUCTOR_REFERENCE);
72+
if (separator <= 0 || separator != reference.lastIndexOf(CONSTRUCTOR_REFERENCE)) {
73+
throw invalidConstructorReference(reference);
74+
}
75+
76+
String className = reference.substring(0, separator);
77+
String descriptor = reference.substring(separator + CONSTRUCTOR_REFERENCE.length());
78+
if (!descriptor.startsWith("(")
79+
|| !descriptor.endsWith(")")
80+
|| descriptor.indexOf(')') != descriptor.length() - 1) {
81+
throw invalidConstructorReference(reference);
82+
}
83+
84+
Class<?> clazz = loadClass(reference, className);
85+
Constructor<?>[] constructors = lookup.getAccessibleConstructors(clazz);
86+
List<Constructor<?>> matchingConstructors =
87+
Arrays.stream(constructors)
88+
.filter(constructor -> Utils.getReadableDescriptor(constructor).equals(descriptor))
89+
.collect(Collectors.toList());
90+
91+
if (matchingConstructors.size() == 1) {
92+
return toConstructorReference(matchingConstructors.get(0));
93+
}
94+
throw noMatchingConstructor(reference, clazz, constructors);
95+
}
96+
97+
private static Class<?> loadClass(String reference, String className) {
98+
try {
99+
return Class.forName(className, false, ClassLoader.getSystemClassLoader());
100+
} catch (ClassNotFoundException e) {
101+
throw classNotFound(reference, className, e);
102+
}
103+
}
104+
105+
private static IllegalArgumentException invalidConstructorReference(String reference) {
106+
return new IllegalArgumentException(
107+
String.format(
108+
"Invalid Autofuzz constructor exclude '%s'; expected exact constructor reference like"
109+
+ " 'com.example.Dangerous::new(int,java.util.Random)'",
110+
reference));
111+
}
112+
113+
private static IllegalArgumentException classNotFound(
114+
String reference, String className, ClassNotFoundException cause) {
115+
return new IllegalArgumentException(
116+
String.format(
117+
"Invalid Autofuzz constructor exclude '%s'; class '%s' could not be found",
118+
reference, className),
119+
cause);
120+
}
121+
122+
private static IllegalArgumentException noMatchingConstructor(
123+
String reference, Class<?> clazz, Constructor<?>[] constructors) {
124+
return new IllegalArgumentException(
125+
String.format(
126+
"Invalid Autofuzz constructor exclude '%s'; no accessible constructor with this"
127+
+ " descriptor found in %s.%nAccessible constructors:%n%s",
128+
reference, clazz.getName(), formatConstructorReferences(constructors)));
129+
}
130+
131+
private static String formatConstructorReferences(Constructor<?>[] constructors) {
132+
if (constructors.length == 0) {
133+
return "<none>";
134+
}
135+
return Arrays.stream(constructors)
136+
.map(ConstructorExclusions::toConstructorReference)
137+
.collect(Collectors.joining(System.lineSeparator()));
138+
}
139+
}

src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
import java.nio.file.Files;
3535
import java.nio.file.Path;
3636
import java.nio.file.Paths;
37+
import java.util.ArrayList;
3738
import java.util.Arrays;
39+
import java.util.Collections;
3840
import java.util.List;
3941
import java.util.Map;
4042
import java.util.Set;
@@ -51,6 +53,7 @@ public final class FuzzTarget {
5153
+ "}";
5254
private static final long MAX_EXECUTIONS_WITHOUT_INVOCATION = 100;
5355

56+
private static List<String> constructorExcludeReferences = Collections.emptyList();
5457
private static Meta meta;
5558
private static String methodReference;
5659
private static Executable[] targetExecutables;
@@ -59,6 +62,10 @@ public final class FuzzTarget {
5962
private static Set<SimpleGlobMatcher> ignoredExceptionMatchers;
6063
private static long executionsSinceLastInvocation = 0;
6164

65+
public static void setConstructorExcludeReferences(List<String> references) {
66+
constructorExcludeReferences = Collections.unmodifiableList(new ArrayList<>(references));
67+
}
68+
6269
public static void fuzzerInitialize(String[] args) {
6370
if (args.length == 0 || !args[0].contains("::")) {
6471
Log.error(
@@ -245,8 +252,22 @@ public static void fuzzerInitialize(String[] args) {
245252
Arrays.stream(method.getExceptionTypes()), alwaysIgnore.stream())
246253
.toArray(Class[]::new)));
247254

255+
ConstructorExclusions constructorExclusions;
256+
try {
257+
constructorExclusions = ConstructorExclusions.from(constructorExcludeReferences, lookup);
258+
} catch (IllegalArgumentException e) {
259+
Log.error(e.getMessage());
260+
System.exit(1);
261+
return;
262+
}
263+
248264
setTarget(
249-
executables, null, methodSignature, ignoredExceptionGlobMatchers, ignoredExceptionClasses);
265+
executables,
266+
null,
267+
methodSignature,
268+
ignoredExceptionGlobMatchers,
269+
ignoredExceptionClasses,
270+
constructorExclusions);
250271
}
251272

252273
/**
@@ -259,6 +280,22 @@ public static void setTarget(
259280
String methodReference,
260281
Set<SimpleGlobMatcher> ignoredExceptionMatchers,
261282
Map<Executable, Class<?>[]> throwsDeclarations) {
283+
setTarget(
284+
targetExecutables,
285+
targetInstance,
286+
methodReference,
287+
ignoredExceptionMatchers,
288+
throwsDeclarations,
289+
ConstructorExclusions.empty());
290+
}
291+
292+
private static void setTarget(
293+
Executable[] targetExecutables,
294+
Object targetInstance,
295+
String methodReference,
296+
Set<SimpleGlobMatcher> ignoredExceptionMatchers,
297+
Map<Executable, Class<?>[]> throwsDeclarations,
298+
ConstructorExclusions constructorExclusions) {
262299
Class<?> targetClass = null;
263300
for (Executable executable : targetExecutables) {
264301
if (targetClass != null && !targetClass.equals(executable.getDeclaringClass())) {
@@ -269,7 +306,7 @@ public static void setTarget(
269306
executable.setAccessible(true);
270307
}
271308

272-
FuzzTarget.meta = new Meta(targetClass);
309+
FuzzTarget.meta = new Meta(targetClass, constructorExclusions);
273310
FuzzTarget.targetExecutables = targetExecutables;
274311
FuzzTarget.targetInstance = targetInstance;
275312
FuzzTarget.methodReference = methodReference;

src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,15 @@ public class Meta {
7272
new WeakHashMap<>();
7373

7474
private final AccessibleObjectLookup lookup;
75+
private final ConstructorExclusions constructorExclusions;
7576

7677
public Meta(Class<?> referenceClass) {
78+
this(referenceClass, ConstructorExclusions.empty());
79+
}
80+
81+
Meta(Class<?> referenceClass, ConstructorExclusions constructorExclusions) {
7782
lookup = new AccessibleObjectLookup(referenceClass);
83+
this.constructorExclusions = constructorExclusions;
7884
}
7985

8086
@SuppressWarnings("unchecked")
@@ -610,7 +616,7 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor
610616
if (visitor != null) {
611617
throw new AutofuzzError("codegen has not been implemented for Constructor.class");
612618
}
613-
return data.pickValue(lookup.getAccessibleConstructors(YourAverageJavaClass.class));
619+
return data.pickValue(getAccessibleConstructors(YourAverageJavaClass.class));
614620
} else if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) {
615621
List<Class<?>> implementingClasses = implementingClassesCache.get(type);
616622
if (implementingClasses == null) {
@@ -665,7 +671,7 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor
665671
}
666672
return result;
667673
}
668-
Constructor<?>[] constructors = lookup.getAccessibleConstructors(type);
674+
Constructor<?>[] constructors = getAccessibleConstructors(type);
669675
if (constructors.length > 0) {
670676
Constructor<?> constructor = data.pickValue(constructors);
671677
boolean applySetters = constructor.getParameterCount() == 0;
@@ -701,7 +707,10 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor
701707

702708
// First, try to find nested classes with names ending in Builder and call a subset of their
703709
// chaining methods.
704-
List<Class<?>> nestedBuilderClasses = getNestedBuilderClasses(type);
710+
List<Class<?>> nestedBuilderClasses =
711+
getNestedBuilderClasses(type).stream()
712+
.filter(builder -> getAccessibleConstructors(builder).length > 0)
713+
.collect(Collectors.toList());
705714
if (!nestedBuilderClasses.isEmpty()) {
706715
Class<?> pickedBuilder = data.pickValue(nestedBuilderClasses);
707716
List<Method> cascadingBuilderMethods = getCascadingBuilderMethods(pickedBuilder);
@@ -717,7 +726,7 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor
717726
}
718727
Object builderObj =
719728
autofuzzForConsume(
720-
data, data.pickValue(lookup.getAccessibleConstructors(pickedBuilder)), visitor);
729+
data, data.pickValue(getAccessibleConstructors(pickedBuilder)), visitor);
721730
for (Method method : pickedMethods) {
722731
builderObj = autofuzzForConsume(data, method, builderObj, visitor);
723732
}
@@ -741,7 +750,7 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor
741750
"Failed to generate instance of %s:%nAccessible constructors: %s%nNested subclasses:"
742751
+ " %s%n",
743752
type.getName(),
744-
Arrays.stream(lookup.getAccessibleConstructors(type))
753+
Arrays.stream(getAccessibleConstructors(type))
745754
.map(Utils::getReadableDescriptor)
746755
.collect(Collectors.joining(", ")),
747756
Arrays.stream(lookup.getAccessibleClasses(type))
@@ -766,6 +775,12 @@ private List<Class<?>> getNestedBuilderClasses(Class<?> type) {
766775
return nestedBuilderClasses;
767776
}
768777

778+
private Constructor<?>[] getAccessibleConstructors(Class<?> type) {
779+
return Arrays.stream(lookup.getAccessibleConstructors(type))
780+
.filter(constructor -> !constructorExclusions.isExcluded(constructor))
781+
.toArray(Constructor<?>[]::new);
782+
}
783+
769784
private List<Method> getOriginalObjectCreationMethods(Class<?> builder) {
770785
List<Method> originalObjectCreationMethods = originalObjectCreationMethodsCache.get(builder);
771786
if (originalObjectCreationMethods == null) {

src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,16 @@ public final class FuzzTargetRunner {
6868
Log.error("--autofuzz_ignore requires --autofuzz");
6969
exit(1);
7070
}
71+
if (!Opt.autofuzzConstructorExcludes.get().isEmpty()) {
72+
Log.error("--autofuzz_constructor_excludes requires --autofuzz");
73+
exit(1);
74+
}
7175
} else {
7276
if (!Opt.targetClass.get().isEmpty()) {
7377
Log.error("--target_class and --autofuzz cannot be specified together");
7478
exit(1);
7579
}
80+
FuzzTarget.setConstructorExcludeReferences(Opt.autofuzzConstructorExcludes.get());
7681
if (!Opt.targetArgs.setIfDefault(
7782
unmodifiableList(
7883
concat(Stream.of(Opt.autofuzz.get()), Opt.autofuzzIgnore.get().stream())

src/main/java/com/code_intelligence/jazzer/driver/Opt.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ public final class Opt {
105105
"autofuzz_ignore",
106106
',',
107107
"Fully qualified names of exception classes to ignore during fuzzing");
108+
public static final OptItem<List<String>> autofuzzConstructorExcludes =
109+
stringListSetting(
110+
"autofuzz_constructor_excludes",
111+
';',
112+
"Exact constructor references to exclude during Autofuzz argument construction");
108113
public static final OptItem<String> coverageDump =
109114
stringSetting(
110115
"coverage_dump",

src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ java_test(
2121
],
2222
)
2323

24+
java_test(
25+
name = "ConstructorExclusionsTest",
26+
size = "small",
27+
srcs = [
28+
"ConstructorExclusionsTest.java",
29+
],
30+
test_class = "com.code_intelligence.jazzer.autofuzz.ConstructorExclusionsTest",
31+
deps = [
32+
"//src/main/java/com/code_intelligence/jazzer/autofuzz",
33+
"@maven//:junit_junit",
34+
],
35+
)
36+
2437
java_test(
2538
name = "InterfaceCreationTest",
2639
size = "small",

0 commit comments

Comments
 (0)