Skip to content

Commit 3b402e9

Browse files
committed
feat: mutator support for generic superclasses
This commit adds mutator support for classes that have generic superclasses. The inheritance chain is walked up collecting all type parameters. This is also done for all interfaces.
1 parent 4674efb commit 3b402e9

File tree

4 files changed

+157
-17
lines changed

4 files changed

+157
-17
lines changed

src/main/java/com/code_intelligence/jazzer/mutation/mutator/aggregate/BeanSupport.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
import java.lang.reflect.AnnotatedType;
3131
import java.lang.reflect.Constructor;
3232
import java.lang.reflect.Executable;
33+
import java.lang.reflect.GenericArrayType;
3334
import java.lang.reflect.Method;
3435
import java.lang.reflect.Modifier;
36+
import java.lang.reflect.ParameterizedType;
3537
import java.lang.reflect.Type;
3638
import java.util.Arrays;
3739
import java.util.Comparator;
@@ -52,17 +54,27 @@ static Optional<Class<?>> optionalClassForName(String targetClassName) {
5254
}
5355
}
5456

57+
private static Class<?> rawType(Type classType) {
58+
if (classType instanceof Class<?>) {
59+
return (Class<?>) classType;
60+
} else if (classType instanceof ParameterizedType) {
61+
return rawType(((ParameterizedType) classType).getRawType());
62+
} else if (classType instanceof GenericArrayType) {
63+
return rawType(((GenericArrayType) classType).getGenericComponentType());
64+
} else {
65+
// Bail out on wildcard types or type variables.
66+
throw new UnsupportedOperationException("Unsupported type: " + classType);
67+
}
68+
}
69+
5570
// Returns the annotated parameter types of a method or constructor resolving all generic type
5671
// arguments.
5772
public static AnnotatedType[] resolveAnnotatedParameterTypes(
5873
Executable e, AnnotatedType classType) {
59-
Type[] generic = e.getGenericParameterTypes();
60-
AnnotatedType[] annotated = e.getAnnotatedParameterTypes();
61-
AnnotatedType[] result = new AnnotatedType[generic.length];
62-
for (int i = 0; i < generic.length; i++) {
63-
result[i] = resolveTypeArguments(e.getDeclaringClass(), classType, annotated[i]);
64-
}
65-
return result;
74+
Class<?> clazz = rawType(classType.getType());
75+
return stream(e.getAnnotatedParameterTypes())
76+
.map(t -> resolveTypeArguments(clazz, classType, t))
77+
.toArray(AnnotatedType[]::new);
6678
}
6779

6880
// Returns the parameter types of a method or constructor resolving all generic type arguments.
@@ -74,7 +86,7 @@ public static Type[] resolveParameterTypes(Executable e, AnnotatedType classType
7486

7587
static Type resolveReturnType(Method method, AnnotatedType classType) {
7688
return resolveTypeArguments(
77-
method.getDeclaringClass(), classType, method.getAnnotatedReturnType())
89+
rawType(classType.getType()), classType, method.getAnnotatedReturnType())
7890
.getType();
7991
}
8092

src/main/java/com/code_intelligence/jazzer/mutation/support/ParameterizedTypeSupport.java

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,50 @@ public class ParameterizedTypeSupport {
6464
*/
6565
public static AnnotatedType resolveTypeArguments(
6666
Class<?> clazz, AnnotatedType classType, AnnotatedType type) {
67-
if (!(classType instanceof AnnotatedParameterizedType)) {
67+
Map<TypeVariable<?>, AnnotatedType> mapping = new HashMap<>();
68+
collectTypeMappings(clazz, classType, mapping);
69+
if (mapping.isEmpty()) {
6870
return type;
6971
}
72+
return resolveRecursive(type.getType(), type, mapping);
73+
}
7074

71-
TypeVariable<?>[] typeParameters = clazz.getTypeParameters();
72-
AnnotatedType[] typeArguments =
73-
((AnnotatedParameterizedType) classType).getAnnotatedActualTypeArguments();
75+
private static void collectTypeMappings(
76+
Class<?> clazz,
77+
AnnotatedType annotatedClazzType,
78+
Map<TypeVariable<?>, AnnotatedType> mapping) {
79+
if (annotatedClazzType instanceof AnnotatedParameterizedType) {
80+
TypeVariable<?>[] typeParameters = clazz.getTypeParameters();
81+
AnnotatedType[] typeArguments =
82+
((AnnotatedParameterizedType) annotatedClazzType).getAnnotatedActualTypeArguments();
83+
require(typeArguments.length == typeParameters.length);
84+
for (int i = 0; i < typeParameters.length; i++) {
85+
mapping.put(typeParameters[i], typeArguments[i]);
86+
}
87+
}
7488

75-
require(typeArguments.length == typeParameters.length);
89+
Class<?> superClass = clazz.getSuperclass();
90+
AnnotatedType annotatedSuperclass = clazz.getAnnotatedSuperclass();
91+
Type genericSuperclass = clazz.getGenericSuperclass();
92+
if (superClass != null && annotatedSuperclass != null && genericSuperclass != null) {
93+
AnnotatedType resolvedSuperclass =
94+
resolveRecursive(genericSuperclass, annotatedSuperclass, mapping);
95+
collectTypeMappings(superClass, resolvedSuperclass, mapping);
96+
}
7697

77-
Map<TypeVariable<?>, AnnotatedType> mapping = new HashMap<>();
78-
for (int i = 0; i < typeParameters.length; i++) {
79-
mapping.put(typeParameters[i], typeArguments[i]);
98+
Class<?>[] interfaces = clazz.getInterfaces();
99+
AnnotatedType[] annotatedInterfaces = clazz.getAnnotatedInterfaces();
100+
Type[] genericInterfaces = clazz.getGenericInterfaces();
101+
for (int i = 0; i < interfaces.length; i++) {
102+
AnnotatedType annotatedInterface = annotatedInterfaces[i];
103+
Type genericInterface = genericInterfaces[i];
104+
if (annotatedInterface == null || genericInterface == null) {
105+
continue;
106+
}
107+
AnnotatedType resolvedInterface =
108+
resolveRecursive(genericInterface, annotatedInterface, mapping);
109+
collectTypeMappings(interfaces[i], resolvedInterface, mapping);
80110
}
81-
return resolveRecursive(type.getType(), type, mapping);
82111
}
83112

84113
/**
@@ -107,6 +136,10 @@ private static AnnotatedType resolveRecursive(
107136
if (replacement == null) {
108137
return annotated;
109138
}
139+
if (replacement instanceof AnnotatedWildcardType) {
140+
// Forwarding annotations to wildcard types is not supported
141+
return replacement;
142+
}
110143
return TypeSupport.forwardAnnotations(annotated, replacement);
111144
}
112145
return annotated;

src/test/java/com/code_intelligence/jazzer/mutation/mutator/aggregate/SetterBasedBeanMutatorTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,14 @@ void genericClass() {
246246
.createOrThrow(new TypeHolder<Generic<String>>() {}.annotatedType());
247247
assertThat(mutator.toString()).isEqualTo("Nullable<[Nullable<Nullable<String>[]>] -> Generic>");
248248
}
249+
250+
public static class Child extends Generic<String> {}
251+
252+
@Test
253+
void genericClassChild() {
254+
SerializingMutator<Child> mutator =
255+
(SerializingMutator<Child>)
256+
Mutators.newFactory().createOrThrow(new TypeHolder<Child>() {}.annotatedType());
257+
assertThat(mutator.toString()).isEqualTo("Nullable<[Nullable<Nullable<String>[]>] -> Child>");
258+
}
249259
}

src/test/java/com/code_intelligence/jazzer/mutation/support/ParameterizedTypeSupportTest.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,89 @@ class Generic<S, T> {
125125
assertThat(parameterizedType.getActualTypeArguments()[0]).isEqualTo(String.class);
126126
assertThat(parameterizedType.getActualTypeArguments()[1]).isEqualTo(Integer.class);
127127
}
128+
129+
@Test
130+
void resolveParameterizedTypeChildClass() throws NoSuchFieldException {
131+
class Base<T, U> {
132+
public Map<T, U> field;
133+
}
134+
class Child<U> extends Base<String, U> {}
135+
AnnotatedType annotatedType = Child.class.getField("field").getAnnotatedType();
136+
AnnotatedParameterizedType classType =
137+
(AnnotatedParameterizedType) new TypeHolder<Child<Integer>>() {}.annotatedType();
138+
AnnotatedType resolved =
139+
ParameterizedTypeSupport.resolveTypeArguments(Child.class, classType, annotatedType);
140+
141+
assertThat(resolved).isInstanceOf(AnnotatedParameterizedType.class);
142+
143+
AnnotatedParameterizedType parameterType = (AnnotatedParameterizedType) resolved;
144+
assertThat(((ParameterizedType) parameterType.getType()).getRawType()).isEqualTo(Map.class);
145+
AnnotatedType[] elementTypes = parameterType.getAnnotatedActualTypeArguments();
146+
assertThat(elementTypes).hasLength(2);
147+
assertThat(elementTypes[0].getType()).isEqualTo(String.class);
148+
assertThat(
149+
TypeSupport.annotatedTypeEquals(
150+
classType.getAnnotatedActualTypeArguments()[0], elementTypes[1]))
151+
.isTrue();
152+
}
153+
154+
@Test
155+
void resolveParameterizedType_multiLevelHierarchy() throws NoSuchFieldException {
156+
class Root<T> {
157+
public List<T> field;
158+
}
159+
class Middle<U> extends Root<List<U>> {}
160+
class Leaf<V> extends Middle<V> {}
161+
class Concrete extends Leaf<String> {}
162+
163+
AnnotatedType annotatedType = Concrete.class.getField("field").getAnnotatedType();
164+
AnnotatedType classType = new TypeHolder<Concrete>() {}.annotatedType();
165+
AnnotatedType resolved =
166+
ParameterizedTypeSupport.resolveTypeArguments(Concrete.class, classType, annotatedType);
167+
168+
assertThat(resolved).isInstanceOf(AnnotatedParameterizedType.class);
169+
170+
AnnotatedParameterizedType outerList = (AnnotatedParameterizedType) resolved;
171+
assertThat(((ParameterizedType) outerList.getType()).getRawType()).isEqualTo(List.class);
172+
AnnotatedType nestedListType = outerList.getAnnotatedActualTypeArguments()[0];
173+
assertThat(nestedListType).isInstanceOf(AnnotatedParameterizedType.class);
174+
175+
AnnotatedParameterizedType innerList = (AnnotatedParameterizedType) nestedListType;
176+
assertThat(((ParameterizedType) innerList.getType()).getRawType()).isEqualTo(List.class);
177+
AnnotatedType innerElement = innerList.getAnnotatedActualTypeArguments()[0];
178+
assertThat(innerElement.getType()).isEqualTo(String.class);
179+
}
180+
181+
private interface LocalSupplier<T> {
182+
List<T> supply();
183+
}
184+
185+
private interface AnnotatedSupplier<U> extends LocalSupplier<List<U>> {}
186+
187+
@Test
188+
void resolveParameterizedType_interfaceHierarchy() throws NoSuchMethodException {
189+
AnnotatedType annotatedType = LocalSupplier.class.getMethod("supply").getAnnotatedReturnType();
190+
AnnotatedParameterizedType interfaceType =
191+
(AnnotatedParameterizedType)
192+
new TypeHolder<AnnotatedSupplier<@NotNull String>>() {}.annotatedType();
193+
AnnotatedType resolved =
194+
ParameterizedTypeSupport.resolveTypeArguments(
195+
AnnotatedSupplier.class, interfaceType, annotatedType);
196+
197+
assertThat(resolved).isInstanceOf(AnnotatedParameterizedType.class);
198+
199+
AnnotatedParameterizedType outerList = (AnnotatedParameterizedType) resolved;
200+
assertThat(((ParameterizedType) outerList.getType()).getRawType()).isEqualTo(List.class);
201+
202+
AnnotatedType nestedType = outerList.getAnnotatedActualTypeArguments()[0];
203+
assertThat(nestedType).isInstanceOf(AnnotatedParameterizedType.class);
204+
205+
AnnotatedParameterizedType innerList = (AnnotatedParameterizedType) nestedType;
206+
assertThat(((ParameterizedType) innerList.getType()).getRawType()).isEqualTo(List.class);
207+
AnnotatedType terminalElement = innerList.getAnnotatedActualTypeArguments()[0];
208+
assertThat(
209+
TypeSupport.annotatedTypeEquals(
210+
interfaceType.getAnnotatedActualTypeArguments()[0], terminalElement))
211+
.isTrue();
212+
}
128213
}

0 commit comments

Comments
 (0)