Skip to content

Commit 1a68d88

Browse files
committed
[GR-14146] Preserve the JDK annotation implementation for run-time allocated annotations.
PullRequest: graal/3412
2 parents 9daab68 + d2d4b2d commit 1a68d88

File tree

6 files changed

+198
-27
lines changed

6 files changed

+198
-27
lines changed

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/Inflation.java

+11
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import com.oracle.svm.hosted.NativeImageClassLoader;
7676
import com.oracle.svm.hosted.SVMHost;
7777
import com.oracle.svm.hosted.analysis.flow.SVMMethodTypeFlowBuilder;
78+
import com.oracle.svm.hosted.meta.HostedType;
7879
import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor;
7980

8081
import jdk.vm.ci.common.JVMCIError;
@@ -612,6 +613,16 @@ private static boolean isAnnotationUsed(AnalysisType annotationType) {
612613
return annotationInterfaceType.isInstantiated() || annotationInterfaceType.isInTypeCheck();
613614
}
614615

616+
public static ResolvedJavaType toWrappedType(ResolvedJavaType type) {
617+
if (type instanceof AnalysisType) {
618+
return ((AnalysisType) type).getWrappedWithoutResolve();
619+
} else if (type instanceof HostedType) {
620+
return ((HostedType) type).getWrapped().getWrappedWithoutResolve();
621+
} else {
622+
return type;
623+
}
624+
}
625+
615626
@Override
616627
public boolean trackConcreteAnalysisObjects(AnalysisType type) {
617628
/*

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationSubstitutionField.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,7 @@ public JavaConstant readValue(JavaConstant receiver) {
110110
* i.e., `jdk.proxy1`, cannot be open to all-unnamed-modules like we do with other
111111
* modules.
112112
*/
113-
Class<?>[] interfaces = proxy.getClass().getInterfaces();
114-
assert interfaces.length == 1 : "Unexpected number of interfaces for annotation proxy class.";
115-
Class<?> annotationInterface = interfaces[0];
113+
Class<?> annotationInterface = AnnotationSupport.findAnnotationInterfaceTypeForMarkedAnnotationType(proxy.getClass());
116114
Method reflectionMethod = annotationInterface.getDeclaredMethod(accessorMethod.getName());
117115
reflectionMethod.setAccessible(true);
118116

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationSubstitutionType.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,24 @@
2626

2727
import java.lang.annotation.Annotation;
2828
import java.lang.reflect.Proxy;
29+
import java.util.Arrays;
2930

3031
import jdk.vm.ci.meta.MetaAccessProvider;
3132
import jdk.vm.ci.meta.ResolvedJavaType;
3233

3334
public class AnnotationSubstitutionType extends CustomSubstitutionType<AnnotationSubstitutionField, AnnotationSubstitutionMethod> {
3435

3536
private final String name;
37+
private final MetaAccessProvider metaAccess;
3638

3739
public AnnotationSubstitutionType(MetaAccessProvider metaAccess, ResolvedJavaType original) {
3840
super(original);
41+
this.metaAccess = metaAccess;
3942

4043
assert original.getSuperclass().equals(metaAccess.lookupJavaType(Proxy.class));
4144
assert metaAccess.lookupJavaType(Annotation.class).isAssignableFrom(original);
4245

43-
ResolvedJavaType annotationInterfaceType = AnnotationSupport.findAnnotationInterfaceType(original);
46+
ResolvedJavaType annotationInterfaceType = AnnotationSupport.findAnnotationInterfaceTypeForMarkedAnnotationType(original, metaAccess);
4447
assert annotationInterfaceType.isAssignableFrom(original);
4548
assert metaAccess.lookupJavaType(Annotation.class).isAssignableFrom(annotationInterfaceType);
4649

@@ -49,6 +52,17 @@ public AnnotationSubstitutionType(MetaAccessProvider metaAccess, ResolvedJavaTyp
4952
name = n.substring(0, n.length() - 1) + "$$ProxyImpl;";
5053
}
5154

55+
@Override
56+
public ResolvedJavaType[] getInterfaces() {
57+
/* Filter out the ConstantAnnotationMarker interface. */
58+
ResolvedJavaType[] interfaces = super.getInterfaces();
59+
return Arrays.stream(interfaces).filter(this::isNotAnnotationMarkerInterface).toArray(ResolvedJavaType[]::new);
60+
}
61+
62+
private boolean isNotAnnotationMarkerInterface(ResolvedJavaType type) {
63+
return !type.equals(metaAccess.lookupJavaType(ConstantAnnotationMarker.class));
64+
}
65+
5266
@Override
5367
public String getName() {
5468
return name;

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationSupport.java

+120-10
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626

2727
import java.lang.annotation.Annotation;
2828
import java.lang.reflect.Modifier;
29+
import java.lang.reflect.Proxy;
2930
import java.util.Arrays;
31+
import java.util.concurrent.ConcurrentHashMap;
32+
import java.util.function.Function;
3033

3134
import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
3235
import org.graalvm.compiler.core.common.type.StampFactory;
@@ -56,14 +59,19 @@
5659
import org.graalvm.compiler.replacements.GraphKit;
5760
import org.graalvm.compiler.replacements.nodes.BasicObjectCloneNode;
5861
import org.graalvm.nativeimage.ImageSingletons;
62+
import org.graalvm.nativeimage.hosted.Feature;
5963

6064
import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException;
6165
import com.oracle.graal.pointsto.meta.HostedProviders;
66+
import com.oracle.svm.core.annotate.AutomaticFeature;
6267
import com.oracle.svm.core.annotate.Substitute;
6368
import com.oracle.svm.core.annotate.TargetClass;
6469
import com.oracle.svm.core.hub.AnnotationTypeSupport;
6570
import com.oracle.svm.core.jdk.AnnotationSupportConfig;
6671
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;
6775
import com.oracle.svm.hosted.phases.HostedGraphKit;
6876
import com.oracle.svm.hosted.snippets.SubstrateGraphBuilderPlugins;
6977

@@ -85,24 +93,35 @@ public class AnnotationSupport extends CustomSubstitution<AnnotationSubstitution
8593

8694
private final ResolvedJavaType javaLangAnnotationAnnotation;
8795
private final ResolvedJavaType javaLangReflectProxy;
96+
private final ResolvedJavaType constantAnnotationMarker;
8897

8998
public AnnotationSupport(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection) {
9099
super(metaAccess);
91100
this.snippetReflection = snippetReflection;
92101

93102
javaLangAnnotationAnnotation = metaAccess.lookupJavaType(java.lang.annotation.Annotation.class);
94103
javaLangReflectProxy = metaAccess.lookupJavaType(java.lang.reflect.Proxy.class);
104+
constantAnnotationMarker = metaAccess.lookupJavaType(ConstantAnnotationMarker.class);
95105

96106
AnnotationSupportConfig.initialize();
97107
}
98108

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);
101120
}
102121

103122
@Override
104123
public ResolvedJavaType lookup(ResolvedJavaType type) {
105-
if (isAnnotation(type)) {
124+
if (isConstantAnnotationType(type)) {
106125
return getSubstitution(type);
107126
}
108127
return type;
@@ -118,15 +137,15 @@ public ResolvedJavaType resolve(ResolvedJavaType type) {
118137

119138
@Override
120139
public ResolvedJavaField lookup(ResolvedJavaField field) {
121-
if (isAnnotation(field.getDeclaringClass())) {
140+
if (isConstantAnnotationType(field.getDeclaringClass())) {
122141
throw new UnsupportedFeatureException("Field of annotation proxy is not accessible: " + field);
123142
}
124143
return field;
125144
}
126145

127146
@Override
128147
public ResolvedJavaMethod lookup(ResolvedJavaMethod method) {
129-
if (isAnnotation(method.getDeclaringClass())) {
148+
if (isConstantAnnotationType(method.getDeclaringClass())) {
130149
AnnotationSubstitutionType declaringClass = getSubstitution(method.getDeclaringClass());
131150
AnnotationSubstitutionMethod result = declaringClass.getSubstitutionMethod(method);
132151
assert result != null && result.original.equals(method);
@@ -286,8 +305,8 @@ static class AnnotationAnnotationTypeMethod extends AnnotationSubstitutionMethod
286305

287306
@Override
288307
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);
291310
JavaConstant returnValue = providers.getConstantReflection().asJavaClass(annotationInterfaceType);
292311

293312
GraphKit kit = new HostedGraphKit(debug, providers, method);
@@ -473,11 +492,42 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method,
473492
}
474493
}
475494

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];
479506
}
480507

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+
}
481531
}
482532

483533
@TargetClass(className = "sun.reflect.annotation.AnnotationType")
@@ -498,3 +548,63 @@ public static AnnotationType getInstance(Class<? extends Annotation> annotationC
498548
}
499549

500550
}
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.hosted.annotation;
26+
27+
import org.graalvm.nativeimage.Platform;
28+
import org.graalvm.nativeimage.Platforms;
29+
30+
/**
31+
* This interface is used to mark the ahead-of-time allocated annotation proxy objects. These
32+
* objects have an optimized type which removes the overhead of storing the annotation values in a
33+
* HashMap. See AnnotationSupport#getSubstitution(ResolvedJavaType) for the logic where this
34+
* substitution is implemented. This is possible since the ahead-of-time allocated annotation proxy
35+
* objects are effectively constant.
36+
*
37+
* The {@link ConstantAnnotationMarker} is removed before runtime. See
38+
* {@link AnnotationSubstitutionType#getInterfaces()}. This is also enforced through the HOSTED_ONLY
39+
* annotation below. No {@link ConstantAnnotationMarker} is allowed in the image heap.
40+
*
41+
* The run-time allocated annotations use the default JDK implementation.
42+
*
43+
* The downside of having a separate (more efficient) implementation for ahead-of-time allocated
44+
* annotations is that at run time there can be two proxy types for the same annotation type and an
45+
* equality check between them would fail.
46+
*/
47+
@Platforms(Platform.HOSTED_ONLY.class)
48+
public interface ConstantAnnotationMarker {
49+
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java

+2-13
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
import com.oracle.svm.core.util.UserError;
5252
import com.oracle.svm.hosted.FeatureImpl;
5353
import com.oracle.svm.hosted.NativeImageOptions;
54-
import com.oracle.svm.hosted.meta.HostedType;
54+
import com.oracle.svm.hosted.analysis.Inflation;
5555
import com.oracle.svm.hosted.meta.MethodPointer;
5656
import com.oracle.svm.hosted.phases.SubstrateClassInitializationPlugin;
5757

@@ -281,23 +281,12 @@ static boolean declaresDefaultMethods(ResolvedJavaType type) {
281281
* We call getDeclaredMethods() directly on the wrapped type. We avoid calling it on the
282282
* AnalysisType because it resolves all the methods in the AnalysisUniverse.
283283
*/
284-
for (ResolvedJavaMethod method : toWrappedType(type).getDeclaredMethods()) {
284+
for (ResolvedJavaMethod method : Inflation.toWrappedType(type).getDeclaredMethods()) {
285285
if (method.isDefault()) {
286286
assert !Modifier.isStatic(method.getModifiers()) : "Default method that is static?";
287287
return true;
288288
}
289289
}
290290
return false;
291291
}
292-
293-
private static ResolvedJavaType toWrappedType(ResolvedJavaType type) {
294-
if (type instanceof AnalysisType) {
295-
return ((AnalysisType) type).getWrappedWithoutResolve();
296-
} else if (type instanceof HostedType) {
297-
return ((HostedType) type).getWrapped().getWrappedWithoutResolve();
298-
} else {
299-
return type;
300-
}
301-
}
302-
303292
}

0 commit comments

Comments
 (0)