-
Notifications
You must be signed in to change notification settings - Fork 163
feat: add mutator support for generic classes #1008
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
f119aa8 to
581c4d5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for generic classes in the mutator framework by resolving type parameters at runtime. Previously, generic classes would fail to create valid mutators due to type erasure causing parameter and return types to be reported as java.lang.Object. The implementation uses AnnotatedType to resolve type parameters and construct mutators with the correct concrete types.
Key Changes:
- Added type resolution logic to handle generic type parameters in constructors, methods, and return types
- Updated all aggregate mutator factories to resolve generic types before creating mutators
- Added comprehensive test coverage for generic classes across different mutator types (setter-based, constructor-based, cached, records)
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| BeanSupport.java | Adds core type resolution methods for resolving generic type arguments in methods, constructors, and return types |
| AggregatesHelper.java | Updates mutator creation to use resolved types instead of raw types from reflection API |
| CachedConstructorMutatorFactory.java | Uses resolved parameter types when building mutators for generic classes |
| ConstructorBasedBeanMutatorFactory.java | Resolves parameter types and return types when matching getters to constructors |
| SetterBasedBeanMutatorFactory.java | Resolves setter parameter types to properly handle generic classes |
| SuperBuilderMutatorFactory.java | Updates to use resolved parameter types for builder pattern support |
| SetterBasedBeanMutatorTest.java | Adds test for generic class with setter-based mutation |
| RecordMutatorTest.java | Adds test for generic record types |
| ConstructorBasedBeanMutatorTest.java | Adds tests for generic classes with single and nested type parameters |
| CachedConstructorMutatorTest.java | Adds test for generic class with cached constructor |
| StressTest.java | Adds stress test cases for various generic class patterns |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/main/java/com/code_intelligence/jazzer/mutation/mutator/aggregate/BeanSupport.java
Show resolved
Hide resolved
src/main/java/com/code_intelligence/jazzer/mutation/mutator/aggregate/BeanSupport.java
Outdated
Show resolved
Hide resolved
src/main/java/com/code_intelligence/jazzer/mutation/mutator/aggregate/AggregatesHelper.java
Show resolved
Hide resolved
08f6331 to
a9ae722
Compare
...va/com/code_intelligence/jazzer/mutation/mutator/aggregate/CachedConstructorMutatorTest.java
Outdated
Show resolved
Hide resolved
src/main/java/com/code_intelligence/jazzer/mutation/mutator/aggregate/BeanSupport.java
Outdated
Show resolved
Hide resolved
src/main/java/com/code_intelligence/jazzer/mutation/mutator/aggregate/BeanSupport.java
Outdated
Show resolved
Hide resolved
a9ae722 to
f8cd737
Compare
|
Added a commit to support non-trivial use of the type variable. For example the fuzz test will create a mutator like this: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
oetr
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thanks!
The type resolution doesn't work for some cases, but other than that it looks solid. Great work!
src/main/java/com/code_intelligence/jazzer/mutation/support/ParameterizedTypeSupport.java
Outdated
Show resolved
Hide resolved
src/main/java/com/code_intelligence/jazzer/mutation/support/ParameterizedTypeSupport.java
Outdated
Show resolved
Hide resolved
src/main/java/com/code_intelligence/jazzer/mutation/support/ParameterizedTypeSupport.java
Outdated
Show resolved
Hide resolved
src/main/java/com/code_intelligence/jazzer/mutation/support/ParameterizedTypeSupport.java
Outdated
Show resolved
Hide resolved
src/main/java/com/code_intelligence/jazzer/mutation/support/ParameterizedTypeSupport.java
Outdated
Show resolved
Hide resolved
src/main/java/com/code_intelligence/jazzer/mutation/support/ParameterizedTypeSupport.java
Outdated
Show resolved
Hide resolved
| * @param classType the annotated instantiation of {@code clazz} | ||
| * @param type the annotated type to resolve (e.g. a constructor parameter or getter return type) | ||
| */ | ||
| public static AnnotatedType resolveTypeArguments( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested various generic Classes with the help of our ArgumentsMutatorFuzzTest.
To kick things off, this non-generic version works and gives us correct mutator: Arguments[[String] -> Child]
public static class Base {
private String x;
public void setX(String x) { this.x = x; }
public String getX() { return x; }
}
public static class Child extends Base {}
@Solo
@SelfFuzzTest
public static void fuzzGenericClass(@NotNull(constraint = PropertyConstraint.RECURSIVE) Child pair) {}The same but generic version with fixed generic type of the superclass Base gives us the trivial mutator that always returns null: Arguments[[] -> Child]
Expected: Arguments[[String]->Child]
public static class Base<T> {
private T x;
public void setX(T x) { this.x = x; }
public T getX() { return x; }
}
public static class Child extends Base<String> {
}
@Solo
@SelfFuzzTest
public static void fuzzGenericClass(@NotNull(constraint = PropertyConstraint.RECURSIVE) Child pair) {}This gives us the wrong mutator: Arguments[[Integer] -> Child]
Expected: Arguments[[String] -> Child]
public static class Base<X> {
protected X x;
public void setX(X x) { this.x = x; }
public X getX() { return x; }
}
public static class Child<U> extends Base<String> { }
@Solo
@SelfFuzzTest
public static void fuzzGenericClass(@NotNull Child<Integer> pair) {}A variation of above with the same problem--- it gives us the wrong mutator: Arguments[[String, String] -> Child]
Expected: Arguments[[Integer,String]->Child] (or first String and then Integer, depending on which class we start with first):
public static class Base<X> {
protected X x;
public void setX(X x) { this.x = x; }
public X getX() { return x; }
}
public static class Child<U> extends Base<Integer> {
protected U u;
public U getU() { return u; }
public void setU(U u) { this.u = u; }
}
@Solo
@SelfFuzzTest
public static void fuzzGenericClass(@NotNull Child<@NotNull String> pair) {
if (pair == null) {
throw new IllegalArgumentException("pair is null");
}
// child gets resolved
String u = pair.getU();
// base does not get resolved
Object x = pair.getX();
if (x == null) {
return;
}
// intelliJ tells me to remove this, because it's "always false"!
if (!(x instanceof Integer)) {
throw new IllegalArgumentException("x is not an Integer: " + x);
}
}The following fuzz test runs into the requirement error in resolveTypeArguments() in ParameterizedTypeSupport.java:75
Expected: Arguments[[String] -> Child]
public static class Base<T> {
protected T x;
public void setX(T x) { this.x = x; }
public T getX() { return x; }
}
// Here U is not related to getters and setters
public static class Child<T, U> extends Base<T> { }
@Solo
@SelfFuzzTest
public static void fuzzGenericClass(@NotNull(constraint = PropertyConstraint.RECURSIVE) Child<String, Integer> pair) { }Here are the values of involved variables:
clazz: class com.code_intelligence.selffuzz.mutation.ArgumentsMutatorFuzzTest$Base
typeParameters: [T]
classType: @com.code_intelligence.selffuzz.jazzer.mutation.annotation.NotNull(constraint="JAZZER_PROPERTY_CONSTRAINT_RECURSIVE") com.code_intelligence.selffuzz.mutation.ArgumentsMutatorFuzzTest$Child<java.lang.String, java.lang.Integer>
typeArguments: [java.lang.String, java.lang.Integer]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great test cases! Yes, generic superclasses were completely missing in the implementation.
I added another commit that walks up all the superclasses (and dependencies) to build up a more accurate mapping of type variables. That seems to cover all the cases you found not to be working.
| return annotated; | ||
| } | ||
|
|
||
| private static AnnotatedParameterizedType resolveParameterizedType( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the reflection API has both ParameterizedType and AnnotatedParameterizedType, I find this name imprecise. Should we rename all functions that return annotated types into ...Annotated... and those that return Type into ...Raw...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is only one version of those functions I would personally prefer the shorter name. Its clear from the return type, that we get an AnnotatedType, no?
src/main/java/com/code_intelligence/jazzer/mutation/mutator/aggregate/BeanSupport.java
Outdated
Show resolved
Hide resolved
| Class<?> clazz, AnnotatedType classType, Stream<Type> types) { | ||
| Map<Type, List<Method>> gettersByType = | ||
| findMethods(clazz, BeanSupport::isGetter) | ||
| .collect(groupingBy(m -> resolveReturnType(m, classType))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If m is not declared in classType, which here is the instantiated class of the concrete bean, the resolution doesn't work, as seen in the examples in another comment below.
To fix it, we should probably resolve all necessary generic types untill we reach the superclass where m is declared.
The same applies for the return types of the setters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the additions in ParameterizedTypeSupport.java a12e29d should take care of this by going up the entire superclass chain to resolve all type variables.
This enables mutator support for classes that use type variables as part
of an array, wildcard or other generic class.
E.g. `class MyClass<T> { MyClass(Set<T[]> setOfArrays){...} }`.
d77f4fc to
a12e29d
Compare
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.
a12e29d to
3b402e9
Compare
Adds mutator support for generic classes.
Constructing mutators for generic classes would previously either fail or not work as expected due to parameter and return types being reported as
java.lang.Objectby the reflection API (type erasure). Since we can't provide a generalObjectmutator this would creation of a valid mutator for most generic classes.Using the
AnnotatedTypeof the class we resolve type parameters to create the correct underlying mutators.Example fuzz test: