Skip to content

Commit a12e29d

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 a12e29d

File tree

4 files changed

+150
-11
lines changed

4 files changed

+150
-11
lines changed

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

Lines changed: 17 additions & 2 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,6 +54,19 @@ 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(
@@ -60,7 +75,7 @@ public static AnnotatedType[] resolveAnnotatedParameterTypes(
6075
AnnotatedType[] annotated = e.getAnnotatedParameterTypes();
6176
AnnotatedType[] result = new AnnotatedType[generic.length];
6277
for (int i = 0; i < generic.length; i++) {
63-
result[i] = resolveTypeArguments(e.getDeclaringClass(), classType, annotated[i]);
78+
result[i] = resolveTypeArguments(rawType(classType.getType()), classType, annotated[i]);
6479
}
6580
return result;
6681
}
@@ -74,7 +89,7 @@ public static Type[] resolveParameterTypes(Executable e, AnnotatedType classType
7489

7590
static Type resolveReturnType(Method method, AnnotatedType classType) {
7691
return resolveTypeArguments(
77-
method.getDeclaringClass(), classType, method.getAnnotatedReturnType())
92+
rawType(classType.getType()), classType, method.getAnnotatedReturnType())
7893
.getType();
7994
}
8095

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

Lines changed: 38 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
/**

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)