geantyref
geantyref copied to clipboard
StackOverflowError in VarMap.map when calling GenericTypeReflector.getParameterTypes()
mfriedenhagen reported an issue in Spock https://github.com/spockframework/spock/issues/1909 where geantyref throws a StackOverflowError
.
I have removed the Spock dependency from the reproducer attached to the Spock issue, and the error is still there:
import java.lang.reflect.Method;
import java.util.Objects;
public class StackOverflowReproducer {
static abstract class Status<StatusT, StatusBuilderT> {
static class Builder<StatusT, StatusBuilderT> {
}
}
static abstract class Resource<StatusT, StatusBuilderT> {
}
static class ProjectValidator {
public <
StatusT extends Status<StatusT, StatusBuilderT>,
StatusBuilderT extends Status.Builder<StatusT, StatusBuilderT>,
T extends Resource<StatusT, StatusBuilderT>>
void validate(long id, Class<T> klass) {
throw new UnsupportedOperationException("Just for testing");
}
}
public static void main(String[] args) throws NoSuchMethodException {
Class<?> cls = ProjectValidator.class;
Method method = Objects.requireNonNull(cls.getMethod("validate", long.class, Class.class));
io.leangen.geantyref.GenericTypeReflector.getParameterTypes(method, cls);
}
}
The exception stack looks like:
java.lang.StackOverflowError
at java.base/sun.reflect.generics.reflectiveObjects.TypeVariableImpl.getGenericDeclaration(TypeVariableImpl.java:144)
at java.base/sun.reflect.generics.reflectiveObjects.TypeVariableImpl.typeVarIndex(TypeVariableImpl.java:227)
at java.base/sun.reflect.generics.reflectiveObjects.TypeVariableImpl.getAnnotatedBounds(TypeVariableImpl.java:220)
at java.base/sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeVariableImpl.getAnnotatedBounds(AnnotatedTypeFactory.java:383)
at io.leangen.geantyref.VarMap.map(VarMap.java:98)
at io.leangen.geantyref.VarMap.map(VarMap.java:115)
at io.leangen.geantyref.VarMap.map(VarMap.java:148)
at io.leangen.geantyref.VarMap.map(VarMap.java:98)
The used geantyref version is io.leangen.geantyref:geantyref:1.3.15
Tested for Java 8
and 17
This problem can be simplified to a single self-referential type.
import java.lang.reflect.Method;
import java.util.Objects;
public class StackOverflowReproducer {
static abstract class Resource<StatusT> {
}
static class ProjectValidator {
public <T extends Resource<T>>
void validate(long id, Class<T> klass) {
throw new UnsupportedOperationException("Just for testing");
}
}
public static void main(String[] args) throws NoSuchMethodException {
Class<?> cls = ProjectValidator.class;
Method method = Objects.requireNonNull(cls.getMethod("validate", long.class, Class.class));
io.leangen.geantyref.GenericTypeReflector.getParameterTypes(method, cls);
}
}
This can probably be avoided by tracking already seen AnnotatedTypeVariable
s in io.leangen.geantyref.VarMap#map(java.lang.reflect.AnnotatedType, io.leangen.geantyref.VarMap.MappingMode)
. However, I'm unsure what the correct return type would be in this case, maybe just Resource
without any generic information? Or, do we need to throw an UnresolvedTypeVariableException
.
@leonard84 The VarMap.MappingMode.EXACT
mode does already throw an UnresolvedTypeVariableException
in that case, so only the VarMap.MappingMode.ALLOW_INCOMPLETE
case is left, where I would say we shall just return the imcomplete type.
I have created the PR #30, which fixes the issue but returning the incomplete type.
Phew, this thing really sent me soul-searching 😅 Nothing seemed correct. Terminating with Object
would violate type constraints, returning the given type
as-is, as I suggested here would potentially skip merging annotations...
I think I got the right semantics in the end (the type stays recursive, annotations get merged) but it required some rather ugly code 😓
Can you (@AndreasTu, @leonard84) please verify it now works correctly for you in Spock? If so, I can release this immediately.
@kaqqao I have tested the Spock issue report against the geantyref master, and it fixes the issue. Thanks a lot!
Thanks, @kaqqao, for the fix. I'm looking forward to the release.
Released v1.3.16 just now 🚀