diff --git a/docs/arguments-and-configuration-options.md b/docs/arguments-and-configuration-options.md index 16e7c5183..1425ae100 100644 --- a/docs/arguments-and-configuration-options.md +++ b/docs/arguments-and-configuration-options.md @@ -42,6 +42,9 @@ Some parameters only have an effect when used with standalone Jazzer binary (mar - **autofuzz_ignore** [list, separator=`','`, default=""] (*DEPRECATED*) - Fully qualified names of exception classes to ignore during fuzzing +- **autofuzz_constructor_excludes** [list, separator=`';'`, default=""] (*DEPRECATED*) + - Exact constructor references to exclude during Autofuzz argument construction + - **command_line** [bool, default="false"] - Whether Jazzer is running a JUnit fuzz test from the command line diff --git a/docs/autofuzz.md b/docs/autofuzz.md index 3508050f5..ac44ea3f5 100644 --- a/docs/autofuzz.md +++ b/docs/autofuzz.md @@ -18,6 +18,17 @@ Under the hood, Jazzer tries various ways of creating objects from the fuzzer in For example, if a parameter is an interface or an abstract class, it will look for all concrete implementing classes on the classpath. Jazzer can also create objects from classes that follow the [builder design pattern](https://www.baeldung.com/creational-design-patterns#builder) or have a default constructor and use setters to set the fields. +Some constructors have undesired side effects or can take a very long time for fuzzed inputs. +Exact constructor references can be excluded via `--autofuzz_constructor_excludes` as a semicolon-separated list: +``` +--autofuzz_constructor_excludes='com.example.Dangerous::new(int,java.util.Random);com.example.Dangerous::new(byte[])' +``` +The constructor descriptor is exact and uses the same parameter type format as `--autofuzz` method descriptors. +Each exclude must name an accessible constructor that exists when Autofuzz initializes. +Nested classes must be specified with their binary names, for example `com.example.Outer$Inner::new()`. +Globs, regular expressions, and broad `Class::new` excludes are not supported. +Explicitly targeted constructors, for example via `--autofuzz=com.example.Dangerous::new(int)`, are not affected by this option. + Creating objects from fuzzer input can lead to many reported exceptions. Jazzer addresses this issue by ignoring exceptions that the target method declares to throw. In addition to that, you can provide a list of exceptions to be ignored during fuzzing via the `--autofuzz_ignore` flag in the form of a comma-separated list. diff --git a/src/main/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel index 91595f93f..957f6aad9 100644 --- a/src/main/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel @@ -4,6 +4,7 @@ java_library( "AccessibleObjectLookup.java", "AutofuzzCodegenVisitor.java", "AutofuzzError.java", + "ConstructorExclusions.java", "FuzzTarget.java", "Meta.java", "YourAverageJavaClass.java", diff --git a/src/main/java/com/code_intelligence/jazzer/autofuzz/ConstructorExclusions.java b/src/main/java/com/code_intelligence/jazzer/autofuzz/ConstructorExclusions.java new file mode 100644 index 000000000..665c45640 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/autofuzz/ConstructorExclusions.java @@ -0,0 +1,139 @@ +/* + * 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.code_intelligence.jazzer.autofuzz; + +import com.code_intelligence.jazzer.utils.Utils; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +final class ConstructorExclusions { + private static final String CONSTRUCTOR_REFERENCE = "::new"; + private static final ConstructorExclusions EMPTY = + new ConstructorExclusions(Collections.emptySet()); + + static ConstructorExclusions empty() { + return EMPTY; + } + + static ConstructorExclusions from( + List constructorReferences, AccessibleObjectLookup lookup) { + return constructorReferences.isEmpty() + ? EMPTY + : new ConstructorExclusions( + constructorReferences.stream() + .map(String::trim) + .filter(reference -> !reference.isEmpty()) + .map(reference -> resolveConstructorReference(reference, lookup)) + .collect( + Collectors.collectingAndThen( + Collectors.toCollection(LinkedHashSet::new), + Collections::unmodifiableSet))); + } + + private final Set excludedConstructorReferences; + + private ConstructorExclusions(Set excludedConstructorReferences) { + this.excludedConstructorReferences = excludedConstructorReferences; + } + + boolean isExcluded(Constructor constructor) { + return !excludedConstructorReferences.isEmpty() + && excludedConstructorReferences.contains(toConstructorReference(constructor)); + } + + private static String toConstructorReference(Constructor constructor) { + return constructor.getDeclaringClass().getName() + + CONSTRUCTOR_REFERENCE + + Utils.getReadableDescriptor(constructor); + } + + private static String resolveConstructorReference( + String reference, AccessibleObjectLookup lookup) { + int separator = reference.indexOf(CONSTRUCTOR_REFERENCE); + if (separator <= 0 || separator != reference.lastIndexOf(CONSTRUCTOR_REFERENCE)) { + throw invalidConstructorReference(reference); + } + + String className = reference.substring(0, separator); + String descriptor = reference.substring(separator + CONSTRUCTOR_REFERENCE.length()); + if (!descriptor.startsWith("(") + || !descriptor.endsWith(")") + || descriptor.indexOf(')') != descriptor.length() - 1) { + throw invalidConstructorReference(reference); + } + + Class clazz = loadClass(reference, className); + Constructor[] constructors = lookup.getAccessibleConstructors(clazz); + List> matchingConstructors = + Arrays.stream(constructors) + .filter(constructor -> Utils.getReadableDescriptor(constructor).equals(descriptor)) + .collect(Collectors.toList()); + + if (matchingConstructors.size() == 1) { + return toConstructorReference(matchingConstructors.get(0)); + } + throw noMatchingConstructor(reference, clazz, constructors); + } + + private static Class loadClass(String reference, String className) { + try { + return Class.forName(className, false, ClassLoader.getSystemClassLoader()); + } catch (ClassNotFoundException e) { + throw classNotFound(reference, className, e); + } + } + + private static IllegalArgumentException invalidConstructorReference(String reference) { + return new IllegalArgumentException( + String.format( + "Invalid Autofuzz constructor exclude '%s'; expected exact constructor reference like" + + " 'com.example.Dangerous::new(int,java.util.Random)'", + reference)); + } + + private static IllegalArgumentException classNotFound( + String reference, String className, ClassNotFoundException cause) { + return new IllegalArgumentException( + String.format( + "Invalid Autofuzz constructor exclude '%s'; class '%s' could not be found", + reference, className), + cause); + } + + private static IllegalArgumentException noMatchingConstructor( + String reference, Class clazz, Constructor[] constructors) { + return new IllegalArgumentException( + String.format( + "Invalid Autofuzz constructor exclude '%s'; no accessible constructor with this" + + " descriptor found in %s.%nAccessible constructors:%n%s", + reference, clazz.getName(), formatConstructorReferences(constructors))); + } + + private static String formatConstructorReferences(Constructor[] constructors) { + if (constructors.length == 0) { + return ""; + } + return Arrays.stream(constructors) + .map(ConstructorExclusions::toConstructorReference) + .collect(Collectors.joining(System.lineSeparator())); + } +} diff --git a/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java b/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java index 175e64fb5..8f1f9cb5e 100644 --- a/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java +++ b/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java @@ -34,7 +34,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -51,6 +53,7 @@ public final class FuzzTarget { + "}"; private static final long MAX_EXECUTIONS_WITHOUT_INVOCATION = 100; + private static List constructorExcludeReferences = Collections.emptyList(); private static Meta meta; private static String methodReference; private static Executable[] targetExecutables; @@ -59,6 +62,10 @@ public final class FuzzTarget { private static Set ignoredExceptionMatchers; private static long executionsSinceLastInvocation = 0; + public static void setConstructorExcludeReferences(List references) { + constructorExcludeReferences = Collections.unmodifiableList(new ArrayList<>(references)); + } + public static void fuzzerInitialize(String[] args) { if (args.length == 0 || !args[0].contains("::")) { Log.error( @@ -245,8 +252,22 @@ public static void fuzzerInitialize(String[] args) { Arrays.stream(method.getExceptionTypes()), alwaysIgnore.stream()) .toArray(Class[]::new))); + ConstructorExclusions constructorExclusions; + try { + constructorExclusions = ConstructorExclusions.from(constructorExcludeReferences, lookup); + } catch (IllegalArgumentException e) { + Log.error(e.getMessage()); + System.exit(1); + return; + } + setTarget( - executables, null, methodSignature, ignoredExceptionGlobMatchers, ignoredExceptionClasses); + executables, + null, + methodSignature, + ignoredExceptionGlobMatchers, + ignoredExceptionClasses, + constructorExclusions); } /** @@ -259,6 +280,22 @@ public static void setTarget( String methodReference, Set ignoredExceptionMatchers, Map[]> throwsDeclarations) { + setTarget( + targetExecutables, + targetInstance, + methodReference, + ignoredExceptionMatchers, + throwsDeclarations, + ConstructorExclusions.empty()); + } + + private static void setTarget( + Executable[] targetExecutables, + Object targetInstance, + String methodReference, + Set ignoredExceptionMatchers, + Map[]> throwsDeclarations, + ConstructorExclusions constructorExclusions) { Class targetClass = null; for (Executable executable : targetExecutables) { if (targetClass != null && !targetClass.equals(executable.getDeclaringClass())) { @@ -269,7 +306,7 @@ public static void setTarget( executable.setAccessible(true); } - FuzzTarget.meta = new Meta(targetClass); + FuzzTarget.meta = new Meta(targetClass, constructorExclusions); FuzzTarget.targetExecutables = targetExecutables; FuzzTarget.targetInstance = targetInstance; FuzzTarget.methodReference = methodReference; diff --git a/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java b/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java index 3c26f255f..56cc53357 100644 --- a/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java +++ b/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java @@ -72,9 +72,15 @@ public class Meta { new WeakHashMap<>(); private final AccessibleObjectLookup lookup; + private final ConstructorExclusions constructorExclusions; public Meta(Class referenceClass) { + this(referenceClass, ConstructorExclusions.empty()); + } + + Meta(Class referenceClass, ConstructorExclusions constructorExclusions) { lookup = new AccessibleObjectLookup(referenceClass); + this.constructorExclusions = constructorExclusions; } @SuppressWarnings("unchecked") @@ -610,7 +616,7 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor if (visitor != null) { throw new AutofuzzError("codegen has not been implemented for Constructor.class"); } - return data.pickValue(lookup.getAccessibleConstructors(YourAverageJavaClass.class)); + return data.pickValue(getAccessibleConstructors(YourAverageJavaClass.class)); } else if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { List> implementingClasses = implementingClassesCache.get(type); if (implementingClasses == null) { @@ -665,7 +671,7 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor } return result; } - Constructor[] constructors = lookup.getAccessibleConstructors(type); + Constructor[] constructors = getAccessibleConstructors(type); if (constructors.length > 0) { Constructor constructor = data.pickValue(constructors); boolean applySetters = constructor.getParameterCount() == 0; @@ -701,7 +707,10 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor // First, try to find nested classes with names ending in Builder and call a subset of their // chaining methods. - List> nestedBuilderClasses = getNestedBuilderClasses(type); + List> nestedBuilderClasses = + getNestedBuilderClasses(type).stream() + .filter(builder -> getAccessibleConstructors(builder).length > 0) + .collect(Collectors.toList()); if (!nestedBuilderClasses.isEmpty()) { Class pickedBuilder = data.pickValue(nestedBuilderClasses); List cascadingBuilderMethods = getCascadingBuilderMethods(pickedBuilder); @@ -717,7 +726,7 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor } Object builderObj = autofuzzForConsume( - data, data.pickValue(lookup.getAccessibleConstructors(pickedBuilder)), visitor); + data, data.pickValue(getAccessibleConstructors(pickedBuilder)), visitor); for (Method method : pickedMethods) { builderObj = autofuzzForConsume(data, method, builderObj, visitor); } @@ -741,7 +750,7 @@ Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor "Failed to generate instance of %s:%nAccessible constructors: %s%nNested subclasses:" + " %s%n", type.getName(), - Arrays.stream(lookup.getAccessibleConstructors(type)) + Arrays.stream(getAccessibleConstructors(type)) .map(Utils::getReadableDescriptor) .collect(Collectors.joining(", ")), Arrays.stream(lookup.getAccessibleClasses(type)) @@ -766,6 +775,12 @@ private List> getNestedBuilderClasses(Class type) { return nestedBuilderClasses; } + private Constructor[] getAccessibleConstructors(Class type) { + return Arrays.stream(lookup.getAccessibleConstructors(type)) + .filter(constructor -> !constructorExclusions.isExcluded(constructor)) + .toArray(Constructor[]::new); + } + private List getOriginalObjectCreationMethods(Class builder) { List originalObjectCreationMethods = originalObjectCreationMethodsCache.get(builder); if (originalObjectCreationMethods == null) { diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 7c8c8b285..bef7a6b17 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -68,11 +68,16 @@ public final class FuzzTargetRunner { Log.error("--autofuzz_ignore requires --autofuzz"); exit(1); } + if (!Opt.autofuzzConstructorExcludes.get().isEmpty()) { + Log.error("--autofuzz_constructor_excludes requires --autofuzz"); + exit(1); + } } else { if (!Opt.targetClass.get().isEmpty()) { Log.error("--target_class and --autofuzz cannot be specified together"); exit(1); } + FuzzTarget.setConstructorExcludeReferences(Opt.autofuzzConstructorExcludes.get()); if (!Opt.targetArgs.setIfDefault( unmodifiableList( concat(Stream.of(Opt.autofuzz.get()), Opt.autofuzzIgnore.get().stream()) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/Opt.java b/src/main/java/com/code_intelligence/jazzer/driver/Opt.java index 16cd022a6..19e731fa3 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/Opt.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/Opt.java @@ -105,6 +105,11 @@ public final class Opt { "autofuzz_ignore", ',', "Fully qualified names of exception classes to ignore during fuzzing"); + public static final OptItem> autofuzzConstructorExcludes = + stringListSetting( + "autofuzz_constructor_excludes", + ';', + "Exact constructor references to exclude during Autofuzz argument construction"); public static final OptItem coverageDump = stringSetting( "coverage_dump", diff --git a/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel index 8314c5ad5..bea491dfc 100644 --- a/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel +++ b/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel @@ -21,6 +21,19 @@ java_test( ], ) +java_test( + name = "ConstructorExclusionsTest", + size = "small", + srcs = [ + "ConstructorExclusionsTest.java", + ], + test_class = "com.code_intelligence.jazzer.autofuzz.ConstructorExclusionsTest", + deps = [ + "//src/main/java/com/code_intelligence/jazzer/autofuzz", + "@maven//:junit_junit", + ], +) + java_test( name = "InterfaceCreationTest", size = "small", diff --git a/src/test/java/com/code_intelligence/jazzer/autofuzz/ConstructorExclusionsTest.java b/src/test/java/com/code_intelligence/jazzer/autofuzz/ConstructorExclusionsTest.java new file mode 100644 index 000000000..126549eb5 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/autofuzz/ConstructorExclusionsTest.java @@ -0,0 +1,128 @@ +/* + * 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.code_intelligence.jazzer.autofuzz; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.math.BigInteger; +import java.util.Collections; +import java.util.Random; +import org.junit.Test; + +public class ConstructorExclusionsTest { + private static final AccessibleObjectLookup PUBLIC_LOOKUP = new AccessibleObjectLookup(null); + + public static class MultipleConstructors { + public MultipleConstructors() {} + + public MultipleConstructors(int value) {} + + public MultipleConstructors(String value) {} + } + + @Test + public void defaultsDoNotExcludeConstructors() throws NoSuchMethodException { + ConstructorExclusions exclusions = ConstructorExclusions.empty(); + + assertFalse(exclusions.isExcluded(MultipleConstructors.class.getConstructor(int.class))); + } + + @Test + public void userExcludeMatchesExactConstructorOnly() throws NoSuchMethodException { + ConstructorExclusions exclusions = + ConstructorExclusions.from( + Collections.singletonList( + "com.code_intelligence.jazzer.autofuzz.ConstructorExclusionsTest" + + "$MultipleConstructors::new(int)"), + PUBLIC_LOOKUP); + + assertTrue(exclusions.isExcluded(MultipleConstructors.class.getConstructor(int.class))); + assertFalse(exclusions.isExcluded(MultipleConstructors.class.getConstructor(String.class))); + assertFalse(exclusions.isExcluded(MultipleConstructors.class.getConstructor())); + } + + @Test + public void userExcludeMatchesBigIntegerPrimeConstructorOnly() throws NoSuchMethodException { + ConstructorExclusions exclusions = + ConstructorExclusions.from( + Collections.singletonList("java.math.BigInteger::new(int,int,java.util.Random)"), + PUBLIC_LOOKUP); + + assertTrue( + exclusions.isExcluded(BigInteger.class.getConstructor(int.class, int.class, Random.class))); + assertFalse(exclusions.isExcluded(BigInteger.class.getConstructor(byte[].class))); + } + + @Test(expected = IllegalArgumentException.class) + public void rejectsMissingDescriptor() { + ConstructorExclusions.from( + Collections.singletonList( + "com.code_intelligence.jazzer.autofuzz.ConstructorExclusionsTest" + + "$MultipleConstructors::new"), + PUBLIC_LOOKUP); + } + + @Test(expected = IllegalArgumentException.class) + public void rejectsCanonicalNestedClassName() { + ConstructorExclusions.from( + Collections.singletonList( + "com.code_intelligence.jazzer.autofuzz.ConstructorExclusionsTest" + + ".MultipleConstructors::new(int)"), + PUBLIC_LOOKUP); + } + + @Test(expected = IllegalArgumentException.class) + public void rejectsGlobs() { + ConstructorExclusions.from(Collections.singletonList("java.math.*::new(int)"), PUBLIC_LOOKUP); + } + + @Test(expected = IllegalArgumentException.class) + public void rejectsWhitespace() { + ConstructorExclusions.from( + Collections.singletonList( + "com.code_intelligence.jazzer.autofuzz.ConstructorExclusionsTest" + + "$MultipleConstructors::new(int )"), + PUBLIC_LOOKUP); + } + + @Test(expected = IllegalArgumentException.class) + public void rejectsGenericTypes() { + ConstructorExclusions.from( + Collections.singletonList( + "com.code_intelligence.jazzer.autofuzz.ConstructorExclusionsTest" + + "$MultipleConstructors::new(java.util.List)"), + PUBLIC_LOOKUP); + } + + @Test + public void missingConstructorErrorListsAvailableConstructors() { + String reference = + "com.code_intelligence.jazzer.autofuzz.ConstructorExclusionsTest" + + "$MultipleConstructors::new(long)"; + + try { + ConstructorExclusions.from(Collections.singletonList(reference), PUBLIC_LOOKUP); + fail("Expected an IllegalArgumentException"); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains(reference)); + assertTrue(e.getMessage().contains("Accessible constructors:")); + assertTrue(e.getMessage().contains(MultipleConstructors.class.getName() + "::new(int)")); + } + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java index e895df42f..885969d6a 100644 --- a/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java +++ b/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java @@ -19,15 +19,19 @@ import static com.code_intelligence.jazzer.autofuzz.TestHelpers.autofuzzTestCase; import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider; import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.google.json.JsonSanitizer; import java.io.ByteArrayInputStream; +import java.lang.reflect.Constructor; import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collections; import java.util.Map; +import java.util.Objects; import org.junit.Test; public class MetaTest { @@ -37,6 +41,31 @@ public enum TestEnum { BAZ, } + public static class ConstructorExclusionTarget { + private final int value; + + public ConstructorExclusionTarget(int value) { + this.value = value; + } + + public ConstructorExclusionTarget(String value) { + this.value = value.length(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConstructorExclusionTarget that = (ConstructorExclusionTarget) o; + return value == that.value; + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + } + @Test public void testConsume() throws NoSuchMethodException { consumeTestCase(5, "5", Collections.singletonList(5)); @@ -234,6 +263,60 @@ private Map returnsStringStringMap() { "Should not be called, only exists to construct its generic return type"); } + @Test + public void testConstructorExclusionsDuringConsume() throws NoSuchMethodException { + AutofuzzCodegenVisitor visitor = new AutofuzzCodegenVisitor(); + FuzzedDataProvider data = + CannedFuzzedDataProvider.create( + Arrays.asList( + (byte) 1, // do not return null + 0, // pick the first non-excluded constructor (String) + (byte) 1, // do not return null for the String constructor argument + 8, // remaining bytes + "safe" // String constructor argument + )); + AccessibleObjectLookup lookup = new AccessibleObjectLookup(null); + Constructor intConstructor = ConstructorExclusionTarget.class.getConstructor(int.class); + Constructor stringConstructor = + ConstructorExclusionTarget.class.getConstructor(String.class); + ConstructorExclusions constructorExclusions = + ConstructorExclusions.from( + Collections.singletonList(ConstructorExclusionTarget.class.getName() + "::new(int)"), + lookup); + + assertEquals( + intConstructor, lookup.getAccessibleConstructors(ConstructorExclusionTarget.class)[0]); + assertTrue(constructorExclusions.isExcluded(intConstructor)); + assertFalse(constructorExclusions.isExcluded(stringConstructor)); + + assertEquals( + new ConstructorExclusionTarget("safe"), + new Meta(null, constructorExclusions) + .consume(data, ConstructorExclusionTarget.class, visitor)); + assertEquals( + "new com.code_intelligence.jazzer.autofuzz.MetaTest.ConstructorExclusionTarget(" + + "\"safe\")", + visitor.generate()); + } + + @Test + public void testConstructorExclusionsDoNotApplyToExplicitTargets() throws NoSuchMethodException { + AutofuzzCodegenVisitor visitor = new AutofuzzCodegenVisitor(); + FuzzedDataProvider data = CannedFuzzedDataProvider.create(Collections.singletonList(42)); + ConstructorExclusions constructorExclusions = + ConstructorExclusions.from( + Collections.singletonList(ConstructorExclusionTarget.class.getName() + "::new(int)"), + new AccessibleObjectLookup(null)); + + assertEquals( + new ConstructorExclusionTarget(42), + new Meta(null, constructorExclusions) + .autofuzz(data, ConstructorExclusionTarget.class.getConstructor(int.class), visitor)); + assertEquals( + "new com.code_intelligence.jazzer.autofuzz.MetaTest.ConstructorExclusionTarget(42)", + visitor.generate()); + } + public static boolean isFive(int arg) { return arg == 5; } diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index bd857bfd2..e05bd94ec 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -564,6 +564,39 @@ java_fuzz_target_test( ], ) +java_library( + name = "autofuzz_constructor_excludes_target", + srcs = ["src/test/java/com/example/AutofuzzConstructorExcludesTarget.java"], + deps = ["//deploy:jazzer-api"], +) + +java_fuzz_target_test( + name = "AutofuzzConstructorExcludesPositiveFuzzer", + allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"], + expect_crash = True, + expect_number_of_findings = 1, + fuzzer_args = [ + "--autofuzz=com.example.AutofuzzConstructorExcludesTarget::target", + "-runs=1000", + ], + runtime_deps = [ + ":autofuzz_constructor_excludes_target", + ], +) + +java_fuzz_target_test( + name = "AutofuzzConstructorExcludesFuzzer", + expect_crash = False, + fuzzer_args = [ + "--autofuzz=com.example.AutofuzzConstructorExcludesTarget::target", + "--autofuzz_constructor_excludes=com.example.AutofuzzConstructorExcludesTarget$$Argument::new(int)", + "-runs=1000", + ], + runtime_deps = [ + ":autofuzz_constructor_excludes_target", + ], +) + java_library( name = "autofuzz_assertion_error_target", srcs = ["src/test/java/com/example/AutofuzzAssertionErrorTarget.java"], diff --git a/tests/src/test/java/com/example/AutofuzzConstructorExcludesTarget.java b/tests/src/test/java/com/example/AutofuzzConstructorExcludesTarget.java new file mode 100644 index 000000000..4e345f399 --- /dev/null +++ b/tests/src/test/java/com/example/AutofuzzConstructorExcludesTarget.java @@ -0,0 +1,39 @@ +/* + * 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; + +public class AutofuzzConstructorExcludesTarget { + public static class Argument { + private final boolean createdByExcludedConstructor; + + public Argument(int ignored) { + createdByExcludedConstructor = true; + } + + public Argument(String ignored) { + createdByExcludedConstructor = false; + } + } + + public static void target(Argument argument) { + if (argument != null && argument.createdByExcludedConstructor) { + throw new FuzzerSecurityIssueLow("Excluded constructor was used"); + } + } +}