Skip to content

Obfuscaton of inner class extending a generic type breaks reflection #519

@HeikoKlare

Description

@HeikoKlare

The obfuscation results for an (anonymous) inner class that extends a generically typed class/interface are incompatible with Java reflection utilities.
This seems to be caused by an (anonymous) inner class of OUTERTYPE not being represented as OUTERTYPE$1 after obfuscation (but as an obfuscated, independently named type) such that the Java reflection utilities fail to resolve a generic type that is defined by the outer type.

How to reproduce

  • Create a class with an inner class that uses a generic type defined in the outer class. Issues especially arise when the generic type parameter is used to parameterize a supertype of that inner class. The following creates an inner class with super interface Supplier<T>:
public class Type {
	public <T> void test(T input) {
		new Supplier<T>() {
			@Override
			public T get() {
				return input;
			}			
		}.get();
	}
}
  • Run ProGuard with obfuscation and ensure that attribute Signature is kept (via -keepattributes Signature, this is important as otherwise reflection will not have the original signature with generic types available anymore)
  • Call getGenericInterfaces() on the inner type, e.g., via the following main method:
public static void main(String[] args) {
	new Type().test("");
	var superInterface = Type.class.getNestMembers()[1].getGenericInterfaces()[0];
}

With Java 21 and below, this code just assignes an invalid ParameterizedType instance to superInterface (having an unresolved generic type "null"). This can be seen when trying to print the type name of that parameterized type:

System.out.println(((ParameterizedType) superInterface).getTypeName());

It produces:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.reflect.Type.getTypeName()" because "t" is null
        at java.base/sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.toString(ParameterizedTypeImpl.java:236)
        at java.base/java.lang.reflect.Type.getTypeName(Type.java:54)
        at test.Main.main(Unknown Source)

When using the type without accessing the generic type, no error occurred at all (which is the reason why we did not experience the issue before switing to Java 25, as that's the use case we had due to reflection performed by HK2).

With Java 25, the issue becomes more severe (and also affects our use case), because getGenericInterfaces() now already fails due to an added check in the CoreReflectionFactory of the JDK:
openjdk/jdk@1d070a3#diff-8e4042af6c50a9ad11d179d224feb250cebcf8298cbd07029a9d5feee47fe567

When just executing the above posted main method, the following exception occurs:

Exception in thread "main" java.lang.TypeNotPresentException: Type T not present
        at java.base/sun.reflect.generics.factory.CoreReflectionFactory.findTypeVariable(CoreReflectionFactory.java:111)
        at java.base/sun.reflect.generics.visitor.Reifier.visitTypeVariableSignature(Reifier.java:165)
        at java.base/sun.reflect.generics.tree.TypeVariableSignature.accept(TypeVariableSignature.java:43)
        at java.base/sun.reflect.generics.visitor.Reifier.reifyTypeArguments(Reifier.java:68)
        at java.base/sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:138)
        at java.base/sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
        at java.base/sun.reflect.generics.repository.ClassRepository.computeSuperInterfaces(ClassRepository.java:117)
        at java.base/sun.reflect.generics.repository.ClassRepository.getSuperInterfaces(ClassRepository.java:95)
        at java.base/java.lang.Class.getGenericInterfaces(Class.java:1278)
        at test.Main.main(Unknown Source)

Version Info

Tested with ProGuard 7.8.2 (and also with 7.4.2).

Issue is present with probably every Java version but became more severe with change in Java 25.
JDK used for testing was: Eclipse Temuring 25.0.2.10

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions