diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index cf6fe7e14ab9..4d94278f9db7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -72,9 +72,6 @@ import java.util.Optional; import java.util.StringJoiner; -import com.oracle.svm.core.reflect.fieldaccessor.UnsafeFieldAccessorFactory; -import jdk.internal.access.JavaLangReflectAccess; -import jdk.internal.reflect.FieldAccessor; import org.graalvm.compiler.core.common.NumUtil; import org.graalvm.compiler.core.common.SuppressFBWarnings; import org.graalvm.nativeimage.AnnotationAccess; @@ -114,13 +111,16 @@ import com.oracle.svm.core.reflect.ReflectionMetadataDecoder.MethodDescriptor; import com.oracle.svm.core.reflect.Target_java_lang_reflect_RecordComponent; import com.oracle.svm.core.reflect.Target_jdk_internal_reflect_ConstantPool; +import com.oracle.svm.core.reflect.fieldaccessor.UnsafeFieldAccessorFactory; import com.oracle.svm.core.util.LazyFinalReference; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError; +import jdk.internal.access.JavaLangReflectAccess; import jdk.internal.misc.Unsafe; import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.FieldAccessor; import jdk.internal.reflect.Reflection; import jdk.internal.reflect.ReflectionFactory; import sun.reflect.annotation.AnnotationType; @@ -246,13 +246,16 @@ public final class DynamicHub implements AnnotatedElement, java.lang.reflect.Typ */ private static final int IS_LAMBDA_FORM_HIDDEN_BIT = 9; + /** Similar to {@link #flags}, but non-final because {@link #setData} sets the value. */ @UnknownPrimitiveField(availability = AfterHostedUniverse.class)// - private byte instantiationFlags; + private byte additionalFlags; /** Has the type been discovered as instantiated by the static analysis? */ private static final int IS_INSTANTIATED_BIT = 0; /** Can this class be instantiated as an instance. */ private static final int CAN_INSTANTIATE_AS_INSTANCE_BIT = 1; + /** Is the class a proxy class according to {@link java.lang.reflect.Proxy#isProxyClass}? */ + private static final int IS_PROXY_CLASS_BIT = 2; /** * The {@link Modifier modifiers} of this class. @@ -427,7 +430,7 @@ public void setClassInitializationInfo(ClassInitializationInfo classInitializati @Platforms(Platform.HOSTED_ONLY.class) public void setData(int layoutEncoding, int typeID, int monitorOffset, int optionalIdentityHashOffset, short typeCheckStart, short typeCheckRange, short typeCheckSlot, - short[] typeCheckSlots, CFunctionPointer[] vtable, long referenceMapIndex, boolean isInstantiated, boolean canInstantiateAsInstance) { + short[] typeCheckSlots, CFunctionPointer[] vtable, long referenceMapIndex, boolean isInstantiated, boolean canInstantiateAsInstance, boolean isProxyClass) { assert this.vtable == null : "Initialization must be called only once"; assert !(!isInstantiated && canInstantiateAsInstance); if (LayoutEncoding.isPureInstance(layoutEncoding)) { @@ -451,7 +454,9 @@ public void setData(int layoutEncoding, int typeID, int monitorOffset, int optio throw VMError.shouldNotReachHere("Reference map index not within integer range, need to switch field from int to long"); } this.referenceMapIndex = (int) referenceMapIndex; - this.instantiationFlags = NumUtil.safeToUByte(makeFlag(IS_INSTANTIATED_BIT, isInstantiated) | makeFlag(CAN_INSTANTIATE_AS_INSTANCE_BIT, canInstantiateAsInstance)); + this.additionalFlags = NumUtil.safeToUByte(makeFlag(IS_INSTANTIATED_BIT, isInstantiated) | + makeFlag(CAN_INSTANTIATE_AS_INSTANCE_BIT, canInstantiateAsInstance) | + makeFlag(IS_PROXY_CLASS_BIT, isProxyClass)); } @Platforms(Platform.HOSTED_ONLY.class) @@ -641,11 +646,15 @@ public int getReferenceMapIndex() { } public boolean isInstantiated() { - return isFlagSet(instantiationFlags, IS_INSTANTIATED_BIT); + return isFlagSet(additionalFlags, IS_INSTANTIATED_BIT); } public boolean canInstantiateAsInstance() { - return isFlagSet(instantiationFlags, CAN_INSTANTIATE_AS_INSTANCE_BIT); + return isFlagSet(additionalFlags, CAN_INSTANTIATE_AS_INSTANCE_BIT); + } + + public boolean isProxyClass() { + return isFlagSet(additionalFlags, IS_PROXY_CLASS_BIT); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java index 57750a6da521..4ebaaec3b8f2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java @@ -26,21 +26,24 @@ import java.lang.reflect.InvocationHandler; import java.util.Arrays; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.EconomicSet; +import org.graalvm.collections.Equivalence; import org.graalvm.compiler.debug.GraalError; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import org.graalvm.nativeimage.hosted.RuntimeReflection; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.PredefinedClassesSupport; import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; import com.oracle.svm.core.option.SubstrateOptionsParser; +import com.oracle.svm.core.util.ImageHeapMap; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.LogUtils; @@ -82,71 +85,78 @@ public String toString() { } } - private final Map proxyCache; + private final EconomicSet> hostedProxyClasses = EconomicSet.create(Equivalence.IDENTITY); + private final EconomicMap proxyCache = ImageHeapMap.create(); + @Platforms(Platform.HOSTED_ONLY.class) public DynamicProxySupport() { - this.proxyCache = new ConcurrentHashMap<>(); } - @Platforms(Platform.HOSTED_ONLY.class) @Override - public void addProxyClass(Class... interfaces) { + @Platforms(Platform.HOSTED_ONLY.class) + public synchronized void addProxyClass(Class... interfaces) { /* * Make a defensive copy of the interfaces array to protect against the caller modifying the * array. */ - final Class[] intfs = interfaces.clone(); + Class[] intfs = interfaces.clone(); ProxyCacheKey key = new ProxyCacheKey(intfs); - proxyCache.computeIfAbsent(key, k -> { - try { - Class clazz = createProxyClassFromImplementedInterfaces(intfs); - - boolean isPredefinedProxy = Arrays.stream(interfaces).anyMatch(PredefinedClassesSupport::isPredefined); + if (!proxyCache.containsKey(key)) { + proxyCache.put(key, createProxyClass(intfs)); + } + } - if (isPredefinedProxy) { - /* - * Treat the proxy as a predefined class so that we can set its class loader to - * the loader passed at runtime. If one of the interfaces is a predefined class, - * this can be required so that the classes can actually see each other - * according to the runtime class loader hierarchy. - */ - PredefinedClassesSupport.registerClass(clazz); - RuntimeClassInitialization.initializeAtRunTime(clazz); - } + @Platforms(Platform.HOSTED_ONLY.class) + private Object createProxyClass(Class[] interfaces) { + try { + Class clazz = createProxyClassFromImplementedInterfaces(interfaces); + boolean isPredefinedProxy = Arrays.stream(interfaces).anyMatch(PredefinedClassesSupport::isPredefined); + if (isPredefinedProxy) { /* - * The constructor of the generated dynamic proxy class that takes a - * `java.lang.reflect.InvocationHandler` argument, i.e., the one reflectively - * invoked by `java.lang.reflect.Proxy.newProxyInstance(ClassLoader, Class[], - * InvocationHandler)`, is registered for reflection so that dynamic proxy instances - * can be allocated at run time. + * Treat the proxy as a predefined class so that we can set its class loader to the + * loader passed at runtime. If one of the interfaces is a predefined class, this + * can be required so that the classes can actually see each other according to the + * runtime class loader hierarchy. */ - RuntimeReflection.register(ReflectionUtil.lookupConstructor(clazz, InvocationHandler.class)); + PredefinedClassesSupport.registerClass(clazz); + RuntimeClassInitialization.initializeAtRunTime(clazz); + } - /* - * The proxy class reflectively looks up the methods of the interfaces it implements - * to pass a Method object to InvocationHandler. - */ - for (Class intf : intfs) { - RuntimeReflection.register(intf.getMethods()); - } - - return clazz; - } catch (Throwable t) { - LogUtils.warning("Could not create a proxy class from list of interfaces: %s. Reason: %s", Arrays.toString(interfaces), t.getMessage()); - return t; + /* + * The constructor of the generated dynamic proxy class that takes a + * `java.lang.reflect.InvocationHandler` argument, i.e., the one reflectively invoked by + * `java.lang.reflect.Proxy.newProxyInstance(ClassLoader, Class[], + * InvocationHandler)`, is registered for reflection so that dynamic proxy instances can + * be allocated at run time. + */ + RuntimeReflection.register(ReflectionUtil.lookupConstructor(clazz, InvocationHandler.class)); + + /* + * The proxy class reflectively looks up the methods of the interfaces it implements to + * pass a Method object to InvocationHandler. + */ + for (Class intf : interfaces) { + RuntimeReflection.register(intf.getMethods()); } - }); + + hostedProxyClasses.add(clazz); + return clazz; + } catch (Throwable t) { + LogUtils.warning("Could not create a proxy class from list of interfaces: %s. Reason: %s", Arrays.toString(interfaces), t.getMessage()); + return t; + } } @Override + @Platforms(Platform.HOSTED_ONLY.class) public Class createProxyClassForSerialization(Class... interfaces) { final Class[] intfs = interfaces.clone(); - return createProxyClassFromImplementedInterfaces(intfs); } + @Platforms(Platform.HOSTED_ONLY.class) private static Class createProxyClassFromImplementedInterfaces(Class[] interfaces) { return getJdkProxyClass(getCommonClassLoaderOrFail(null, interfaces), interfaces); } @@ -228,7 +238,15 @@ private static void describeLoaderChain(StringBuilder b, ClassLoader loader) { @Override public boolean isProxyClass(Class clazz) { - return proxyCache.containsValue(clazz); + if (SubstrateUtil.HOSTED) { + return isHostedProxyClass(clazz); + } + return DynamicHub.fromClass(clazz).isProxyClass(); + } + + @Platforms(Platform.HOSTED_ONLY.class) + private synchronized boolean isHostedProxyClass(Class clazz) { + return hostedProxyClasses.contains(clazz); } @SuppressWarnings("deprecation") diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java index 6935572b84ee..6c0d6cf89167 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java @@ -84,6 +84,7 @@ import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.DynamicHubSupport; import com.oracle.svm.core.hub.LayoutEncoding; +import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; import com.oracle.svm.core.meta.MethodPointer; import com.oracle.svm.core.reflect.SubstrateConstructorAccessor; import com.oracle.svm.core.reflect.SubstrateMethodAccessor; @@ -1065,9 +1066,11 @@ private void buildHubs() { assert ((SubstrateReferenceMap) referenceMap).hasNoDerivedOffsets(); long referenceMapIndex = referenceMapEncoder.lookupEncoding(referenceMap); + boolean isProxyClass = ImageSingletons.lookup(DynamicProxyRegistry.class).isProxyClass(type.getJavaClass()); + DynamicHub hub = type.getHub(); hub.setData(layoutHelper, type.getTypeID(), monitorOffset, optionalIdHashOffset, type.getTypeCheckStart(), type.getTypeCheckRange(), - type.getTypeCheckSlot(), type.getTypeCheckSlots(), vtable, referenceMapIndex, type.isInstantiated(), canInstantiateAsInstance); + type.getTypeCheckSlot(), type.getTypeCheckSlots(), vtable, referenceMapIndex, type.isInstantiated(), canInstantiateAsInstance, isProxyClass); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java index f41d82e818c8..3caa69ae79f8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java @@ -35,10 +35,10 @@ import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ProxyConfigurationParser; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; import com.oracle.svm.core.reflect.proxy.DynamicProxySupport; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.hosted.ConfigurationTypeResolver; import com.oracle.svm.hosted.FallbackFeature; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl;