26
26
27
27
import java .lang .annotation .Annotation ;
28
28
import java .lang .reflect .Modifier ;
29
+ import java .lang .reflect .Proxy ;
29
30
import java .util .Arrays ;
31
+ import java .util .concurrent .ConcurrentHashMap ;
32
+ import java .util .function .Function ;
30
33
31
34
import org .graalvm .compiler .api .replacements .SnippetReflectionProvider ;
32
35
import org .graalvm .compiler .core .common .type .StampFactory ;
56
59
import org .graalvm .compiler .replacements .GraphKit ;
57
60
import org .graalvm .compiler .replacements .nodes .BasicObjectCloneNode ;
58
61
import org .graalvm .nativeimage .ImageSingletons ;
62
+ import org .graalvm .nativeimage .hosted .Feature ;
59
63
60
64
import com .oracle .graal .pointsto .constraints .UnsupportedFeatureException ;
61
65
import com .oracle .graal .pointsto .meta .HostedProviders ;
66
+ import com .oracle .svm .core .annotate .AutomaticFeature ;
62
67
import com .oracle .svm .core .annotate .Substitute ;
63
68
import com .oracle .svm .core .annotate .TargetClass ;
64
69
import com .oracle .svm .core .hub .AnnotationTypeSupport ;
65
70
import com .oracle .svm .core .jdk .AnnotationSupportConfig ;
66
71
import com .oracle .svm .core .meta .SubstrateObjectConstant ;
72
+ import com .oracle .svm .core .util .VMError ;
73
+ import com .oracle .svm .hosted .FeatureImpl .DuringSetupAccessImpl ;
74
+ import com .oracle .svm .hosted .analysis .Inflation ;
67
75
import com .oracle .svm .hosted .phases .HostedGraphKit ;
68
76
import com .oracle .svm .hosted .snippets .SubstrateGraphBuilderPlugins ;
69
77
@@ -85,24 +93,35 @@ public class AnnotationSupport extends CustomSubstitution<AnnotationSubstitution
85
93
86
94
private final ResolvedJavaType javaLangAnnotationAnnotation ;
87
95
private final ResolvedJavaType javaLangReflectProxy ;
96
+ private final ResolvedJavaType constantAnnotationMarker ;
88
97
89
98
public AnnotationSupport (MetaAccessProvider metaAccess , SnippetReflectionProvider snippetReflection ) {
90
99
super (metaAccess );
91
100
this .snippetReflection = snippetReflection ;
92
101
93
102
javaLangAnnotationAnnotation = metaAccess .lookupJavaType (java .lang .annotation .Annotation .class );
94
103
javaLangReflectProxy = metaAccess .lookupJavaType (java .lang .reflect .Proxy .class );
104
+ constantAnnotationMarker = metaAccess .lookupJavaType (ConstantAnnotationMarker .class );
95
105
96
106
AnnotationSupportConfig .initialize ();
97
107
}
98
108
99
- private boolean isAnnotation (ResolvedJavaType type ) {
100
- return javaLangAnnotationAnnotation .isAssignableFrom (type ) && javaLangReflectProxy .isAssignableFrom (type );
109
+ private boolean isConstantAnnotationType (ResolvedJavaType type ) {
110
+ /*
111
+ * Check if the type implements all of Annotation, Proxy and ConstantAnnotationMarker. If
112
+ * so, then it is the type of a annotation proxy object encountered during heap scanning.
113
+ * Only those types are substituted with a more efficient annotation proxy type
114
+ * implementation. If a type implements only Annotation and Proxy but not
115
+ * ConstantAnnotationMarker then it is a proxy type registered via the dynamic proxy API.
116
+ * Such type is used to allocate annotation instances at run time and must not be replaced.
117
+ */
118
+ return javaLangAnnotationAnnotation .isAssignableFrom (type ) && javaLangReflectProxy .isAssignableFrom (type ) &&
119
+ constantAnnotationMarker .isAssignableFrom (type );
101
120
}
102
121
103
122
@ Override
104
123
public ResolvedJavaType lookup (ResolvedJavaType type ) {
105
- if (isAnnotation (type )) {
124
+ if (isConstantAnnotationType (type )) {
106
125
return getSubstitution (type );
107
126
}
108
127
return type ;
@@ -118,15 +137,15 @@ public ResolvedJavaType resolve(ResolvedJavaType type) {
118
137
119
138
@ Override
120
139
public ResolvedJavaField lookup (ResolvedJavaField field ) {
121
- if (isAnnotation (field .getDeclaringClass ())) {
140
+ if (isConstantAnnotationType (field .getDeclaringClass ())) {
122
141
throw new UnsupportedFeatureException ("Field of annotation proxy is not accessible: " + field );
123
142
}
124
143
return field ;
125
144
}
126
145
127
146
@ Override
128
147
public ResolvedJavaMethod lookup (ResolvedJavaMethod method ) {
129
- if (isAnnotation (method .getDeclaringClass ())) {
148
+ if (isConstantAnnotationType (method .getDeclaringClass ())) {
130
149
AnnotationSubstitutionType declaringClass = getSubstitution (method .getDeclaringClass ());
131
150
AnnotationSubstitutionMethod result = declaringClass .getSubstitutionMethod (method );
132
151
assert result != null && result .original .equals (method );
@@ -286,8 +305,8 @@ static class AnnotationAnnotationTypeMethod extends AnnotationSubstitutionMethod
286
305
287
306
@ Override
288
307
public StructuredGraph buildGraph (DebugContext debug , ResolvedJavaMethod method , HostedProviders providers , Purpose purpose ) {
289
- assert method .getDeclaringClass (). getInterfaces (). length == 1 ;
290
- ResolvedJavaType annotationInterfaceType = method . getDeclaringClass (). getInterfaces ()[ 0 ] ;
308
+ ResolvedJavaType annotationType = method .getDeclaringClass ();
309
+ ResolvedJavaType annotationInterfaceType = findAnnotationInterfaceType ( annotationType ) ;
291
310
JavaConstant returnValue = providers .getConstantReflection ().asJavaClass (annotationInterfaceType );
292
311
293
312
GraphKit kit = new HostedGraphKit (debug , providers , method );
@@ -473,11 +492,42 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method,
473
492
}
474
493
}
475
494
476
- static ResolvedJavaType findAnnotationInterfaceType (ResolvedJavaType annotationType ) {
477
- assert annotationType .getInterfaces ().length == 1 ;
478
- return annotationType .getInterfaces ()[0 ];
495
+ /*
496
+ * This method retrieves the annotation interface type from an annotation proxy type represented
497
+ * as an AnnotationSubstitutionType (or a type that wraps an AnnotationSubstitutionType). The
498
+ * ConstantAnnotationMarker interface is already filtered out when
499
+ * AnnotationSubstitutionType.getInterfaces() is called.
500
+ */
501
+ private static ResolvedJavaType findAnnotationInterfaceType (ResolvedJavaType annotationType ) {
502
+ VMError .guarantee (Inflation .toWrappedType (annotationType ) instanceof AnnotationSubstitutionType );
503
+ ResolvedJavaType [] interfaces = annotationType .getInterfaces ();
504
+ VMError .guarantee (interfaces .length == 1 , "Unexpected number of interfaces for annotation proxy class." );
505
+ return interfaces [0 ];
479
506
}
480
507
508
+ /**
509
+ * This method retrieves the annotation interface type from a marked annotation proxy type.
510
+ * Annotation proxy types implement only the annotation interface by default. However, since we
511
+ * inject the ConstantAnnotationMarker the Annotation proxy types for ahead-of-time allocated
512
+ * annotations implement two interfaces. We make sure we return the right one here.
513
+ */
514
+ static ResolvedJavaType findAnnotationInterfaceTypeForMarkedAnnotationType (ResolvedJavaType annotationType , MetaAccessProvider metaAccess ) {
515
+ ResolvedJavaType [] interfaces = annotationType .getInterfaces ();
516
+ VMError .guarantee (interfaces .length == 2 , "Unexpected number of interfaces for annotation proxy class." );
517
+ VMError .guarantee (interfaces [1 ].equals (metaAccess .lookupJavaType (ConstantAnnotationMarker .class )));
518
+ return interfaces [0 ];
519
+ }
520
+
521
+ /*
522
+ * This method is similar to the above one, with the difference that it takes a Class<?> instead
523
+ * of an ResolvedJavaType as an argument.
524
+ */
525
+ static Class <?> findAnnotationInterfaceTypeForMarkedAnnotationType (Class <? extends Proxy > clazz ) {
526
+ Class <?>[] interfaces = clazz .getInterfaces ();
527
+ VMError .guarantee (interfaces .length == 2 , "Unexpected number of interfaces for annotation proxy class." );
528
+ VMError .guarantee (interfaces [1 ].equals (ConstantAnnotationMarker .class ));
529
+ return interfaces [0 ];
530
+ }
481
531
}
482
532
483
533
@ TargetClass (className = "sun.reflect.annotation.AnnotationType" )
@@ -498,3 +548,63 @@ public static AnnotationType getInstance(Class<? extends Annotation> annotationC
498
548
}
499
549
500
550
}
551
+
552
+ @ AutomaticFeature
553
+ class AnnotationSupportFeature implements Feature {
554
+
555
+ @ Override
556
+ public void duringSetup (DuringSetupAccess access ) {
557
+ DuringSetupAccessImpl config = (DuringSetupAccessImpl ) access ;
558
+ access .registerObjectReplacer (new AnnotationObjectReplacer (config .getImageClassLoader ().getClassLoader ()));
559
+ }
560
+ }
561
+
562
+ /**
563
+ * This replacer replaces the annotation proxy instances with a clone that additionaly implements
564
+ * the ConstantAnnotationMarker interface.
565
+ */
566
+ class AnnotationObjectReplacer implements Function <Object , Object > {
567
+
568
+ private final ClassLoader classLoader ;
569
+ /**
570
+ * Cache the replaced objects to ensure that they are only replaced once. We are using a
571
+ * concurrent hash map because replace() may be called from BigBang.finish(), which is
572
+ * multi-threaded.
573
+ *
574
+ * A side effect of this caching is de-duplication of annotation instances. When running as a
575
+ * native image two equal annotation instances are also identical. On HotSpot that is not true,
576
+ * the two annotation instances, although equal, are actually two distinct objects. Although
577
+ * this is a small deviation from HotSpot semantics it can improve the native image size.
578
+ *
579
+ * If de-duplication is not desired that can be achieved by replacing the ConcurrentHashMap with
580
+ * an IdentityHashMap (and additional access synchronisation).
581
+ */
582
+ private ConcurrentHashMap <Object , Object > objectCache = new ConcurrentHashMap <>();
583
+
584
+ AnnotationObjectReplacer (ClassLoader loader ) {
585
+ this .classLoader = loader ;
586
+ }
587
+
588
+ @ Override
589
+ public Object apply (Object original ) {
590
+ Class <?> clazz = original .getClass ();
591
+ if (Annotation .class .isAssignableFrom (clazz ) && Proxy .class .isAssignableFrom (clazz )) {
592
+ return objectCache .computeIfAbsent (original , obj -> replacementComputer (obj , classLoader ));
593
+ }
594
+
595
+ return original ;
596
+ }
597
+
598
+ /**
599
+ * Effectively clones the original proxy object and it adds the ConstantAnnotationMarker
600
+ * interface.
601
+ */
602
+ private static Object replacementComputer (Object original , ClassLoader classLoader ) {
603
+ Class <?>[] interfaces = original .getClass ().getInterfaces ();
604
+ Class <?>[] extendedInterfaces = Arrays .copyOf (interfaces , interfaces .length + 1 );
605
+ extendedInterfaces [extendedInterfaces .length - 1 ] = ConstantAnnotationMarker .class ;
606
+
607
+ return Proxy .newProxyInstance (classLoader , extendedInterfaces , Proxy .getInvocationHandler (original ));
608
+ }
609
+
610
+ }
0 commit comments