From 758e8cb38a8d3d35181681a9b1fe7d1428d1ce97 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 30 Jul 2021 14:29:20 +1000 Subject: [PATCH] New weak reflection implementation This implementation uses a reachability handler to only register the classes if they are reachable. --- .../nativeimage/ReflectiveClassBuildItem.java | 8 ++ .../steps/NativeImageAutoFeatureStep.java | 130 ++++++++++-------- .../quarkus/runtime/graal/WeakReflection.java | 55 ++++++++ 3 files changed, 132 insertions(+), 61 deletions(-) create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/graal/WeakReflection.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassBuildItem.java index 262751b00c3aa..3be5d46e253f9 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/ReflectiveClassBuildItem.java @@ -50,6 +50,14 @@ private ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean this.finalFieldsWritable = finalFieldsWritable; this.weak = weak; this.serialization = serialization; + if (weak) { + if (serialization) { + throw new RuntimeException("Weak reflection not supported with serialization"); + } + if (finalFieldsWritable) { + throw new RuntimeException("Weak reflection not supported with finalFieldsWritable"); + } + } } public ReflectiveClassBuildItem(boolean methods, boolean fields, String... className) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java index 3b933d2522c78..dacefc389d66c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageAutoFeatureStep.java @@ -52,6 +52,7 @@ import io.quarkus.gizmo.TryBlock; import io.quarkus.runtime.ResourceHelper; import io.quarkus.runtime.graal.ResourcesFeature; +import io.quarkus.runtime.graal.WeakReflection; public class NativeImageAutoFeatureStep { @@ -83,6 +84,9 @@ public class NativeImageAutoFeatureStep { static final String DYNAMIC_PROXY_REGISTRY = "com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry"; static final String LEGACY_LOCALIZATION_FEATURE = "com.oracle.svm.core.jdk.LocalizationFeature"; static final String LOCALIZATION_FEATURE = "com.oracle.svm.core.jdk.localization.LocalizationFeature"; + public static final MethodDescriptor WEAK_REFLECTION_REGISTRATION = MethodDescriptor.ofMethod(WeakReflection.class, + "register", void.class, Feature.BeforeAnalysisAccess.class, Class.class, boolean.class, boolean.class, + boolean.class); @BuildStep GeneratedResourceBuildItem generateNativeResourcesList(List resources, @@ -314,9 +318,9 @@ public void write(String s, byte[] bytes) { MethodDescriptor registerSerializationMethod = null; for (Map.Entry entry : reflectiveClasses.entrySet()) { - MethodCreator mv = file.getMethodCreator("registerClass" + count++, "V"); + MethodCreator mv = file.getMethodCreator("registerClass" + count++, void.class, Feature.BeforeAnalysisAccess.class); mv.setModifiers(Modifier.PRIVATE | Modifier.STATIC); - overallCatch.invokeStaticMethod(mv.getMethodDescriptor()); + overallCatch.invokeStaticMethod(mv.getMethodDescriptor(), overallCatch.getMethodParam(0)); TryBlock tc = mv.tryBlock(); @@ -339,73 +343,77 @@ public void write(String s, byte[] bytes) { tc.writeArrayValue(carray, 0, clazz); tc.invokeStaticMethod(ofMethod(RUNTIME_REFLECTION, "register", void.class, Class[].class), carray); - } - if (entry.getValue().constructors) { - tc.invokeStaticMethod( - ofMethod(RUNTIME_REFLECTION, "register", void.class, Executable[].class), - constructors); - } else if (!entry.getValue().ctorSet.isEmpty()) { - ResultHandle farray = tc.newArray(Constructor.class, tc.load(1)); - for (ReflectiveMethodBuildItem ctor : entry.getValue().ctorSet) { - ResultHandle paramArray = tc.newArray(Class.class, tc.load(ctor.getParams().length)); - for (int i = 0; i < ctor.getParams().length; ++i) { - String type = ctor.getParams()[i]; - tc.writeArrayValue(paramArray, i, tc.loadClass(type)); - } - ResultHandle fhandle = tc.invokeVirtualMethod( - ofMethod(Class.class, "getDeclaredConstructor", Constructor.class, Class[].class), clazz, - paramArray); - tc.writeArrayValue(farray, 0, fhandle); + if (entry.getValue().constructors) { tc.invokeStaticMethod( ofMethod(RUNTIME_REFLECTION, "register", void.class, Executable[].class), - farray); - } - } - if (entry.getValue().methods) { - tc.invokeStaticMethod( - ofMethod(RUNTIME_REFLECTION, "register", void.class, Executable[].class), - methods); - } else if (!entry.getValue().methodSet.isEmpty()) { - ResultHandle farray = tc.newArray(Method.class, tc.load(1)); - for (ReflectiveMethodBuildItem method : entry.getValue().methodSet) { - ResultHandle paramArray = tc.newArray(Class.class, tc.load(method.getParams().length)); - for (int i = 0; i < method.getParams().length; ++i) { - String type = method.getParams()[i]; - tc.writeArrayValue(paramArray, i, tc.loadClass(type)); + constructors); + } else if (!entry.getValue().ctorSet.isEmpty()) { + ResultHandle farray = tc.newArray(Constructor.class, tc.load(1)); + for (ReflectiveMethodBuildItem ctor : entry.getValue().ctorSet) { + ResultHandle paramArray = tc.newArray(Class.class, tc.load(ctor.getParams().length)); + for (int i = 0; i < ctor.getParams().length; ++i) { + String type = ctor.getParams()[i]; + tc.writeArrayValue(paramArray, i, tc.loadClass(type)); + } + ResultHandle fhandle = tc.invokeVirtualMethod( + ofMethod(Class.class, "getDeclaredConstructor", Constructor.class, Class[].class), clazz, + paramArray); + tc.writeArrayValue(farray, 0, fhandle); + tc.invokeStaticMethod( + ofMethod(RUNTIME_REFLECTION, "register", void.class, Executable[].class), + farray); } - ResultHandle fhandle = tc.invokeVirtualMethod( - ofMethod(Class.class, "getDeclaredMethod", Method.class, String.class, Class[].class), clazz, - tc.load(method.getName()), paramArray); - tc.writeArrayValue(farray, 0, fhandle); + } + if (entry.getValue().methods) { tc.invokeStaticMethod( ofMethod(RUNTIME_REFLECTION, "register", void.class, Executable[].class), - farray); + methods); + } else if (!entry.getValue().methodSet.isEmpty()) { + ResultHandle farray = tc.newArray(Method.class, tc.load(1)); + for (ReflectiveMethodBuildItem method : entry.getValue().methodSet) { + ResultHandle paramArray = tc.newArray(Class.class, tc.load(method.getParams().length)); + for (int i = 0; i < method.getParams().length; ++i) { + String type = method.getParams()[i]; + tc.writeArrayValue(paramArray, i, tc.loadClass(type)); + } + ResultHandle fhandle = tc.invokeVirtualMethod( + ofMethod(Class.class, "getDeclaredMethod", Method.class, String.class, Class[].class), clazz, + tc.load(method.getName()), paramArray); + tc.writeArrayValue(farray, 0, fhandle); + tc.invokeStaticMethod( + ofMethod(RUNTIME_REFLECTION, "register", void.class, Executable[].class), + farray); + } } - } - if (entry.getValue().fields) { - BranchResult graalVm21Test = tc.ifGreaterEqualZero( - tc.invokeVirtualMethod(VERSION_COMPARE_TO, - tc.invokeStaticMethod(VERSION_CURRENT), - tc.marshalAsArray(int.class, tc.load(21)))); - graalVm21Test.trueBranch().invokeStaticMethod( - ofMethod(RUNTIME_REFLECTION, "register", void.class, - boolean.class, boolean.class, Field[].class), - tc.load(entry.getValue().finalFieldsWritable), tc.load(entry.getValue().serialization), fields); - graalVm21Test.falseBranch().invokeStaticMethod( - ofMethod(RUNTIME_REFLECTION, "register", void.class, - boolean.class, Field[].class), - tc.load(entry.getValue().finalFieldsWritable), fields); - } else if (!entry.getValue().fieldSet.isEmpty()) { - ResultHandle farray = tc.newArray(Field.class, tc.load(1)); - for (String field : entry.getValue().fieldSet) { - ResultHandle fhandle = tc.invokeVirtualMethod( - ofMethod(Class.class, "getDeclaredField", Field.class, String.class), clazz, tc.load(field)); - tc.writeArrayValue(farray, 0, fhandle); - tc.invokeStaticMethod( - ofMethod(RUNTIME_REFLECTION, "register", void.class, Field[].class), - farray); + if (entry.getValue().fields) { + BranchResult graalVm21Test = tc.ifGreaterEqualZero( + tc.invokeVirtualMethod(VERSION_COMPARE_TO, + tc.invokeStaticMethod(VERSION_CURRENT), + tc.marshalAsArray(int.class, tc.load(21)))); + graalVm21Test.trueBranch().invokeStaticMethod( + ofMethod(RUNTIME_REFLECTION, "register", void.class, + boolean.class, boolean.class, Field[].class), + tc.load(entry.getValue().finalFieldsWritable), tc.load(entry.getValue().serialization), fields); + graalVm21Test.falseBranch().invokeStaticMethod( + ofMethod(RUNTIME_REFLECTION, "register", void.class, + boolean.class, Field[].class), + tc.load(entry.getValue().finalFieldsWritable), fields); + } else if (!entry.getValue().fieldSet.isEmpty()) { + ResultHandle farray = tc.newArray(Field.class, tc.load(1)); + for (String field : entry.getValue().fieldSet) { + ResultHandle fhandle = tc.invokeVirtualMethod( + ofMethod(Class.class, "getDeclaredField", Field.class, String.class), clazz, tc.load(field)); + tc.writeArrayValue(farray, 0, fhandle); + tc.invokeStaticMethod( + ofMethod(RUNTIME_REFLECTION, "register", void.class, Field[].class), + farray); + } } + } else { + tc.invokeStaticMethod(WEAK_REFLECTION_REGISTRATION, tc.getMethodParam(0), clazz, + tc.load(entry.getValue().constructors), tc.load(entry.getValue().methods), + tc.load(entry.getValue().fields)); } if (entry.getValue().serialization) { diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/WeakReflection.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/WeakReflection.java new file mode 100644 index 0000000000000..f4314777935ed --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/WeakReflection.java @@ -0,0 +1,55 @@ +package io.quarkus.runtime.graal; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeReflection; +import org.jboss.logging.Logger; + +/** + * Weak reflection implementation called from generated bytecode + */ +public class WeakReflection { + + static final Logger log = Logger.getLogger(WeakReflection.class); + + public static void register(Feature.BeforeAnalysisAccess analysisAccess, Class clazz, boolean constructors, + boolean methods, boolean fields) { + analysisAccess.registerReachabilityHandler(new Callback(clazz, constructors, methods, fields), clazz); + } + + public static class Callback implements Consumer { + final AtomicBoolean onlyOnce = new AtomicBoolean(false); + final Class clazz; + final boolean constructors; + final boolean methods; + final boolean fields; + + public Callback(Class clazz, boolean constructors, boolean methods, boolean fields) { + this.clazz = clazz; + this.constructors = constructors; + this.methods = methods; + this.fields = fields; + } + + @Override + public void accept(Feature.DuringAnalysisAccess duringAnalysisAccess) { + if (!onlyOnce.compareAndSet(false, true)) { + return; + } + log.debugf("Registering %s for reflection as it is reachable", clazz); + RuntimeReflection.register(clazz); + if (fields) { + RuntimeReflection.register(clazz.getDeclaredFields()); + } + if (constructors) { + RuntimeReflection.register(clazz.getDeclaredConstructors()); + } + if (methods) { + RuntimeReflection.register(clazz.getDeclaredMethods()); + } + } + } + +}