geantyref icon indicating copy to clipboard operation
geantyref copied to clipboard

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

Open AndreasTu opened this issue 11 months ago • 3 comments

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

AndreasTu avatar Mar 06 '24 18:03 AndreasTu

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 avatar Mar 21 '24 18:03 leonard84

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.

leonard84 avatar Mar 21 '24 19:03 leonard84

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

AndreasTu avatar Jul 15 '24 20:07 AndreasTu

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 avatar Aug 25 '24 17:08 kaqqao

@kaqqao I have tested the Spock issue report against the geantyref master, and it fixes the issue. Thanks a lot!

AndreasTu avatar Aug 25 '24 20:08 AndreasTu

Thanks, @kaqqao, for the fix. I'm looking forward to the release.

leonard84 avatar Aug 26 '24 12:08 leonard84

Released v1.3.16 just now 🚀

kaqqao avatar Aug 27 '24 11:08 kaqqao