Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateThenMap;
import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateThenMapToImmutable;
import static com.code_intelligence.jazzer.mutation.mutator.aggregate.BeanSupport.resolveAnnotatedParameterTypes;
import static com.code_intelligence.jazzer.mutation.mutator.aggregate.BeanSupport.resolveParameterTypes;
import static com.code_intelligence.jazzer.mutation.mutator.aggregate.BeanSupport.resolveReturnType;
import static com.code_intelligence.jazzer.mutation.support.PropertyConstraintSupport.propagatePropertyConstraints;
import static com.code_intelligence.jazzer.mutation.support.ReflectionSupport.unreflectMethod;
import static com.code_intelligence.jazzer.mutation.support.ReflectionSupport.unreflectMethods;
Expand Down Expand Up @@ -60,25 +63,22 @@ static Optional<SerializingMutator<?>> createMutator(
getters.length, instantiator));
for (int i = 0; i < getters.length; i++) {
Preconditions.check(
getters[i]
.getAnnotatedReturnType()
.getType()
.equals(instantiator.getAnnotatedParameterTypes()[i].getType()),
resolveReturnType(getters[i], initialType)
.equals(resolveParameterTypes(instantiator, initialType)[i]),
String.format(
"Parameter %d of %s does not match return type of %s", i, instantiator, getters[i]));
}

// TODO: Ideally, we would have the mutator framework pass in a Lookup for the fuzz test class.
MethodHandles.Lookup lookup = MethodHandles.lookup();
return createMutator(
factory,
instantiator.getDeclaringClass(),
instantiator.getAnnotatedParameterTypes(),
asInstantiationFunction(lookup, instantiator),
makeSingleGetter(unreflectMethods(lookup, getters)),
initialType,
isImmutable)
.map(m -> m);
factory,
instantiator.getDeclaringClass(),
resolveAnnotatedParameterTypes(instantiator, initialType),
asInstantiationFunction(lookup, instantiator),
makeSingleGetter(unreflectMethods(lookup, getters)),
initialType,
isImmutable);
}

static Optional<SerializingMutator<?>> createMutator(
Expand All @@ -94,25 +94,22 @@ static Optional<SerializingMutator<?>> createMutator(
getters.length, setters.length));
for (int i = 0; i < getters.length; i++) {
Preconditions.check(
getters[i]
.getAnnotatedReturnType()
.getType()
.equals(setters[i].getAnnotatedParameterTypes()[0].getType()),
resolveReturnType(getters[i], initialType)
.equals(resolveParameterTypes(setters[i], initialType)[0]),
String.format(
"Parameter of %s does not match return type of %s", setters[i], getters[i]));
}

// TODO: Ideally, we would have the mutator framework pass in a Lookup for the fuzz test class.
MethodHandles.Lookup lookup = MethodHandles.lookup();
return createMutator(
factory,
newInstance.getDeclaringClass(),
parameterTypes(setters),
asInstantiationFunction(lookup, newInstance, setters),
makeSingleGetter(unreflectMethods(lookup, getters)),
initialType,
/* isImmutable= */ false)
.map(m -> m);
factory,
newInstance.getDeclaringClass(),
parameterTypes(setters, initialType),
asInstantiationFunction(lookup, newInstance, setters),
makeSingleGetter(unreflectMethods(lookup, getters)),
initialType,
/* isImmutable= */ false);
}

@SuppressWarnings("Immutable")
Expand Down Expand Up @@ -221,9 +218,9 @@ private static <R> Function<Object[], R> asInstantiatorFunction(
}
}

static AnnotatedType[] parameterTypes(Method[] methods) {
static AnnotatedType[] parameterTypes(Method[] methods, AnnotatedType classType) {
return stream(methods)
.map(Method::getAnnotatedParameterTypes)
.map(m -> resolveAnnotatedParameterTypes(m, classType))
.flatMap(Arrays::stream)
.toArray(AnnotatedType[]::new);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@
import static java.util.stream.Collectors.toMap;

import java.beans.Introspector;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
Expand All @@ -49,6 +53,54 @@ static Optional<Class<?>> optionalClassForName(String targetClassName) {
}
}

// Returns the resolved type argument for a generic class if one exists.
// For example: For the class `class MyClass<T> {}` with annotated type `MyClass<String>`,
// calling `resolveTypeArgument(MyClass.class, annotatedType, "T")` returns
// `Optional.of(String.class)`.
private static Optional<AnnotatedType> resolveTypeArgument(
Class<?> clazz, AnnotatedType classType, String typeName) {
if (classType instanceof AnnotatedParameterizedType) {
TypeVariable<?>[] typeParameters = clazz.getTypeParameters();
AnnotatedType[] typeArguments =
((AnnotatedParameterizedType) classType).getAnnotatedActualTypeArguments();
for (int i = 0; i < typeParameters.length; i++) {
if (typeParameters[i].getName().equals(typeName)) {
return Optional.of(typeArguments[i]);
}
}
}
return Optional.empty();
}

// Returns the annotated parameter types of a method or constructor resolving all generic type
// arguments.
public static AnnotatedType[] resolveAnnotatedParameterTypes(
Executable e, AnnotatedType classType) {
Type[] generic = e.getGenericParameterTypes();
AnnotatedType[] annotated = e.getAnnotatedParameterTypes();
AnnotatedType[] result = new AnnotatedType[generic.length];
for (int i = 0; i < generic.length; i++) {
result[i] =
resolveTypeArgument(e.getDeclaringClass(), classType, generic[i].getTypeName())
.orElse(annotated[i]);
}
return result;
}

// Returns the parameter types of a method or constructor resolving all generic type arguments.
public static Type[] resolveParameterTypes(Executable e, AnnotatedType classType) {
return stream(resolveAnnotatedParameterTypes(e, classType))
.map(AnnotatedType::getType)
.toArray(Type[]::new);
}

static Type resolveReturnType(Method method, AnnotatedType classType) {
return resolveTypeArgument(
method.getDeclaringClass(), classType, method.getGenericReturnType().getTypeName())
.orElse(method.getAnnotatedReturnType())
.getType();
}

static boolean isConcreteClass(Class<?> clazz) {
return !Modifier.isAbstract(clazz.getModifiers());
}
Expand Down Expand Up @@ -88,9 +140,11 @@ static Optional<Method[]> findGettersByPropertyNames(
propertyNames.map(gettersByPropertyName::get).map(Optional::ofNullable), Method[]::new);
}

static Optional<Method[]> findGettersByPropertyTypes(Class<?> clazz, Stream<Class<?>> types) {
Map<Class<?>, List<Method>> gettersByType =
findMethods(clazz, BeanSupport::isGetter).collect(groupingBy(Method::getReturnType));
static Optional<Method[]> findGettersByPropertyTypes(
Class<?> clazz, AnnotatedType classType, Stream<Type> types) {
Map<Type, List<Method>> gettersByType =
findMethods(clazz, BeanSupport::isGetter)
.collect(groupingBy(m -> resolveReturnType(m, classType)));
return toArrayOrEmpty(
types.map(
type -> {
Expand Down Expand Up @@ -122,10 +176,10 @@ private static Optional<String> trimPrefix(String name, String prefix) {
}
}

static boolean matchingReturnTypes(Method[] methods, Type[] types) {
static boolean matchingReturnTypes(Method[] methods, AnnotatedType classType, Type[] types) {
for (int i = 0; i < methods.length; i++) {
// TODO: Support Optional<T> getters, which often have a corresponding T setter.
if (!methods[i].getAnnotatedReturnType().getType().equals(types[i])) {
if (!resolveReturnType(methods[i], classType).equals(types[i])) {
return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static com.code_intelligence.jazzer.mutation.mutator.aggregate.AggregatesHelper.asInstantiationFunction;
import static com.code_intelligence.jazzer.mutation.mutator.aggregate.BeanSupport.findConstructorsByParameterCount;
import static com.code_intelligence.jazzer.mutation.mutator.aggregate.BeanSupport.resolveAnnotatedParameterTypes;
import static com.code_intelligence.jazzer.mutation.support.StreamSupport.findFirstPresent;
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.asSubclassOrEmpty;

Expand Down Expand Up @@ -63,7 +64,7 @@ private static Optional<SerializingMutator<?>> buildMutator(
return AggregatesHelper.createMutator(
factory,
constructor.getDeclaringClass(),
constructor.getAnnotatedParameterTypes(),
resolveAnnotatedParameterTypes(constructor, initialType),
fromParametersToObject,
fromObjectToParameters,
initialType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static com.code_intelligence.jazzer.mutation.mutator.aggregate.BeanSupport.findGettersByPropertyNames;
import static com.code_intelligence.jazzer.mutation.mutator.aggregate.BeanSupport.findGettersByPropertyTypes;
import static com.code_intelligence.jazzer.mutation.mutator.aggregate.BeanSupport.matchingReturnTypes;
import static com.code_intelligence.jazzer.mutation.mutator.aggregate.BeanSupport.resolveParameterTypes;
import static com.code_intelligence.jazzer.mutation.support.StreamSupport.findFirstPresent;
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.asSubclassOrEmpty;
import static java.util.Arrays.stream;
Expand Down Expand Up @@ -50,11 +51,13 @@ public Optional<SerializingMutator<?>> tryCreate(
.filter(constructor -> constructor.getParameterCount() > 0)
.map(
constructor ->
findParameterGetters(clazz, constructor)
findParameterGetters(clazz, type, constructor)
.filter(
getters ->
matchingReturnTypes(
getters, constructor.getParameterTypes()))
getters,
type,
resolveParameterTypes(constructor, type)))
.flatMap(
getters -> {
// Try to create mutator based on constructor and getters,
Expand All @@ -65,7 +68,8 @@ public Optional<SerializingMutator<?>> tryCreate(
}))));
}

private Optional<Method[]> findParameterGetters(Class<?> clazz, Constructor<?> constructor) {
private Optional<Method[]> findParameterGetters(
Class<?> clazz, AnnotatedType type, Constructor<?> constructor) {
// Prefer explicit Java Bean ConstructorProperties annotation to determine parameter names.
ConstructorProperties parameterNames = constructor.getAnnotation(ConstructorProperties.class);
if (parameterNames != null
Expand All @@ -78,7 +82,8 @@ private Optional<Method[]> findParameterGetters(Class<?> clazz, Constructor<?> c
return findGettersByPropertyNames(clazz, stream(parameters).map(Parameter::getName));
} else {
// Last fallback to parameter types.
return findGettersByPropertyTypes(clazz, stream(parameters).map(Parameter::getType));
return findGettersByPropertyTypes(
clazz, type, stream(resolveParameterTypes(constructor, type)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ clazz, stream(setters).map(BeanSupport::toPropertyName))
getters ->
matchingReturnTypes(
getters,
type,
stream(setters)
.map(setter -> setter.getAnnotatedParameterTypes()[0].getType())
.map(setter -> resolveParameterTypes(setter, type)[0])
.toArray(Type[]::new)))
.flatMap(
getters ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public Optional<SerializingMutator<?>> tryCreate(
return AggregatesHelper.createMutator(
factory,
clazz,
parameterTypes(builderSetters),
parameterTypes(builderSetters, type),
fromParametersToObject,
fromObjectToParameters,
type,
Expand Down
Loading