leangen / geantyref

Advanced generic type reflection library with support for working with AnnotatedTypes (for Java 8+)
Apache License 2.0
99 stars 15 forks source link

StackOverflowError in VarMap.map when calling GenericTypeReflector.getParameterTypes() #27

Open AndreasTu opened 5 months ago

AndreasTu commented 5 months ago

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

leonard84 commented 4 months ago

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);
  }
}
leonard84 commented 4 months ago

This can probably be avoided by tracking already seen AnnotatedTypeVariables 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.

AndreasTu commented 2 weeks ago

@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.