JDK-8191466 : TYPE_USE annotations on a generic bound are inaccessible/lost
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 8,9.0.1,10
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: linux
  • CPU: x86_64
  • Submitted: 2017-11-15
  • Updated: 2024-08-02
  • Resolved: 2024-08-02
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdResolved
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_152"
Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Linux d0074211 4.13.11 #1 SMP Thu Nov 2 22:32:48 CET 2017 x86_64 GNU/Linux

EXTRA RELEVANT SYSTEM CONFIGURATION :
Verified the bug on multiple OSes (Linux, OS X, Windows) and JDK 8.x and 9.x versions

A DESCRIPTION OF THE PROBLEM :
Consider this simple class:

public class SomeClass<T> {
    public <@A1 S extends @A2 T> @A3 S myMethod() { ...}
}

@A2 annotation on the bound T is inaccessible via reflection. See below for details.
 
Assuming myMethod is SomeClass.class.getDeclaredMethod("myMethod"). Type casts removed for readability.

myMethod.getGenericReturnType().getAnnotations() - returns @A1 (expected)
myMethod.getGenericReturnType().getBounds()[0].getAnnotations() - returns nothing (no declaration annotation on T)
myMethod.getAnnotatedReturnType().getAnnotations() - returns @A3 (expected)
myMethod.getAnnotatedReturnType().getAnnotatedBounds()[0].getAnnotations() - returns @A2

See https://stackoverflow.com/questions/47175573/how-to-get-type-use-annotations-on-a-generic-bound for a better formatted description, discussion and analysis

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create a generic class containing a method with an annotated bounded type variable as the return type.
E.g.
public class SomeClass<T> {
    public <@A1 S extends @A2 T> @A3 S myMethod() { ...}
}

2. Try retrieving the @A2 annotation via reflection:
Method myMethod = SomeClass.class.getDeclaredMethod("myMethod");
Annotation[] annotations = myMethod.getGenericReturnType().getBounds()[0].getAnnotations(); // declaration annotation on T
Annotation[] annotations2 = myMethod.getAnnotatedReturnType().getAnnotatedBounds()[0].getAnnotations(); // type use annotation on T's use as upper(extends) bound


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
At least one of the annotation arrays from above (annotations and annotations2) should contain the @A2 instance
ACTUAL -
Both methods return no annotations

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package io.leangen.graphql.jimmy;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedTypeVariable;
import java.lang.reflect.Method;
import java.lang.reflect.TypeVariable;

public class AnnotatedBoundTest {

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE_USE)
    @interface A1 {
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE_USE)
    @interface A2 {
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE_USE)
    @interface A3 {
    }

    public class SomeClass<T> {
        <@A1 S extends @A2 T> @A3 S myMethod() { return null;}
    }


    public static void main(String[] args) throws NoSuchMethodException {
        Method myMethod = SomeClass.class.getDeclaredMethod("myMethod");
        Annotation[] annotations = ((TypeVariable) ((TypeVariable) myMethod.getGenericReturnType()).getBounds()[0]).getAnnotations();
        Annotation[] annotations2 = ((AnnotatedTypeVariable) myMethod.getAnnotatedReturnType()).getAnnotatedBounds()[0].getAnnotations();
        assert annotations.length == 1; // fails, no declaration annotation on T
        assert annotations2.length == 1; // succeeds
    }
}

---------- END SOURCE ----------


Comments
This is a user error. I have attached the correct behaviors in the 4 cases in the original issue body. public <@A1 S extends @A2 T> @A3 S myMethod() { ...} Note that A1 is a declaration annotation on type variable S, A2 and A3 are type use annotations on use of T as upper (extends) bound and use of S as a method return type. myMethod.getGenericReturnType() returns the type variable S declaration, which can produce @A1 if the type variable is viewed as AnnotatedElement. The TypeVariable has a getAnnotatedBounds, which will return @A2 T AnnotatedTypeVariable, and you can find @A2 from that. If you do getBounds, you will find the declaration of T; and since in the example T has no declaration annotations, you will find nothing. myMethod.getAnnotatedReturnType() returns the type variable @A3 S usage, so you can find @A3 on it. Its getAnnotatedBounds is same as getGenericReturnType() cast then .getAnnotatedBounds(), so you can also find @A2.
02-08-2024

this is the bytecode javac is generating for method SomeClass::myMethod: <S extends T> S myMethod(); descriptor: ()Ljava/lang/Object; flags: (0x0000) Code: stack=1, locals=1, args_size=1 0: aconst_null 1: areturn LineNumberTable: line 25: 0 Signature: #14 // <S:TT;>()TS; RuntimeVisibleTypeAnnotations: 0: #19(): METHOD_TYPE_PARAMETER, param_index=0 1: #22(): METHOD_TYPE_PARAMETER_BOUND, param_index=0, bound_index=0 2: #25(): METHOD_RETURN the related portion of the constant pool is: #19 = Utf8 LAnnotatedBoundTest$A1; #22 = Utf8 LAnnotatedBoundTest$A2; #25 = Utf8 LAnnotatedBoundTest$A3; so it seems to me that javac is generating correct code and that the pipeline should be broken on the reflection yard.
17-11-2017

Moving across to dev-team for evaluation.
17-11-2017

To reproduce the issue, run the attached test case. JDK 8u151 - Fail JDK 9.0.1 - Fail JDK 10-ea+30 - Fail Exception in thread "main" java.lang.AssertionError at AnnotatedBoundTest.main(AnnotatedBoundTest.java:35)
17-11-2017