From 12062b70e4e44b331501cea29911927a05118328 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Wed, 4 Sep 2024 15:28:45 +0300 Subject: [PATCH 1/3] Extend reflection configuration for VertX Doesn't seem to fix any functionality issues but prevents `MissingRegistrationErrors` from being thrown when `ThrowMissingRegistrationErrors` is enabled. Related to quarkusio#41995 --- .../vertx/deployment/VertxProcessor.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java index 1c3d7fb2e9915..438add51a2f58 100644 --- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java @@ -54,7 +54,9 @@ import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.gizmo.ClassOutput; @@ -274,4 +276,20 @@ private Class tryLoad(String name, ClassLoader tccl) { throw new IllegalStateException("Unable to load type: " + name, e); } } + + @BuildStep + void registerNativeImageResources(BuildProducer resources) { + // Accessed by io.vertx.core.impl.VertxBuilder. + resources.produce(new NativeImageResourceBuildItem("META-INF/services/io.vertx.core.spi.VertxServiceProvider")); + // Accessed by io.vertx.core.impl.VertxImpl. + resources.produce(new NativeImageResourceBuildItem("META-INF/services/io.vertx.core.spi.VerticleFactory")); + } + + @BuildStep + void registerReflectivelyAccessedMethods(BuildProducer reflectiveMethods) { + // Accessed by io.vertx.core.impl.VertxImpl. + reflectiveMethods.produce(new ReflectiveMethodBuildItem("java.lang.Thread$Builder$OfVirtual", "name", + String.class, long.class)); + reflectiveMethods.produce(new ReflectiveMethodBuildItem("java.lang.Thread$Builder", "factory", new Class[0])); + } } From bc3917887216f12acfd873fb579e0d5c308e30d1 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Thu, 1 Aug 2024 12:24:18 +0300 Subject: [PATCH 2/3] Extend ReflectiveClassBuildItem with queryPublicMethods option Configures whether declared methods should be registered for reflection, for query purposes only, i.e. {@link Class#getMethods()}. Setting this enables getting all declared methods for the class but does not allow invoking them reflectively. Follow up to https://github.com/quarkusio/quarkus/pull/42035 --- .../nativeimage/ReflectiveClassBuildItem.java | 31 ++++++++++++++++--- .../steps/NativeImageReflectConfigStep.java | 8 +++++ 2 files changed, 34 insertions(+), 5 deletions(-) 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 ca4b0e37c7fdf..a58ea3fd70d7b 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 @@ -17,6 +17,7 @@ public final class ReflectiveClassBuildItem extends MultiBuildItem { private final List className; private final boolean methods; private final boolean queryMethods; + private final boolean queryPublicMethods; private final boolean fields; private final boolean classes; private final boolean constructors; @@ -46,7 +47,7 @@ public static Builder builder(String... classNames) { private ReflectiveClassBuildItem(boolean constructors, boolean queryConstructors, boolean methods, boolean queryMethods, boolean fields, boolean getClasses, boolean weak, boolean serialization, boolean unsafeAllocated, String reason, Class... classes) { - this(constructors, queryConstructors, methods, queryMethods, fields, getClasses, weak, serialization, + this(constructors, queryConstructors, methods, queryMethods, false, fields, getClasses, weak, serialization, unsafeAllocated, reason, stream(classes).map(Class::getName).toArray(String[]::new)); } @@ -118,12 +119,12 @@ public static ReflectiveClassBuildItem serializationClass(String... classNames) ReflectiveClassBuildItem(boolean constructors, boolean queryConstructors, boolean methods, boolean queryMethods, boolean fields, boolean weak, boolean serialization, boolean unsafeAllocated, String... className) { - this(constructors, queryConstructors, methods, queryMethods, fields, false, weak, serialization, unsafeAllocated, + this(constructors, queryConstructors, methods, queryMethods, false, fields, false, weak, serialization, unsafeAllocated, null, className); } ReflectiveClassBuildItem(boolean constructors, boolean queryConstructors, boolean methods, boolean queryMethods, - boolean fields, boolean classes, boolean weak, boolean serialization, + boolean queryPublicMethods, boolean fields, boolean classes, boolean weak, boolean serialization, boolean unsafeAllocated, String reason, String... className) { for (String i : className) { if (i == null) { @@ -140,6 +141,7 @@ public static ReflectiveClassBuildItem serializationClass(String... classNames) } else { this.queryMethods = queryMethods; } + this.queryPublicMethods = queryPublicMethods; this.fields = fields; this.classes = classes; this.constructors = constructors; @@ -169,6 +171,10 @@ public boolean isQueryMethods() { return queryMethods; } + public boolean isQueryPublicMethods() { + return queryPublicMethods; + } + public boolean isFields() { return fields; } @@ -216,6 +222,7 @@ public static class Builder { private boolean queryConstructors; private boolean methods; private boolean queryMethods; + private boolean queryPublicMethods; private boolean fields; private boolean classes; private boolean weak; @@ -284,6 +291,20 @@ public Builder queryMethods() { return queryMethods(true); } + /** + * Configures whether declared methods should be registered for reflection, for query purposes only, + * i.e. {@link Class#getMethods()}. Setting this enables getting all declared methods for the class but + * does not allow invoking them reflectively. + */ + public Builder queryPublicMethods(boolean queryPublicMethods) { + this.queryPublicMethods = queryPublicMethods; + return this; + } + + public Builder queryPublicMethods() { + return queryPublicMethods(true); + } + /** * Configures whether fields should be registered for reflection. * Setting this enables getting all declared fields for the class as well as accessing them reflectively. @@ -358,8 +379,8 @@ public Builder unsafeAllocated() { } public ReflectiveClassBuildItem build() { - return new ReflectiveClassBuildItem(constructors, queryConstructors, methods, queryMethods, fields, classes, weak, - serialization, unsafeAllocated, reason, className); + return new ReflectiveClassBuildItem(constructors, queryConstructors, methods, queryMethods, queryPublicMethods, + fields, classes, weak, serialization, unsafeAllocated, reason, className); } } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java index efe9a04ac4ddd..627df0728147f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/NativeImageReflectConfigStep.java @@ -107,6 +107,9 @@ void generateReflectConfig(BuildProducer reflectConf extractToJsonArray(info.queriedMethodSet, queriedMethodsArray); } } + if (info.queryPublicMethods) { + json.put("queryAllPublicMethods", true); + } if (!methodsArray.isEmpty()) { json.put("methods", methodsArray); } @@ -206,6 +209,9 @@ public void addReflectiveClass(Map reflectiveClasses, Se if (classBuildItem.isQueryMethods()) { existing.queryMethods = true; } + if (classBuildItem.isQueryPublicMethods()) { + existing.queryPublicMethods = true; + } if (classBuildItem.isFields()) { existing.fields = true; } @@ -249,6 +255,7 @@ static final class ReflectionInfo { boolean queryConstructors; boolean methods; boolean queryMethods; + boolean queryPublicMethods; boolean fields; boolean classes; boolean serialization; @@ -266,6 +273,7 @@ private ReflectionInfo() { private ReflectionInfo(ReflectiveClassBuildItem classBuildItem, String typeReachable) { this.methods = classBuildItem.isMethods(); this.queryMethods = classBuildItem.isQueryMethods(); + this.queryPublicMethods = classBuildItem.isQueryPublicMethods(); this.fields = classBuildItem.isFields(); this.classes = classBuildItem.isClasses(); this.typeReachable = typeReachable; From 9812460f66ab9adfbabef687d3ca0ef88f0801e7 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Tue, 30 Jul 2024 22:39:36 +0300 Subject: [PATCH 3/3] Extend reflection configuration for SmallRye FT Related to https://github.com/quarkusio/quarkus/issues/41995 --- .../SmallRyeFaultToleranceProcessor.java | 102 +++++++++++------- 1 file changed, 62 insertions(+), 40 deletions(-) diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java index 8ad9312182049..de2fe2223e5c4 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java @@ -1,7 +1,6 @@ package io.quarkus.smallrye.faulttolerance.deployment; import java.time.temporal.ChronoUnit; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -9,7 +8,6 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Queue; import java.util.Set; import jakarta.annotation.Priority; @@ -88,7 +86,6 @@ public void build(BuildProducer annotationsTran BuildProducer systemProperty, CombinedIndexBuildItem combinedIndexBuildItem, BuildProducer reflectiveClass, - BuildProducer reflectiveMethod, BuildProducer config, BuildProducer runtimeInitializedClassBuildItems) { @@ -120,43 +117,6 @@ public void build(BuildProducer annotationsTran } beans.produce(handlerBeans.build()); } - // Add reflective access to fallback methods - for (AnnotationInstance annotation : index.getAnnotations(DotNames.FALLBACK)) { - AnnotationValue fallbackMethodValue = annotation.value("fallbackMethod"); - if (fallbackMethodValue == null) { - continue; - } - String fallbackMethod = fallbackMethodValue.asString(); - - Queue classesToScan = new ArrayDeque<>(); // work queue - - // @Fallback can only be present on methods, so this is just future-proofing - AnnotationTarget target = annotation.target(); - if (target.kind() == Kind.METHOD) { - classesToScan.add(target.asMethod().declaringClass().name()); - } - - while (!classesToScan.isEmpty()) { - DotName name = classesToScan.poll(); - ClassInfo clazz = index.getClassByName(name); - if (clazz == null) { - continue; - } - - // we could further restrict the set of registered methods based on matching parameter types, - // but that's relatively complex and SmallRye Fault Tolerance has to do it anyway - clazz.methods() - .stream() - .filter(it -> fallbackMethod.equals(it.name())) - .forEach(it -> reflectiveMethod.produce(new ReflectiveMethodBuildItem(getClass().getName(), it))); - - DotName superClass = clazz.superName(); - if (superClass != null && !DotNames.OBJECT.equals(superClass)) { - classesToScan.add(superClass); - } - classesToScan.addAll(clazz.interfaceNames()); - } - } // Add reflective access to custom backoff strategies for (ClassInfo strategy : index.getAllKnownImplementors(DotNames.CUSTOM_BACKOFF_STRATEGY)) { reflectiveClass.produce(ReflectiveClassBuildItem.builder(strategy.name().toString()).methods().build()); @@ -260,6 +220,67 @@ public void transform(TransformationContext ctx) { }); } + @BuildStep + // Add reflective access to fallback methods, use bean index since fallback methods can be in bean classes + void processFallbackMethodsAndClases(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, + BuildProducer reflectiveClass, + BuildProducer reflectiveMethod) { + IndexView index = beanArchiveIndexBuildItem.getIndex(); + Map> classesToScan = new HashMap<>(); + + for (AnnotationInstance annotation : index.getAnnotations(DotNames.FALLBACK)) { + AnnotationValue fallbackMethodValue = annotation.value("fallbackMethod"); + if (fallbackMethodValue == null) { + continue; + } + String fallbackMethod = fallbackMethodValue.asString(); + + // @Fallback can only be present on methods, so this is just future-proofing + AnnotationTarget target = annotation.target(); + final ClassInfo clazz = target.asMethod().declaringClass(); + if (target.kind() != Kind.METHOD) { + continue; + } + + // Scan both the hierarchy of the declaring class and its (super)interfaces like in + // io.smallrye.faulttolerance.internal.SecurityActions.findDeclaredMethodNames + DotName name = clazz.name(); + while (name != null && !DotNames.OBJECT.equals(name)) { + classesToScan.computeIfAbsent(name, k1 -> new HashSet<>()).add(fallbackMethod); + clazz.interfaceNames() + .forEach(it -> classesToScan.computeIfAbsent(it, k -> new HashSet<>()).add(fallbackMethod)); + ClassInfo classInfo = index.getClassByName(name); + if (classInfo == null) { + break; + } + name = classInfo.superName(); + } + } + + for (Map.Entry> entry : classesToScan.entrySet()) { + DotName name = entry.getKey(); + Set methods = entry.getValue(); + ClassInfo classInfo = index.getClassByName(name); + + // io.smallrye.faulttolerance.config.FaultToleranceOperation.validate reflectively accesses methods and + // interfaces of the bean class and its superclasses through + // io.smallrye.faulttolerance.internal.SecurityActions.findDeclaredMethodNames + if (classInfo.isInterface()) { + // for interfaces getMethods() is invoked in addition to getDeclaredMethods() + reflectiveClass.produce(ReflectiveClassBuildItem.builder(name.toString()).queryPublicMethods().build()); + } + reflectiveClass.produce(ReflectiveClassBuildItem.builder(name.toString()).queryMethods().build()); + + // we could further restrict the set of registered methods based on matching parameter types, + // but that's relatively complex and SmallRye Fault Tolerance has to do it anyway + classInfo.methods() + .stream() + .filter(it -> methods.contains(it.name())) + .forEach(it -> reflectiveMethod.produce(new ReflectiveMethodBuildItem(it))); + } + + } + @BuildStep // needs to be RUNTIME_INIT because we need to read MP Config @Record(ExecutionTime.RUNTIME_INIT) @@ -288,6 +309,7 @@ void processFaultToleranceAnnotations(SmallRyeFaultToleranceRecorder recorder, AnnotationStore annotationStore = validationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE); IndexView index = beanArchiveIndexBuildItem.getIndex(); + // only generating annotation literal classes for MicroProfile/SmallRye Fault Tolerance annotations, // none of them are application classes ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, false);