JDK-8057898 : Annotations on receiver type's type arguments are lost
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 8,8u20
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2014-09-03
  • Updated: 2016-07-28
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
tbd_majorUnresolved
Related Reports
Cloners :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
MacOS X 10.9.4
(uname reports: Darwin Kernel Version 13.3.0)

A DESCRIPTION OF THE PROBLEM :
It is not possible to access type annotations on type arguments on an inner class's receiver type. Furthermore, the receiver type, though implicitly the first (synthesized) ctor argument for an inner class, differs in Java reflection from simply examining the first parameter (also available via reflection).

Type annotations via core reflection, in general, seem quite busted in 1.8.0_20. (This is the second bug I've found, and I've also found some omissions and filed two enhancement requests on this same topic.)

I'm guessing this one is closely related to (perhaps same root cause as) this bug: https://bugs.openjdk.java.net/browse/JDK-8039916?page=com.atlassian.jira.plugin.system.issuetabpanels%3acomment-tabpanel

However that bug report says (I think -- I could be misinterpreting it) the fix made it into 1.8.0_20 b20.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the program found below in the "source code" section of this bug report. Observe its output.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Output of the program listed below should include type annotations in the AnnotatedType for the receiver type. Ideally, the same information could also be accessed via the synthesized first constructor argument (since it is the same as the receiver in this case). So, ideally, the program's output would look like so:

param[0] generic #1 => FirstParameterVsReceiverTypeBug<T,U>

param[0] generic #2 => FirstParameterVsReceiverTypeBug<T,U>

param[0] annotated #1 => @FirstParameterVsReceiverTypeBug$A() FirstParameterVsReceiverTypeBug<@FirstParameterVsReceiverTypeBug$B() T,@FirstParameterVsReceiverTypeBug$C() U>

param[0] annotated #2 => @FirstParameterVsReceiverTypeBug$A() FirstParameterVsReceiverTypeBug<@FirstParameterVsReceiverTypeBug$B() T,@FirstParameterVsReceiverTypeBug$C() U>

receiver generic => FirstParameterVsReceiverTypeBug<T,U>

receiver annotated => @FirstParameterVsReceiverTypeBug$A() FirstParameterVsReceiverTypeBug<@FirstParameterVsReceiverTypeBug$B() T,@FirstParameterVsReceiverTypeBug$C() U>
ACTUAL -
Actual output of the program listed below shows that type annotations on type arguments to receiver type cannot be accessed. Furthermore, no annotations or generic type information are available from the first constructor parameter (which, since this is an inner class, is the receiver).

param[0] generic #1 => FirstParameterVsReceiverTypeBug

param[0] generic #2 => FirstParameterVsReceiverTypeBug

param[0] annotated #1 => FirstParameterVsReceiverTypeBug

param[0] annotated #2 => FirstParameterVsReceiverTypeBug

receiver generic => FirstParameterVsReceiverTypeBug

receiver annotated => @FirstParameterVsReceiverTypeBug$A() FirstParameterVsReceiverTypeBug

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
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.AnnotatedArrayType;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.AnnotatedTypeVariable;
import java.lang.reflect.AnnotatedWildcardType;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

public class FirstParameterVsReceiverTypeBug<T, U> {

   @Target(ElementType.TYPE_USE)
   @Retention(RetentionPolicy.RUNTIME)
   @interface A {
   }
   
   @Target(ElementType.TYPE_USE)
   @Retention(RetentionPolicy.RUNTIME)
   @interface B {
   }
   
   @Target(ElementType.TYPE_USE)
   @Retention(RetentionPolicy.RUNTIME)
   @interface C {
   }
   
   class Foo {
      Foo(@A FirstParameterVsReceiverTypeBug<@B T, @C U> FirstParameterVsReceiverTypeBug.this) {
      }
   }
   
   public static void main(String args[]) {
      Constructor<?> cons = FirstParameterVsReceiverTypeBug.Foo.class.getDeclaredConstructors()[0];
      
      Type param0GenericType1 = cons.getGenericParameterTypes()[0];
      Type param0GenericType2 = cons.getParameters()[0].getParameterizedType();
      Type receiverGenericType1 = cons.getAnnotatedReceiverType().getType();
      
      AnnotatedType param0AnnotatedType1 = cons.getAnnotatedParameterTypes()[0];
      AnnotatedType param0AnnotatedType2 = cons.getParameters()[0].getAnnotatedType();
      AnnotatedType receiverAnnotatedType1 = cons.getAnnotatedReceiverType();

      System.out.println("param[0] generic #1 => " + param0GenericType1.getTypeName() + "\n");
      System.out.println("param[0] generic #2 => " + param0GenericType2.getTypeName() + "\n");
      System.out.println("param[0] annotated #1 => " + toString(param0AnnotatedType1) + "\n");
      System.out.println("param[0] annotated #2 => " + toString(param0AnnotatedType2) + "\n");
      System.out.println("receiver generic => " + receiverGenericType1.getTypeName() + "\n");
      System.out.println("receiver annotated => " + toString(receiverAnnotatedType1) + "\n");
   }

   
   // Converts an AnnotatedType to a useful String that includes both type annotations *and* generic
   // type information:
   
   private static String toString(AnnotatedType t) {
      StringBuilder sb = new StringBuilder();
      toString(t, sb);
      return sb.toString();
   }
   
   private static void toString(AnnotatedType t, StringBuilder sb) {
      if (t instanceof AnnotatedParameterizedType) {
         for (Annotation a : t.getDeclaredAnnotations()) {
            sb.append(a.toString());
            sb.append(' ');
         }
         Type s = t.getType();
         assert s instanceof ParameterizedType || s instanceof Class;
         if (s instanceof ParameterizedType) {
            ParameterizedType p = (ParameterizedType) s;
            Type owner = p.getOwnerType();
            if (owner != null) {
               sb.append(owner.getTypeName());
               sb.append('.');
            }
            sb.append(p.getRawType().getTypeName());
         } else {
            sb.append(s.getTypeName());
         }
         sb.append('<');
         boolean first = true;
         for (AnnotatedType param : ((AnnotatedParameterizedType) t).getAnnotatedActualTypeArguments()) {
            if (first) {
               first = false;
            } else {
               sb.append(", ");
            }
            toString(param, sb);
         }
         sb.append(">");
      } else if (t instanceof AnnotatedArrayType) {
         toString(((AnnotatedArrayType) t).getAnnotatedGenericComponentType(), sb);
         for (Annotation a : t.getDeclaredAnnotations()) {
            sb.append(a.toString());
            sb.append(' ');
         }
         sb.append("[]");
      } else if (t instanceof AnnotatedTypeVariable) {
         for (Annotation a : t.getDeclaredAnnotations()) {
            sb.append(a.toString());
            sb.append(' ');
         }
         TypeVariable<?> tv = (TypeVariable<?>) t.getType();
         sb.append(tv.getName());
      } else if (t instanceof AnnotatedWildcardType) {
         for (Annotation a : t.getDeclaredAnnotations()) {
            sb.append(a.toString());
            sb.append(' ');
         }
         AnnotatedWildcardType awt = (AnnotatedWildcardType) t;
         AnnotatedType lower[] = awt.getAnnotatedLowerBounds();
         AnnotatedType upper[] = awt.getAnnotatedUpperBounds();
         AnnotatedType bounds[];
         if (lower.length > 0) {
            assert upper.length == 1 && upper[0].getType() == Object.class;
            bounds = lower;
         } else {
            bounds = upper;
         }
         boolean first = true;
         for (AnnotatedType bound : bounds) {
            if (first) {
               first = false;
            } else {
               sb.append(" & ");
            }
            toString(bound, sb);
         }
      } else {
         for (Annotation a : t.getDeclaredAnnotations()) {
            sb.append(a.toString());
            sb.append(' ');
         }
         sb.append(t.getType().getTypeName());
      }
   }
}

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


Comments
This is a reflection bug.
23-09-2014

Smaller repro, @A and @B doesnt show up in reflection in the following program: public class Receiver<T extends String, U> { class Foo { Foo(Receiver<@A T, @B U> Receiver.this) { } } } This may still be a reflection or compiler bug, depending on interpretation of allowable receiver types.
19-09-2014

see: http://mail.openjdk.java.net/pipermail/compiler-dev/2014-September/008992.html
12-09-2014

Original test case
11-09-2014

There are 2-3 issues here in the compiler, possibly spec and reflection. See the cloned issues.
11-09-2014