From 027d79b276a2be3bad227e980fa732f1cca9425c Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Tue, 4 Nov 2025 10:02:55 +0100 Subject: [PATCH 1/7] svm: add FeatureImpl#findTypeByName --- .../src/com/oracle/svm/hosted/FeatureImpl.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java index b346ba36f3ef..b2bbe178b541 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java @@ -243,6 +243,14 @@ public AnalysisMetaAccess getMetaAccess() { return bb.getMetaAccess(); } + public AnalysisType findTypeByName(String className) { + Class clazz = findClassByName(className); + if (clazz == null) { + return null; + } + return getMetaAccess().lookupJavaType(clazz); + } + public boolean isReachable(Class clazz) { return isReachable(getMetaAccess().lookupJavaType(clazz)); } From 7c8bd29aa731222b50b9162fa75c16b04896de39 Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Tue, 4 Nov 2025 13:07:12 +0100 Subject: [PATCH 2/7] svm: add FeatureImpl#findSubtypes --- .../src/com/oracle/svm/hosted/FeatureImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java index b2bbe178b541..279c5e3a58c0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java @@ -251,6 +251,10 @@ public AnalysisType findTypeByName(String className) { return getMetaAccess().lookupJavaType(clazz); } + public List findSubtypes(AnalysisType baseClass) { + return imageClassLoader.findSubclasses(baseClass.getJavaClass(), false).stream().map(getMetaAccess()::lookupJavaType).toList(); + } + public boolean isReachable(Class clazz) { return isReachable(getMetaAccess().lookupJavaType(clazz)); } From e03fe095a8dc460fbb91c2bf1da0600d2002ecc3 Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Tue, 4 Nov 2025 10:03:31 +0100 Subject: [PATCH 3/7] svm: add JVMCIReflectionUtil#getConstructors --- .../src/com/oracle/svm/util/JVMCIReflectionUtil.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java index 7a3954e870da..c6d7bfdcb016 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java @@ -32,6 +32,7 @@ import jdk.graal.compiler.debug.GraalError; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ModifiersProvider; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; @@ -134,6 +135,15 @@ public static ResolvedJavaMethod getDeclaredConstructor(MetaAccessProvider metaA return getDeclaredConstructor(false, metaAccess, declaringClass, parameterTypes); } + /** + * Gets the constructors declared by {@code declaringClass}. Like + * {@link Class#getConstructors()}, this only returns public constructors and does not consider + * super classes. + */ + public static ResolvedJavaMethod[] getConstructors(ResolvedJavaType declaringClass) { + return Arrays.stream(declaringClass.getDeclaredConstructors(false)).filter(ModifiersProvider::isPublic).toArray(ResolvedJavaMethod[]::new); + } + private static ResolvedJavaMethod findMethod(ResolvedJavaType declaringClass, ResolvedJavaMethod[] methods, String name, ResolvedJavaType... parameterTypes) { ResolvedJavaMethod res = null; for (ResolvedJavaMethod m : methods) { From fcf61bb2bd2776b96bf11b0130b4c7e77b911ff8 Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Thu, 6 Nov 2025 15:06:39 +0100 Subject: [PATCH 4/7] svm: add JVMCIReflectionUtil#getResolvedReturnType() --- .../oracle/svm/util/JVMCIReflectionUtil.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java index c6d7bfdcb016..8800517f0305 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/JVMCIReflectionUtil.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.util; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -158,7 +159,7 @@ private static ResolvedJavaMethod findMethod(ResolvedJavaType declaringClass, Re if (res == null) { res = m; } else { - throw new GraalError("More than one method with signature %s in %s", res.format("%h(%p)"), declaringClass.toClassName()); + throw new GraalError("More than one method with signature %s in %s", res.format("%H.%n(%p)"), declaringClass.toClassName()); } } return res; @@ -236,4 +237,18 @@ public static String getPackageName(ResolvedJavaType type) { int dot = cn.lastIndexOf('.'); return (dot != -1) ? cn.substring(0, dot).intern() : ""; } + + /** + * Gets the return type for a {@link ResolvedJavaMethod}. This is the same as calling + * {@link Method#getReturnType()} on the underlying method. + * + * @throws GraalError if the return type is not a {@link ResolvedJavaType} + */ + public static ResolvedJavaType getResolvedReturnType(ResolvedJavaMethod m) { + JavaType returnType = m.getSignature().getReturnType(m.getDeclaringClass()); + if (returnType instanceof ResolvedJavaType resolvedJavaType) { + return resolvedJavaType; + } + throw new GraalError("Method does not have a resolved return type: %s", m.format("%H.%n(%p)")); + } } From 8a3fc8788815307ae96878511bd9620bc3251fc1 Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Thu, 30 Oct 2025 10:10:16 +0100 Subject: [PATCH 5/7] svm: migrate SubstrateGraphBuilderPlugins to JVMCIReflection --- .../SubstrateGraphBuilderPlugins.java | 65 +++++++++++-------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java index f8c261280784..60dc46d9b35b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java @@ -45,7 +45,6 @@ import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.c.struct.SizeOf; import org.graalvm.nativeimage.hosted.RuntimeProxyCreation; -import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.hosted.RuntimeSerialization; import org.graalvm.word.Pointer; import org.graalvm.word.PointerBase; @@ -99,12 +98,14 @@ import com.oracle.svm.hosted.ReachabilityCallbackNode; import com.oracle.svm.hosted.SharedArenaSupport; import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; +import com.oracle.svm.hosted.dynamicaccess.JVMCIRuntimeReflection; import com.oracle.svm.hosted.dynamicaccessinference.DynamicAccessInferenceLog; import com.oracle.svm.hosted.dynamicaccessinference.StrictDynamicAccessInferenceFeature; import com.oracle.svm.hosted.nodes.DeoptProxyNode; import com.oracle.svm.hosted.nodes.ReadReservedRegister; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.util.AnnotationUtil; +import com.oracle.svm.util.JVMCIReflectionUtil; import com.oracle.svm.util.OriginalClassProvider; import com.oracle.svm.util.ReflectionUtil; @@ -297,6 +298,13 @@ public static T asConstantObject(GraphBuilderContext b, Class type, Value return StandardGraphBuilderPlugins.asConstantObject(b, type, node); } + public static ResolvedJavaType asConstantType(GraphBuilderContext b, ValueNode node) { + if (node instanceof ConstantNode constantNode && constantNode.getValue() instanceof JavaConstant javaConstant && javaConstant.isNonNull()) { + return b.getConstantReflection().asJavaType(javaConstant); + } + return null; + } + public static int asConstantIntegerOrMinusOne(ValueNode node) { if (node instanceof ConstantNode constantNode && constantNode.getValue() instanceof JavaConstant javaConstant) { return javaConstant.asInt(); @@ -739,18 +747,18 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec * them for reflection/unsafe access. */ private static void interceptUpdaterInvoke(GraphBuilderContext b, ValueNode tclassNode, ValueNode fieldNameNode) { - Class tclass = asConstantObject(b, Class.class, tclassNode); + ResolvedJavaType type = asConstantType(b, tclassNode); String fieldName = asConstantObject(b, String.class, fieldNameNode); - if (tclass != null && fieldName != null) { + if (type != null && fieldName != null) { try { - Field field = tclass.getDeclaredField(fieldName); + ResolvedJavaField field = JVMCIReflectionUtil.getDeclaredField(type, fieldName); /* * Register the holder class and the field for reflection. This also registers the * field for unsafe access. */ - RuntimeReflection.register(tclass); - RuntimeReflection.register(field); - } catch (NoSuchFieldException e) { + JVMCIRuntimeReflection.register(type); + JVMCIRuntimeReflection.register(field); + } catch (NoSuchFieldError e) { /* * Ignore the exception. If the field does not exist, there will be an error at run * time. That is then the same behavior as on HotSpot. The allocation of the @@ -791,17 +799,15 @@ private static void registerUnsafePlugins(InvocationPlugins plugins) { r.register(new RequiredInvocationPlugin("objectFieldOffset", Receiver.class, Class.class, String.class) { @Override public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode classNode, ValueNode nameNode) { - Class clazz = asConstantObject(b, Class.class, classNode); + ResolvedJavaType type = asConstantType(b, classNode); String fieldName = asConstantObject(b, String.class, nameNode); - if (clazz != null && fieldName != null) { - Field targetField; - try { - targetField = clazz.getDeclaredField(fieldName); - } catch (ReflectiveOperationException | LinkageError e) { - return false; + if (type != null && fieldName != null) { + ResolvedJavaField targetField = JVMCIReflectionUtil.getDeclaredField(false, type, fieldName); + if (targetField != null) { + return processFieldOffset(b, receiver, false, targetField); } - return processFieldOffset(b, receiver, targetField, false); } + /* A NullPointerException will be thrown at run time for this call. */ return false; } }); @@ -819,8 +825,10 @@ private static void registerUnsafePlugins(Registration r, boolean isSunMiscUnsaf public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode fieldNode) { Field targetField = asConstantObject(b, Field.class, fieldNode); if (targetField != null) { - return processFieldOffset(b, receiver, targetField, isSunMiscUnsafe); + ResolvedJavaField resolvedJavaField = b.getMetaAccess().lookupJavaField(targetField); + return processFieldOffset(b, receiver, isSunMiscUnsafe, resolvedJavaField); } + /* A NullPointerException will be thrown at run time for this call. */ return false; } }); @@ -840,8 +848,10 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode fieldNode) { Field targetField = asConstantObject(b, Field.class, fieldNode); if (targetField != null) { - return processFieldOffset(b, receiver, targetField, isSunMiscUnsafe); + ResolvedJavaField resolvedJavaField = b.getMetaAccess().lookupJavaField(targetField); + return processFieldOffset(b, receiver, isSunMiscUnsafe, resolvedJavaField); } + /* A NullPointerException will be thrown at run time for this call. */ return false; } }); @@ -869,8 +879,8 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec }); } - private static boolean processFieldOffset(GraphBuilderContext b, Receiver receiver, Field targetField, boolean isSunMiscUnsafe) { - if (!isValidField(targetField, isSunMiscUnsafe)) { + private static boolean processFieldOffset(GraphBuilderContext b, Receiver receiver, boolean isSunMiscUnsafe, ResolvedJavaField resolvedJavaField) { + if (!isValidField(resolvedJavaField, isSunMiscUnsafe)) { return false; } @@ -880,15 +890,11 @@ private static boolean processFieldOffset(GraphBuilderContext b, Receiver receiv * The static analysis registers the field for unsafe access if the node remains in the * graph until then. */ - b.addPush(JavaKind.Long, FieldOffsetNode.create(JavaKind.Long, b.getMetaAccess().lookupJavaField(targetField))); + b.addPush(JavaKind.Long, FieldOffsetNode.create(JavaKind.Long, resolvedJavaField)); return true; } - private static boolean isValidField(Field targetField, boolean isSunMiscUnsafe) { - if (targetField == null) { - /* A NullPointerException will be thrown at run time for this call. */ - return false; - } + private static boolean isValidField(ResolvedJavaField targetField, boolean isSunMiscUnsafe) { /* * sun.misc.Unsafe performs a few more checks than jdk.internal.misc.Unsafe to explicitly * disallow hidden classes and records. @@ -897,13 +903,18 @@ private static boolean isValidField(Field targetField, boolean isSunMiscUnsafe) } private static boolean processStaticFieldBase(GraphBuilderContext b, Receiver receiver, Field targetField, boolean isSunMiscUnsafe) { - if (!isValidField(targetField, isSunMiscUnsafe)) { + if (targetField == null) { + /* A NullPointerException will be thrown at run time for this call. */ + return false; + } + ResolvedJavaField resolvedJavaField = b.getMetaAccess().lookupJavaField(targetField); + if (!isValidField(resolvedJavaField, isSunMiscUnsafe)) { return false; } /* Emits a null-check for the otherwise unused receiver. */ receiver.get(true); - b.addPush(JavaKind.Object, StaticFieldsSupport.createStaticFieldBaseNode(b.getMetaAccess().lookupJavaField(targetField))); + b.addPush(JavaKind.Object, StaticFieldsSupport.createStaticFieldBaseNode(resolvedJavaField)); return true; } From d2f2ae6a8e24bec2076be6d5e47e1109e8d11028 Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Thu, 30 Oct 2025 12:04:26 +0100 Subject: [PATCH 6/7] svm: migrate Jmx*Feature to JVMCI reflection --- .../svm/hosted/jdk/JmxClientFeature.java | 39 +++++----- .../svm/hosted/jdk/JmxCommonFeature.java | 76 +++++++++++++++---- .../svm/hosted/jdk/JmxServerFeature.java | 20 ++--- 3 files changed, 93 insertions(+), 42 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxClientFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxClientFeature.java index ffb96409d6ca..0dfa077f57eb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxClientFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxClientFeature.java @@ -25,15 +25,17 @@ */ package com.oracle.svm.hosted.jdk; -import org.graalvm.nativeimage.hosted.RuntimeJNIAccess; -import org.graalvm.nativeimage.hosted.RuntimeReflection; - +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.VMInspectionOptions; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.jdk.JNIRegistrationUtil; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.util.ReflectionUtil; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.hosted.dynamicaccess.JVMCIRuntimeJNIAccess; +import com.oracle.svm.hosted.dynamicaccess.JVMCIRuntimeReflection; +import com.oracle.svm.util.JVMCIReflectionUtil; @AutomaticallyRegisteredFeature public class JmxClientFeature extends JNIRegistrationUtil implements InternalFeature { @@ -45,16 +47,19 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { try { - configureJNI(); - configureReflection(access); + BeforeAnalysisAccessImpl accessImpl = (BeforeAnalysisAccessImpl) access; + configureJNI(accessImpl); + configureReflection(accessImpl); } catch (Exception e) { throw VMError.shouldNotReachHere("ManagementClientFeature configuration failed: " + e); } } - private static void configureJNI() { - RuntimeJNIAccess.register(Boolean.class); - RuntimeJNIAccess.register(ReflectionUtil.lookupMethod(Boolean.class, "getBoolean", String.class)); + private static void configureJNI(BeforeAnalysisAccessImpl access) { + AnalysisMetaAccess metaAccess = access.getMetaAccess(); + AnalysisType type = metaAccess.lookupJavaType(Boolean.class); + JVMCIRuntimeJNIAccess.register(type); + JVMCIRuntimeJNIAccess.register(JVMCIReflectionUtil.getDeclaredMethod(metaAccess, type, "getBoolean", String.class)); } /** @@ -69,14 +74,14 @@ private static void configureJNI() { * {@code sun.rmi.server.UnicastRef#getRefClass(ObjectOutput)}. * */ - private static void configureReflection(BeforeAnalysisAccess access) { - RuntimeReflection.register(access.findClassByName("com.sun.jndi.url.rmi.rmiURLContextFactory")); - RuntimeReflection.register(access.findClassByName("sun.rmi.server.UnicastRef")); + private static void configureReflection(BeforeAnalysisAccessImpl access) { + JVMCIRuntimeReflection.register(access.findTypeByName("com.sun.jndi.url.rmi.rmiURLContextFactory")); + JVMCIRuntimeReflection.register(access.findTypeByName("sun.rmi.server.UnicastRef")); - RuntimeReflection.register(access.findClassByName("com.sun.jmx.remote.protocol.rmi.ClientProvider")); - RuntimeReflection.register(access.findClassByName("com.sun.jndi.url.rmi.rmiURLContextFactory").getConstructors()); - RuntimeReflection.register(access.findClassByName("sun.rmi.server.UnicastRef").getConstructors()); - RuntimeReflection.register(access.findClassByName("sun.rmi.server.UnicastRef2").getConstructors()); - RuntimeReflection.register(access.findClassByName("com.sun.jmx.remote.protocol.rmi.ClientProvider").getConstructors()); + JVMCIRuntimeReflection.register(access.findTypeByName("com.sun.jmx.remote.protocol.rmi.ClientProvider")); + JVMCIRuntimeReflection.register(JVMCIReflectionUtil.getConstructors(access.findTypeByName("com.sun.jndi.url.rmi.rmiURLContextFactory"))); + JVMCIRuntimeReflection.register(JVMCIReflectionUtil.getConstructors(access.findTypeByName("sun.rmi.server.UnicastRef"))); + JVMCIRuntimeReflection.register(JVMCIReflectionUtil.getConstructors(access.findTypeByName("sun.rmi.server.UnicastRef2"))); + JVMCIRuntimeReflection.register(JVMCIReflectionUtil.getConstructors(access.findTypeByName("com.sun.jmx.remote.protocol.rmi.ClientProvider"))); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxCommonFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxCommonFeature.java index 9ce2e3505e37..e3d455af573c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxCommonFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxCommonFeature.java @@ -30,15 +30,20 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.dynamicaccess.AccessCondition; -import org.graalvm.nativeimage.hosted.RuntimeJNIAccess; -import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.hosted.RuntimeSerialization; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.svm.core.VMInspectionOptions; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.hosted.dynamicaccess.JVMCIRuntimeJNIAccess; +import com.oracle.svm.hosted.dynamicaccess.JVMCIRuntimeReflection; import com.oracle.svm.hosted.reflect.proxy.ProxyRegistry; -import com.oracle.svm.util.ReflectionUtil; +import com.oracle.svm.util.JVMCIReflectionUtil; + +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; @AutomaticallyRegisteredFeature public class JmxCommonFeature implements InternalFeature { @@ -125,10 +130,11 @@ public void afterRegistration(AfterRegistrationAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - configureJNI(); - configureSerialization(access); - configureReflection(access); - configureProxy(access); + BeforeAnalysisAccessImpl accessImpl = (BeforeAnalysisAccessImpl) access; + configureJNI(accessImpl); + configureSerialization(accessImpl); + configureReflection(accessImpl); + configureProxy(accessImpl); } /** @@ -156,7 +162,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { * *

*/ - private static void configureProxy(BeforeAnalysisAccess access) { + private static void configureProxy(BeforeAnalysisAccessImpl access) { ProxyRegistry proxyRegistry = ImageSingletons.lookup(ProxyRegistry.class); proxyRegistry.registerProxy(AccessCondition.unconditional(), false, access.findClassByName("com.sun.management.GarbageCollectorMXBean"), access.findClassByName("javax.management.NotificationEmitter")); @@ -182,9 +188,11 @@ private static void configureProxy(BeforeAnalysisAccess access) { access.findClassByName("javax.management.NotificationEmitter")); } - private static void configureJNI() { - RuntimeJNIAccess.register(Arrays.class); - RuntimeJNIAccess.register(ReflectionUtil.lookupMethod(Arrays.class, "asList", Object[].class)); + private static void configureJNI(BeforeAnalysisAccessImpl access) { + AnalysisMetaAccess metaAccess = access.getMetaAccess(); + ResolvedJavaType type = metaAccess.lookupJavaType(Arrays.class); + JVMCIRuntimeJNIAccess.register(type); + JVMCIRuntimeJNIAccess.register(JVMCIReflectionUtil.getDeclaredMethod(metaAccess, type, "asList", Object[].class)); } /** @@ -202,7 +210,7 @@ private static void configureJNI() { * the remote JMX infrastructure (See {@code sun.management.MappedMXBeanType}, * {@code com.sun.jmx.mbeanserver.MXBeanMapping#makeOpenClass(Type, javax.management.openmbean.OpenType)}) */ - private static void configureSerialization(BeforeAnalysisAccess access) { + private static void configureSerialization(BeforeAnalysisAccessImpl access) { String[] classes = { "[B", "com.oracle.svm.core.jdk.UnsupportedFeatureError", "java.io.IOException", "java.lang.Boolean", "java.lang.ClassCastException", "java.lang.Error", @@ -281,7 +289,7 @@ private static void configureSerialization(BeforeAnalysisAccess access) { * {@code javax.management.remote.rmi.RMIConnectionImpl_Stub}. * */ - private static void configureReflection(BeforeAnalysisAccess access) { + private static void configureReflection(BeforeAnalysisAccessImpl access) { String[] classes = { "com.sun.management.internal.OperatingSystemImpl", "javax.management.remote.rmi.RMIConnectionImpl_Stub", @@ -311,13 +319,49 @@ private static void configureReflection(BeforeAnalysisAccess access) { }; for (String clazz : classes) { - RuntimeReflection.register(access.findClassByName(clazz)); + JVMCIRuntimeReflection.register(access.findTypeByName(clazz)); } for (String clazz : methods) { - RuntimeReflection.register(access.findClassByName(clazz).getMethods()); + registerMethods(access.findTypeByName(clazz)); } for (String clazz : constructors) { - RuntimeReflection.register(access.findClassByName(clazz).getConstructors()); + JVMCIRuntimeReflection.register(JVMCIReflectionUtil.getConstructors(access.findTypeByName(clazz))); + } + } + + /** + * Registers all public methods from the {@code declaringClass} and its super classes and + * interfaces for runtime reflection. + *

+ * Note: this code originally used {@link Class#getMethods()}, which has slightly different + * semantics. For methods with the same signature, it only returns the most specific one. This + * code registers all of them. This trades-off registering more methods than really needed for a + * much simpler algorithm. Should at some point {@link JVMCIReflectionUtil} or + * {@link ResolvedJavaType} get an implementation that behaves as {@link Class#getMethods()}, we + * might want to use it. + */ + private static void registerMethods(ResolvedJavaType declaringClass) { + if ("java.lang.Object".equals(declaringClass.toClassName())) { + /* + * Don't register java.lang.Object methods since those are not reflectively needed by + * JMX. This a deviation from Class#getMethods(). + */ + return; + } + // Start by fetching public declared methods... + for (ResolvedJavaMethod m : declaringClass.getDeclaredMethods(false)) { + if (m.isPublic()) { + JVMCIRuntimeReflection.register(m); + } + } + // ...then recur over superclass methods... + ResolvedJavaType sc = declaringClass.getSuperclass(); + if (sc != null) { + registerMethods(sc); + } + // ...and finally over direct superinterfaces. + for (ResolvedJavaType intf : declaringClass.getInterfaces()) { + registerMethods(intf); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxServerFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxServerFeature.java index ddbd6a33fe0c..29a7d70e908c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxServerFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JmxServerFeature.java @@ -35,7 +35,6 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.dynamicaccess.AccessCondition; -import org.graalvm.nativeimage.hosted.RuntimeReflection; import com.oracle.svm.configure.ResourcesRegistry; import com.oracle.svm.core.VMInspectionOptions; @@ -46,8 +45,12 @@ import com.oracle.svm.core.jdk.RuntimeSupport; import com.oracle.svm.core.jdk.management.ManagementAgentStartupHook; import com.oracle.svm.core.jdk.management.ManagementSupport; -import com.oracle.svm.hosted.reflect.proxy.ProxyRegistry; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.hosted.dynamicaccess.JVMCIRuntimeReflection; +import com.oracle.svm.hosted.reflect.proxy.ProxyRegistry; +import com.oracle.svm.util.JVMCIReflectionUtil; + +import jdk.vm.ci.meta.ResolvedJavaType; @AutomaticallyRegisteredFeature public class JmxServerFeature implements InternalFeature { @@ -71,7 +74,7 @@ private static void handleNativeLibraries(BeforeAnalysisAccess access) { public void beforeAnalysis(BeforeAnalysisAccess access) { handleNativeLibraries(access); registerJMXAgentResources(); - configureReflection(access); + configureReflection((BeforeAnalysisAccessImpl) access); configureProxy(access); RuntimeSupport.getRuntimeSupport().addStartupHook(new ManagementAgentStartupHook()); } @@ -107,7 +110,7 @@ private static void configureProxy(BeforeAnalysisAccess access) { * * */ - private static void configureReflection(BeforeAnalysisAccess access) { + private static void configureReflection(BeforeAnalysisAccessImpl access) { Set platformManagedObjects = ManagementSupport.getSingleton().getPlatformManagedObjects(); for (PlatformManagedObject p : platformManagedObjects) { // The platformManagedObjects list contains some PlatformManagedObjectSupplier objects @@ -115,12 +118,11 @@ private static void configureReflection(BeforeAnalysisAccess access) { if (p instanceof ManagementSupport.PlatformManagedObjectSupplier) { continue; } - Class clazz = p.getClass(); - RuntimeReflection.register(clazz); + JVMCIRuntimeReflection.register(access.getMetaAccess().lookupJavaType(p.getClass())); } - Class serviceProviderClass = access.findClassByName("com.sun.jmx.remote.protocol.rmi.ServerProvider"); - RuntimeReflection.register(serviceProviderClass); - RuntimeReflection.register(serviceProviderClass.getConstructors()); + ResolvedJavaType serviceProviderClass = access.findTypeByName("com.sun.jmx.remote.protocol.rmi.ServerProvider"); + JVMCIRuntimeReflection.register(serviceProviderClass); + JVMCIRuntimeReflection.register(JVMCIReflectionUtil.getConstructors(serviceProviderClass)); } } From 9ed4eaf9e0f39e7a4b7b4bef054199e394f69688 Mon Sep 17 00:00:00 2001 From: Josef Eisl Date: Tue, 4 Nov 2025 13:07:08 +0100 Subject: [PATCH 7/7] svm: migrate ScalaFeature to JVMCI reflection --- .../svm/polyglot/scala/ScalaFeature.java | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/substratevm/src/com.oracle.svm.polyglot/src/com/oracle/svm/polyglot/scala/ScalaFeature.java b/substratevm/src/com.oracle.svm.polyglot/src/com/oracle/svm/polyglot/scala/ScalaFeature.java index 450d4c4de5ce..e7c272816c08 100644 --- a/substratevm/src/com.oracle.svm.polyglot/src/com/oracle/svm/polyglot/scala/ScalaFeature.java +++ b/substratevm/src/com.oracle.svm.polyglot/src/com/oracle/svm/polyglot/scala/ScalaFeature.java @@ -24,23 +24,25 @@ */ package com.oracle.svm.polyglot.scala; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.Arrays; import java.util.function.BooleanSupplier; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; -import org.graalvm.nativeimage.hosted.RuntimeReflection; +import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.ParsingReason; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.util.UserError; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.hosted.dynamicaccess.JVMCIRuntimeReflection; +import com.oracle.svm.util.JVMCIReflectionUtil; import com.oracle.svm.util.ModuleSupport; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins; import jdk.graal.compiler.phases.util.Providers; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; public class ScalaFeature implements InternalFeature { @@ -76,8 +78,8 @@ public void registerGraphBuilderPlugins(Providers providers, Plugins plugins, Pa plugins.appendNodePlugin(new ScalaAnalysisPlugin()); } - private static boolean isValDef(Field[] fields, Method m) { - return Arrays.stream(fields).anyMatch(fd -> fd.getName().equals(m.getName()) && fd.getType().equals(m.getReturnType())); + private static boolean isValDef(ResolvedJavaField[] fields, ResolvedJavaMethod m) { + return Arrays.stream(fields).anyMatch(fd -> fd.getName().equals(m.getName()) && fd.getType().equals(JVMCIReflectionUtil.getResolvedReturnType(m))); } /** @@ -87,27 +89,28 @@ private static boolean isValDef(Field[] fields, Method m) { private static void initializeScalaEnumerations(BeforeAnalysisAccess beforeAnalysisAccess) { BeforeAnalysisAccessImpl access = (BeforeAnalysisAccessImpl) beforeAnalysisAccess; - Class scalaEnum = access.findClassByName("scala.Enumeration"); + AnalysisType scalaEnum = access.findTypeByName("scala.Enumeration"); UserError.guarantee(scalaEnum != null, "%s", UNSUPPORTED_SCALA_VERSION); - Class scalaEnumVal = access.findClassByName("scala.Enumeration$Val"); + AnalysisType scalaEnumVal = access.findTypeByName("scala.Enumeration$Val"); UserError.guarantee(scalaEnumVal != null, "%s", UNSUPPORTED_SCALA_VERSION); - Class valueClass = access.findClassByName("scala.Enumeration$Value"); + AnalysisType valueClass = access.findTypeByName("scala.Enumeration$Value"); UserError.guarantee(valueClass != null, "%s", UNSUPPORTED_SCALA_VERSION); - access.findSubclasses(scalaEnum).forEach(enumClass -> { + access.findSubtypes(scalaEnum).forEach(enumClass -> { /* this is based on implementation of scala.Enumeration.populateNamesMap */ - RuntimeReflection.registerAllDeclaredFields(enumClass); + JVMCIRuntimeReflection.registerAllDeclaredFields(enumClass); // all method relevant for Enums - Method[] relevantMethods = Arrays.stream(enumClass.getDeclaredMethods()) - .filter(m -> m.getParameterTypes().length == 0 && - m.getDeclaringClass() != scalaEnum && - valueClass.isAssignableFrom(m.getReturnType()) && - isValDef(enumClass.getDeclaredFields(), m)) - .toArray(Method[]::new); - RuntimeReflection.register(relevantMethods); + ResolvedJavaMethod[] relevantMethods = Arrays.stream(enumClass.getDeclaredMethods(false)) + .filter(m -> m.getParameters().length == 0 && + !scalaEnum.equals(m.getDeclaringClass()) && + valueClass.isAssignableFrom(JVMCIReflectionUtil.getResolvedReturnType(m)) && + (isValDef(enumClass.getInstanceFields(false), m) || + isValDef(enumClass.getStaticFields(), m))) + .toArray(ResolvedJavaMethod[]::new); + JVMCIRuntimeReflection.register(relevantMethods); try { - RuntimeReflection.register(scalaEnumVal.getDeclaredMethod("id")); - } catch (NoSuchMethodException e) { + JVMCIRuntimeReflection.register(JVMCIReflectionUtil.getDeclaredMethod(access.getMetaAccess(), scalaEnumVal, "id")); + } catch (NoSuchMethodError e) { throw UserError.abort("%s", UNSUPPORTED_SCALA_VERSION); } });