Skip to content

Commit b15372a

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 bee8c4e commit b15372a

File tree

4 files changed

+156
-17
lines changed

4 files changed

+156
-17
lines changed

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

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.lang.reflect.Executable;
3333
import java.lang.reflect.Method;
3434
import java.lang.reflect.Modifier;
35+
import java.lang.reflect.ParameterizedType;
3536
import java.lang.reflect.Type;
3637
import java.util.Arrays;
3738
import java.util.Comparator;
@@ -52,17 +53,27 @@ static Optional<Class<?>> optionalClassForName(String targetClassName) {
5253
}
5354
}
5455

56+
private static Class<?> rawBeanType(Type classType) {
57+
if (classType instanceof Class<?>) {
58+
return (Class<?>) classType;
59+
} else if (classType instanceof ParameterizedType) {
60+
return rawBeanType(((ParameterizedType) classType).getRawType());
61+
} else {
62+
// Bail out on wildcard types or type variables neither of which are supported as "top level"
63+
// types in a @FuzzTest. Also bail out on generic array types as they are handled by the
64+
// ArrayMutatorFactory.
65+
throw new UnsupportedOperationException("Unsupported type: " + classType);
66+
}
67+
}
68+
5569
// Returns the annotated parameter types of a method or constructor resolving all generic type
5670
// arguments.
5771
public static AnnotatedType[] resolveAnnotatedParameterTypes(
5872
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;
73+
Class<?> clazz = rawBeanType(classType.getType());
74+
return stream(e.getAnnotatedParameterTypes())
75+
.map(t -> resolveTypeArguments(clazz, classType, t))
76+
.toArray(AnnotatedType[]::new);
6677
}
6778

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

7586
static Type resolveReturnType(Method method, AnnotatedType classType) {
7687
return resolveTypeArguments(
77-
method.getDeclaringClass(), classType, method.getAnnotatedReturnType())
88+
rawBeanType(classType.getType()), classType, method.getAnnotatedReturnType())
7889
.getType();
7990
}
8091

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+
updateTypeMappings(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 updateTypeMappings(
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+
updateTypeMappings(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+
updateTypeMappings(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)