// 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, List>> implementingClassesCache = new WeakHashMap<>(); private static final WeakHashMap, List>> nestedBuilderClassesCache = new WeakHashMap<>(); private static final WeakHashMap, List> originalObjectCreationMethodsCache = new WeakHashMap<>(); private static final WeakHashMap, List> cascadingBuilderMethodsCache = new WeakHashMap<>(); private final AccessibleObjectLookup lookup; public Meta(Class referenceClass) { lookup = new AccessibleObjectLookup(referenceClass); } @SuppressWarnings("unchecked") public static void autofuzz(FuzzedDataProvider data, Consumer1 func) { Class[] types = TypeResolver.resolveRawArguments(Consumer1.class, func.getClass()); func.accept((T1) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 0)); } @SuppressWarnings("unchecked") public static void autofuzz(FuzzedDataProvider data, Consumer2 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 void autofuzz(FuzzedDataProvider data, Consumer3 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 void autofuzz( FuzzedDataProvider data, Consumer4 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 void autofuzz( FuzzedDataProvider data, Consumer5 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 R autofuzz(FuzzedDataProvider data, Function1 func) { Class[] types = TypeResolver.resolveRawArguments(Function1.class, func.getClass()); return func.apply((T1) PUBLIC_LOOKUP_INSTANCE.consumeChecked(data, types, 0)); } @SuppressWarnings("unchecked") public static R autofuzz(FuzzedDataProvider data, Function2 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 R autofuzz(FuzzedDataProvider data, Function3 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 R autofuzz( FuzzedDataProvider data, Function4 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 R autofuzz( FuzzedDataProvider data, Function5 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 autofuzz(FuzzedDataProvider data, Constructor constructor) { return autofuzz(data, constructor, null); } R autofuzz( FuzzedDataProvider data, Constructor 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 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.>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 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> 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 potentialSetters = getPotentialSetters(type); if (!potentialSetters.isEmpty()) { List 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> nestedBuilderClasses = getNestedBuilderClasses(type); if (!nestedBuilderClasses.isEmpty()) { Class pickedBuilder = data.pickValue(nestedBuilderClasses); List cascadingBuilderMethods = getCascadingBuilderMethods(pickedBuilder); List originalObjectCreationMethods = getOriginalObjectCreationMethods(pickedBuilder); int pickedMethodsNumber = data.consumeInt(0, cascadingBuilderMethods.size()); List 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> getNestedBuilderClasses(Class type) { List> 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 getOriginalObjectCreationMethods(Class builder) { List 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 getCascadingBuilderMethods(Class builder) { List 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 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; } }