blob: 543284f141db31f348f613f0151174d3fc2207b8 [file] [log] [blame]
// Copyright 2021 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.api.AutofuzzConstructionException;
import com.code_intelligence.jazzer.api.AutofuzzInvocationException;
import com.code_intelligence.jazzer.api.Consumer1;
import com.code_intelligence.jazzer.api.Consumer2;
import com.code_intelligence.jazzer.api.Consumer3;
import com.code_intelligence.jazzer.api.Consumer4;
import com.code_intelligence.jazzer.api.Consumer5;
import com.code_intelligence.jazzer.api.Function1;
import com.code_intelligence.jazzer.api.Function2;
import com.code_intelligence.jazzer.api.Function3;
import com.code_intelligence.jazzer.api.Function4;
import com.code_intelligence.jazzer.api.Function5;
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.runtime.HardToCatchError;
import com.code_intelligence.jazzer.utils.Utils;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import net.jodah.typetools.TypeResolver;
import net.jodah.typetools.TypeResolver.Unknown;
public class Meta {
public static final boolean IS_DEBUG = isDebug();
private static final Meta PUBLIC_LOOKUP_INSTANCE = new Meta(null);
private static final boolean IS_TEST = isTest();
private static final WeakHashMap<Class<?>, List<Class<?>>> implementingClassesCache =
new WeakHashMap<>();
private static final WeakHashMap<Class<?>, List<Class<?>>> nestedBuilderClassesCache =
new WeakHashMap<>();
private static final WeakHashMap<Class<?>, List<Method>> originalObjectCreationMethodsCache =
new WeakHashMap<>();
private static final WeakHashMap<Class<?>, List<Method>> cascadingBuilderMethodsCache =
new WeakHashMap<>();
private final AccessibleObjectLookup lookup;
public Meta(Class<?> referenceClass) {
lookup = new AccessibleObjectLookup(referenceClass);
}
@SuppressWarnings("unchecked")
public static <T1> void autofuzz(FuzzedDataProvider data, Consumer1<T1> func) {
Class<?>[] types = TypeResolver.resolveRawArguments(Consumer1.class, func.getClass());
func.accept((T1) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 0));
}
@SuppressWarnings("unchecked")
public static <T1, T2> void autofuzz(FuzzedDataProvider data, Consumer2<T1, T2> func) {
Class<?>[] types = TypeResolver.resolveRawArguments(Consumer2.class, func.getClass());
func.accept((T1) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 0),
(T2) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 1));
}
@SuppressWarnings("unchecked")
public static <T1, T2, T3> void autofuzz(FuzzedDataProvider data, Consumer3<T1, T2, T3> func) {
Class<?>[] types = TypeResolver.resolveRawArguments(Consumer3.class, func.getClass());
func.accept((T1) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 0),
(T2) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 1),
(T3) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 2));
}
@SuppressWarnings("unchecked")
public static <T1, T2, T3, T4> void autofuzz(
FuzzedDataProvider data, Consumer4<T1, T2, T3, T4> func) {
Class<?>[] types = TypeResolver.resolveRawArguments(Consumer4.class, func.getClass());
func.accept((T1) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 0),
(T2) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 1),
(T3) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 2),
(T4) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 3));
}
@SuppressWarnings("unchecked")
public static <T1, T2, T3, T4, T5> void autofuzz(
FuzzedDataProvider data, Consumer5<T1, T2, T3, T4, T5> func) {
Class<?>[] types = TypeResolver.resolveRawArguments(Consumer5.class, func.getClass());
func.accept((T1) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 0),
(T2) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 1),
(T3) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 2),
(T4) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 3),
(T5) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 4));
}
@SuppressWarnings("unchecked")
public static <T1, R> R autofuzz(FuzzedDataProvider data, Function1<T1, R> func) {
Class<?>[] types = TypeResolver.resolveRawArguments(Function1.class, func.getClass());
return func.apply((T1) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 0));
}
@SuppressWarnings("unchecked")
public static <T1, T2, R> R autofuzz(FuzzedDataProvider data, Function2<T1, T2, R> func) {
Class<?>[] types = TypeResolver.resolveRawArguments(Function2.class, func.getClass());
return func.apply((T1) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 0),
(T2) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 1));
}
@SuppressWarnings("unchecked")
public static <T1, T2, T3, R> R autofuzz(FuzzedDataProvider data, Function3<T1, T2, T3, R> func) {
Class<?>[] types = TypeResolver.resolveRawArguments(Function3.class, func.getClass());
return func.apply((T1) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 0),
(T2) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 1),
(T3) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 2));
}
@SuppressWarnings("unchecked")
public static <T1, T2, T3, T4, R> R autofuzz(
FuzzedDataProvider data, Function4<T1, T2, T3, T4, R> func) {
Class<?>[] types = TypeResolver.resolveRawArguments(Function4.class, func.getClass());
return func.apply((T1) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 0),
(T2) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 1),
(T3) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 2),
(T4) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 3));
}
@SuppressWarnings("unchecked")
public static <T1, T2, T3, T4, T5, R> R autofuzz(
FuzzedDataProvider data, Function5<T1, T2, T3, T4, T5, R> func) {
Class<?>[] types = TypeResolver.resolveRawArguments(Function5.class, func.getClass());
return func.apply((T1) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 0),
(T2) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 1),
(T3) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 2),
(T4) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 3),
(T5) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 4));
}
public static Object consume(FuzzedDataProvider data, Class<?> type) {
return PUBLIC_LOOKUP_INSTANCE.consume(data, type, null);
}
static void rescanClasspath() {
implementingClassesCache.clear();
}
private static boolean isTest() {
String value = System.getenv("JAZZER_AUTOFUZZ_TESTING");
return value != null && !value.isEmpty();
}
private static boolean isDebug() {
String value = System.getenv("JAZZER_AUTOFUZZ_DEBUG");
return value != null && !value.isEmpty();
}
private static int consumeArrayLength(FuzzedDataProvider data, int sizeOfElement) {
// Spend at most half of the fuzzer input bytes so that the remaining arguments that require
// construction still have non-trivial data to work with.
int bytesToSpend = data.remainingBytes() / 2;
return bytesToSpend / Math.max(sizeOfElement, 1);
}
private static String deepToString(Object obj) {
if (obj == null) {
return "null";
}
if (obj.getClass().isArray()) {
return String.format("(%s[]) %s", obj.getClass().getComponentType().getName(),
Arrays.deepToString((Object[]) obj));
}
return obj.toString();
}
private static String getDebugSummary(
Executable executable, Object thisObject, Object[] arguments) {
return String.format("%nMethod: %s::%s%s%nthis: %s%nArguments: %s",
executable.getDeclaringClass().getName(), executable.getName(),
Utils.getReadableDescriptor(executable), thisObject,
Arrays.stream(arguments).map(Meta::deepToString).collect(Collectors.joining(", ")));
}
static Class<?> getRawType(Type genericType) {
if (genericType instanceof Class<?>) {
return (Class<?>) genericType;
} else if (genericType instanceof ParameterizedType) {
return getRawType(((ParameterizedType) genericType).getRawType());
} else if (genericType instanceof WildcardType) {
// TODO: Improve this.
return Object.class;
} else if (genericType instanceof TypeVariable<?>) {
throw new AutofuzzError("Did not expect genericType to be a TypeVariable: " + genericType);
} else if (genericType instanceof GenericArrayType) {
return Array
.newInstance(getRawType(((GenericArrayType) genericType).getGenericComponentType()), 0)
.getClass();
} else {
throw new AutofuzzError("Got unexpected class implementing Type: " + genericType);
}
}
public Object autofuzz(FuzzedDataProvider data, Method method) {
return autofuzz(data, method, null);
}
// Renamed so that it doesn't clash with the static method consume, which we don't want to rename
// as the api package depends on it by name.
public Object consumeNonStatic(FuzzedDataProvider data, Class<?> type) {
return consume(data, type, null);
}
Object autofuzz(FuzzedDataProvider data, Method method, AutofuzzCodegenVisitor visitor) {
Object result;
if (Modifier.isStatic(method.getModifiers())) {
if (visitor != null) {
// This group will always have two elements: The class name and the method call.
visitor.pushGroup(
String.format("%s.", method.getDeclaringClass().getCanonicalName()), "", "");
}
try {
result = autofuzz(data, method, null, visitor);
} finally {
if (visitor != null) {
visitor.popGroup();
}
}
} else {
if (visitor != null) {
// This group will always have two elements: The thisObject and the method call.
// Since the this object can be a complex expression, wrap it in parenthesis.
visitor.pushGroup("(", ").", "");
}
try {
Object thisObject = consume(data, method.getDeclaringClass(), visitor);
if (thisObject == null) {
throw new AutofuzzConstructionException();
}
result = autofuzz(data, method, thisObject, visitor);
} finally {
if (visitor != null) {
visitor.popGroup();
}
}
}
return result;
}
public Object autofuzz(FuzzedDataProvider data, Method method, Object thisObject) {
return autofuzz(data, method, thisObject, null);
}
Object autofuzz(
FuzzedDataProvider data, Method method, Object thisObject, AutofuzzCodegenVisitor visitor) {
if (visitor != null) {
visitor.pushGroup(String.format("%s(", method.getName()), ", ", ")");
}
Object[] arguments = consumeArguments(data, method, visitor);
if (visitor != null) {
visitor.popGroup();
}
try {
return method.invoke(thisObject, arguments);
} catch (IllegalAccessException | IllegalArgumentException | NullPointerException e) {
// We should ensure that the arguments fed into the method are always valid.
throw new AutofuzzError(getDebugSummary(method, thisObject, arguments), e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof HardToCatchError) {
throw new AutofuzzInvocationException();
}
throw new AutofuzzInvocationException(e.getCause());
}
}
Object autofuzzForConsume(
FuzzedDataProvider data, Constructor<?> constructor, AutofuzzCodegenVisitor visitor) {
try {
return autofuzz(data, constructor, visitor);
} catch (AutofuzzConstructionException e) {
// Do not nest AutofuzzConstructionExceptions.
throw e;
} catch (AutofuzzInvocationException e) {
// If an invocation fails during consume and thus while trying to construct a valid object,
// the exception should not be reported as a finding, so we rewrap it.
throw new AutofuzzConstructionException(e.getCause());
} catch (Throwable t) {
throw new AutofuzzConstructionException(t);
}
}
Object autofuzzForConsume(
FuzzedDataProvider data, Method method, Object thisObject, AutofuzzCodegenVisitor visitor) {
try {
return autofuzz(data, method, thisObject, visitor);
} catch (AutofuzzConstructionException e) {
// Do not nest AutofuzzConstructionExceptions.
throw e;
} catch (AutofuzzInvocationException e) {
// If an invocation fails during consume and thus while trying to construct a valid object,
// the exception should not be reported as a finding, so we rewrap it.
throw new AutofuzzConstructionException(e.getCause());
} catch (Throwable t) {
throw new AutofuzzConstructionException(t);
}
}
public <R> R autofuzz(FuzzedDataProvider data, Constructor<R> constructor) {
return autofuzz(data, constructor, null);
}
<R> R autofuzz(
FuzzedDataProvider data, Constructor<R> constructor, AutofuzzCodegenVisitor visitor) {
if (visitor != null) {
// getCanonicalName is correct also for nested classes.
visitor.pushGroup(
String.format("new %s(", constructor.getDeclaringClass().getCanonicalName()), ", ", ")");
}
Object[] arguments = consumeArguments(data, constructor, visitor);
if (visitor != null) {
visitor.popGroup();
}
try {
return constructor.newInstance(arguments);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) {
// This should never be reached as the logic in consume should prevent us from e.g. calling
// constructors of abstract classes or private constructors.
throw new AutofuzzError(getDebugSummary(constructor, null, arguments), e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof HardToCatchError) {
throw new AutofuzzInvocationException();
}
throw new AutofuzzInvocationException(e.getCause());
}
}
// Invariant: The Java source code representation of the returned object visited by visitor must
// represent an object of the same type as genericType. For example, a null value returned for
// the genericType Class<java.lang.String> should lead to the generated code
// "(java.lang.String) null", not just "null". This makes it possible to safely use consume in
// recursive argument constructions.
// Exception: Some Java libraries offer public methods that take private interfaces or abstract
// classes as parameters. In this case, a cast to the parent type would cause an
// IllegalAccessError. Since this case should be rare and there is no good alternative to
// disambiguate overloads, we omit the cast in this case.
Object consume(FuzzedDataProvider data, Type genericType, AutofuzzCodegenVisitor visitor) {
Class<?> type = getRawType(genericType);
if (type == byte.class || type == Byte.class) {
byte result = data.consumeByte();
if (visitor != null) {
visitor.pushElement(String.format("(byte) %s", result));
}
return result;
} else if (type == short.class || type == Short.class) {
short result = data.consumeShort();
if (visitor != null) {
visitor.pushElement(String.format("(short) %s", result));
}
return result;
} else if (type == int.class || type == Integer.class) {
int result = data.consumeInt();
if (visitor != null) {
visitor.pushElement(Integer.toString(result));
}
return result;
} else if (type == long.class || type == Long.class) {
long result = data.consumeLong();
if (visitor != null) {
visitor.pushElement(String.format("%sL", result));
}
return result;
} else if (type == float.class || type == Float.class) {
float result = data.consumeFloat();
if (visitor != null) {
visitor.pushElement(String.format("%sF", result));
}
return result;
} else if (type == double.class || type == Double.class) {
double result = data.consumeDouble();
if (visitor != null) {
visitor.pushElement(Double.toString(result));
}
return result;
} else if (type == boolean.class || type == Boolean.class) {
boolean result = data.consumeBoolean();
if (visitor != null) {
visitor.pushElement(Boolean.toString(result));
}
return result;
} else if (type == char.class || type == Character.class) {
char result = data.consumeChar();
if (visitor != null) {
visitor.addCharLiteral(result);
}
return result;
}
// Sometimes, but rarely return null for non-primitive and non-boxed types.
// TODO: We might want to return null for boxed types sometimes, but this is complicated by the
// fact that TypeUtils can't distinguish between a primitive type and its wrapper and may
// thus easily cause false-positive NullPointerExceptions.
if (!type.isPrimitive() && data.consumeByte() == 0) {
if (visitor != null) {
if (type == Object.class) {
visitor.pushElement("null");
} else {
visitor.pushElement(String.format("(%s) null", type.getCanonicalName()));
}
}
return null;
}
if (type == String.class || type == CharSequence.class) {
String result = data.consumeString(consumeArrayLength(data, 1));
if (visitor != null) {
visitor.addStringLiteral(result);
}
return result;
} else if (type.isArray()) {
if (type == byte[].class) {
byte[] result = data.consumeBytes(consumeArrayLength(data, Byte.BYTES));
if (visitor != null) {
visitor.pushElement(IntStream.range(0, result.length)
.mapToObj(i -> "(byte) " + result[i])
.collect(Collectors.joining(", ", "new byte[]{", "}")));
}
return result;
} else if (type == int[].class) {
int[] result = data.consumeInts(consumeArrayLength(data, Integer.BYTES));
if (visitor != null) {
visitor.pushElement(Arrays.stream(result)
.mapToObj(String::valueOf)
.collect(Collectors.joining(", ", "new int[]{", "}")));
}
return result;
} else if (type == short[].class) {
short[] result = data.consumeShorts(consumeArrayLength(data, Short.BYTES));
if (visitor != null) {
visitor.pushElement(IntStream.range(0, result.length)
.mapToObj(i -> "(short) " + result[i])
.collect(Collectors.joining(", ", "new short[]{", "}")));
}
return result;
} else if (type == long[].class) {
long[] result = data.consumeLongs(consumeArrayLength(data, Long.BYTES));
if (visitor != null) {
visitor.pushElement(Arrays.stream(result)
.mapToObj(e -> e + "L")
.collect(Collectors.joining(", ", "new long[]{", "}")));
}
return result;
} else if (type == boolean[].class) {
boolean[] result = data.consumeBooleans(consumeArrayLength(data, 1));
if (visitor != null) {
visitor.pushElement(
Arrays.toString(result).replace(']', '}').replace("[", "new boolean[]{"));
}
return result;
} else {
if (visitor != null) {
visitor.pushGroup(
String.format("new %s[]{", type.getComponentType().getName()), ", ", "}");
}
int remainingBytesBeforeFirstElementCreation = data.remainingBytes();
Object firstElement = consume(data, type.getComponentType(), visitor);
int remainingBytesAfterFirstElementCreation = data.remainingBytes();
int sizeOfElementEstimate =
remainingBytesBeforeFirstElementCreation - remainingBytesAfterFirstElementCreation;
Object array = Array.newInstance(
type.getComponentType(), consumeArrayLength(data, sizeOfElementEstimate));
for (int i = 0; i < Array.getLength(array); i++) {
if (i == 0) {
Array.set(array, i, firstElement);
} else {
Array.set(array, i, consume(data, type.getComponentType(), visitor));
}
}
if (visitor != null) {
if (Array.getLength(array) == 0) {
// We implicitly pushed the first element with the call to consume above, but it is not
// part of the array.
visitor.popElement();
}
visitor.popGroup();
}
return array;
}
} else if (type == ByteArrayInputStream.class || type == InputStream.class) {
byte[] array = data.consumeBytes(consumeArrayLength(data, Byte.BYTES));
if (visitor != null) {
visitor.pushElement(IntStream.range(0, array.length)
.mapToObj(i -> "(byte) " + array[i])
.collect(Collectors.joining(
", ", "new java.io.ByteArrayInputStream(new byte[]{", "})")));
}
return new ByteArrayInputStream(array);
} else if (type == Map.class) {
ParameterizedType mapType = (ParameterizedType) genericType;
if (mapType.getActualTypeArguments().length != 2) {
throw new AutofuzzError(
"Expected Map generic type to have two type parameters: " + mapType);
}
Type keyType = mapType.getActualTypeArguments()[0];
Type valueType = mapType.getActualTypeArguments()[1];
if (visitor != null) {
// Do not use Collectors.toMap() since it cannot handle null values.
// Also annotate the type of the entry stream since it might be empty, in which case type
// inference on the accumulator could fail.
visitor.pushGroup(
String.format("java.util.stream.Stream.<java.util.AbstractMap.SimpleEntry<%s, %s>>of(",
keyType.getTypeName(), valueType.getTypeName()),
", ",
").collect(java.util.HashMap::new, (map, e) -> map.put(e.getKey(), e.getValue()), java.util.HashMap::putAll)");
}
int remainingBytesBeforeFirstEntryCreation = data.remainingBytes();
if (visitor != null) {
visitor.pushGroup("new java.util.AbstractMap.SimpleEntry<>(", ", ", ")");
}
Object firstKey = consume(data, keyType, visitor);
Object firstValue = consume(data, valueType, visitor);
if (visitor != null) {
visitor.popGroup();
}
int remainingBytesAfterFirstEntryCreation = data.remainingBytes();
int sizeOfElementEstimate =
remainingBytesBeforeFirstEntryCreation - remainingBytesAfterFirstEntryCreation;
int mapSize = consumeArrayLength(data, sizeOfElementEstimate);
Map<Object, Object> map = new HashMap<>(mapSize);
for (int i = 0; i < mapSize; i++) {
if (i == 0) {
map.put(firstKey, firstValue);
} else {
if (visitor != null) {
visitor.pushGroup("new java.util.AbstractMap.SimpleEntry<>(", ", ", ")");
}
map.put(consume(data, keyType, visitor), consume(data, valueType, visitor));
if (visitor != null) {
visitor.popGroup();
}
}
}
if (visitor != null) {
if (mapSize == 0) {
// We implicitly pushed the first entry with the call to consume above, but it is not
// part of the array.
visitor.popElement();
}
visitor.popGroup();
}
return map;
} else if (type.isEnum()) {
Enum<?> enumValue = (Enum<?>) data.pickValue(type.getEnumConstants());
if (visitor != null) {
visitor.pushElement(String.format("%s.%s", type.getName(), enumValue.name()));
}
return enumValue;
} else if (type == Class.class) {
if (visitor != null) {
visitor.pushElement(String.format("%s.class", YourAverageJavaClass.class.getName()));
}
return YourAverageJavaClass.class;
} else if (type == Method.class) {
if (visitor != null) {
throw new AutofuzzError("codegen has not been implemented for Method.class");
}
return data.pickValue(lookup.getAccessibleMethods(YourAverageJavaClass.class));
} else if (type == Constructor.class) {
if (visitor != null) {
throw new AutofuzzError("codegen has not been implemented for Constructor.class");
}
return data.pickValue(lookup.getAccessibleConstructors(YourAverageJavaClass.class));
} else if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) {
List<Class<?>> implementingClasses = implementingClassesCache.get(type);
if (implementingClasses == null) {
// TODO: We may be scanning multiple times. Instead, we should keep the ScanResult around
// for as long as there is enough memory.
ClassGraph classGraph = new ClassGraph()
.enableClassInfo()
.ignoreClassVisibility()
.ignoreMethodVisibility()
.enableInterClassDependencies()
.rejectPackages("jaz");
if (!IS_TEST) {
classGraph.rejectPackages("com.code_intelligence.jazzer");
}
try (ScanResult result = classGraph.scan()) {
ClassInfoList children =
type.isInterface() ? result.getClassesImplementing(type) : result.getSubclasses(type);
implementingClasses = children.getStandardClasses()
.filter(info -> !Modifier.isAbstract(info.getModifiers()))
.filter(info -> lookup.isAccessible(info, info.getModifiers()))
// Filter out anonymous and local classes, which can't be
// instantiated in reproducers.
.filter(info -> info.getName() != null)
.loadClasses();
implementingClassesCache.put(type, implementingClasses);
}
}
if (implementingClasses.isEmpty()) {
if (IS_DEBUG) {
throw new AutofuzzConstructionException(String.format(
"Could not find classes implementing %s on the classpath", type.getName()));
} else {
throw new AutofuzzConstructionException();
}
}
if (visitor != null) {
// See the "Exception" note in the method comment.
if (Modifier.isPublic(type.getModifiers())) {
// This group will always have a single element: The instance of the implementing class.
visitor.pushGroup(String.format("(%s) ", type.getCanonicalName()), "", "");
}
}
Object result = consume(data, data.pickValue(implementingClasses), visitor);
if (visitor != null) {
if (Modifier.isPublic(type.getModifiers())) {
visitor.popGroup();
}
}
return result;
}
Constructor<?>[] constructors = lookup.getAccessibleConstructors(type);
if (constructors.length > 0) {
Constructor<?> constructor = data.pickValue(constructors);
boolean applySetters = constructor.getParameterCount() == 0;
if (visitor != null && applySetters) {
// Embed the instance creation and setters into an immediately invoked lambda expression to
// turn them into an expression.
String uniqueVariableName = visitor.uniqueVariableName();
visitor.pushGroup(String.format("((java.util.function.Supplier<%1$s>) (() -> {%1$s %2$s = ",
type.getCanonicalName(), uniqueVariableName),
String.format("; %s.", uniqueVariableName),
String.format("; return %s;})).get()", uniqueVariableName));
}
Object obj = autofuzzForConsume(data, constructor, visitor);
if (applySetters) {
List<Method> potentialSetters = getPotentialSetters(type);
if (!potentialSetters.isEmpty()) {
List<Method> pickedSetters =
data.pickValues(potentialSetters, data.consumeInt(0, potentialSetters.size()));
for (Method setter : pickedSetters) {
autofuzzForConsume(data, setter, obj, visitor);
}
}
if (visitor != null) {
visitor.popGroup();
}
}
return obj;
}
// We are out of more or less canonical ways to construct an instance of this class and have to
// resort to more heuristic approaches.
// First, try to find nested classes with names ending in Builder and call a subset of their
// chaining methods.
List<Class<?>> nestedBuilderClasses = getNestedBuilderClasses(type);
if (!nestedBuilderClasses.isEmpty()) {
Class<?> pickedBuilder = data.pickValue(nestedBuilderClasses);
List<Method> cascadingBuilderMethods = getCascadingBuilderMethods(pickedBuilder);
List<Method> originalObjectCreationMethods = getOriginalObjectCreationMethods(pickedBuilder);
int pickedMethodsNumber = data.consumeInt(0, cascadingBuilderMethods.size());
List<Method> pickedMethods = data.pickValues(cascadingBuilderMethods, pickedMethodsNumber);
Method builderMethod = data.pickValue(originalObjectCreationMethods);
if (visitor != null) {
// Group for the chain of builder methods.
visitor.pushGroup("", ".", "");
}
Object builderObj = autofuzzForConsume(
data, data.pickValue(lookup.getAccessibleConstructors(pickedBuilder)), visitor);
for (Method method : pickedMethods) {
builderObj = autofuzzForConsume(data, method, builderObj, visitor);
}
try {
Object obj = autofuzzForConsume(data, builderMethod, builderObj, visitor);
if (visitor != null) {
visitor.popGroup();
}
return obj;
} catch (Exception e) {
throw new AutofuzzConstructionException(e);
}
}
// We ran out of ways to construct an instance of the requested type. If in debug mode, report
// more detailed information.
if (IS_DEBUG) {
String summary = String.format(
"Failed to generate instance of %s:%nAccessible constructors: %s%nNested subclasses: %s%n",
type.getName(),
Arrays.stream(lookup.getAccessibleConstructors(type))
.map(Utils::getReadableDescriptor)
.collect(Collectors.joining(", ")),
Arrays.stream(lookup.getAccessibleClasses(type))
.map(Class::getName)
.collect(Collectors.joining(", ")));
throw new AutofuzzConstructionException(summary);
} else {
throw new AutofuzzConstructionException();
}
}
private List<Class<?>> getNestedBuilderClasses(Class<?> type) {
List<Class<?>> nestedBuilderClasses = nestedBuilderClassesCache.get(type);
if (nestedBuilderClasses == null) {
nestedBuilderClasses = Arrays.stream(lookup.getAccessibleClasses(type))
.filter(cls -> cls.getName().endsWith("Builder"))
.filter(cls -> !getOriginalObjectCreationMethods(cls).isEmpty())
.collect(Collectors.toList());
nestedBuilderClassesCache.put(type, nestedBuilderClasses);
}
return nestedBuilderClasses;
}
private List<Method> getOriginalObjectCreationMethods(Class<?> builder) {
List<Method> originalObjectCreationMethods = originalObjectCreationMethodsCache.get(builder);
if (originalObjectCreationMethods == null) {
originalObjectCreationMethods =
Arrays.stream(lookup.getAccessibleMethods(builder))
.filter(m -> m.getReturnType() == builder.getEnclosingClass())
.collect(Collectors.toList());
originalObjectCreationMethodsCache.put(builder, originalObjectCreationMethods);
}
return originalObjectCreationMethods;
}
private List<Method> getCascadingBuilderMethods(Class<?> builder) {
List<Method> cascadingBuilderMethods = cascadingBuilderMethodsCache.get(builder);
if (cascadingBuilderMethods == null) {
cascadingBuilderMethods = Arrays.stream(lookup.getAccessibleMethods(builder))
.filter(m -> m.getReturnType() == builder)
.collect(Collectors.toList());
cascadingBuilderMethodsCache.put(builder, cascadingBuilderMethods);
}
return cascadingBuilderMethods;
}
private List<Method> getPotentialSetters(Class<?> type) {
return Arrays.stream(lookup.getAccessibleMethods(type))
.filter(method -> void.class.equals(method.getReturnType()))
.filter(method -> method.getParameterCount() == 1)
.filter(method -> method.getName().startsWith("set"))
.collect(Collectors.toList());
}
public Object[] consumeArguments(
FuzzedDataProvider data, Executable executable, AutofuzzCodegenVisitor visitor) {
Object[] result;
try {
result = Arrays.stream(executable.getGenericParameterTypes())
.map(type -> consume(data, type, visitor))
.toArray();
return result;
} catch (AutofuzzConstructionException e) {
// Do not nest AutofuzzConstructionExceptions.
throw e;
} catch (AutofuzzInvocationException e) {
// If an invocation fails while creating the arguments for another invocation, the exception
// should not be reported, so we rewrap it.
throw new AutofuzzConstructionException(e.getCause());
} catch (Throwable t) {
throw new AutofuzzConstructionException(t);
}
}
private Object consumeChecked(FuzzedDataProvider data, Class<?>[] types, int i) {
if (types[i] == Unknown.class) {
throw new AutofuzzError("Failed to determine type of argument " + (i + 1));
}
Object result;
try {
result = consumeNonStatic(data, types[i]);
} catch (AutofuzzConstructionException e) {
// Do not nest AutofuzzConstructionExceptions.
throw e;
} catch (AutofuzzInvocationException e) {
// If an invocation fails while creating the arguments for another invocation, the exception
// should not be reported, so we rewrap it.
throw new AutofuzzConstructionException(e.getCause());
} catch (Throwable t) {
throw new AutofuzzConstructionException(t);
}
if (result != null && !types[i].isAssignableFrom(result.getClass())) {
throw new AutofuzzError("consume returned " + result.getClass() + ", but need " + types[i]);
}
return result;
}
}