From fd48630e1e28a13731d3268d6e2cc0ff6492b0be Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 3 May 2023 09:58:42 +0200 Subject: [PATCH] ArC - static method interception fix - fix the problem with dev mode and external libraries - also make sure the generated method does not start with a number - fixes #32990 --- .../InterceptedStaticMethodBuildItem.java | 8 ++++ .../InterceptedStaticMethodsProcessor.java | 43 +++++++++++++++---- .../InterceptedStaticMethodsRecorder.java | 1 + 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java index 991405d1cfd42..bc565c4bf15cd 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java @@ -60,4 +60,12 @@ public String getHash() { return hash; } + /** + * + * @return the name of the generated forwarding method + */ + public String getForwardingMethodName() { + return "_" + hash; + } + } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java index 6e5f32ac07e65..0e20e735c60e2 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java @@ -17,6 +17,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.function.BiFunction; +import java.util.function.Predicate; import java.util.stream.Collectors; import jakarta.enterprise.context.spi.Contextual; @@ -39,6 +40,7 @@ import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem; +import io.quarkus.arc.deployment.CompletedApplicationClassPredicateBuildItem; import io.quarkus.arc.deployment.InterceptorResolverBuildItem; import io.quarkus.arc.deployment.TransformedAnnotationsBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; @@ -146,6 +148,7 @@ void collectInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, BeanRegistrationPhaseBuildItem phase, List interceptedStaticMethods, + CompletedApplicationClassPredicateBuildItem applicationClassPredicate, BuildProducer generatedClasses, BuildProducer transformers, BuildProducer reflectiveMethods) { @@ -154,7 +157,27 @@ void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, return; } - ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, true); + // org.acme.Foo -> org.acme.Foo_InterceptorInitializer + Map baseToGeneratedInitializer = new HashMap<>(); + ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, new Predicate() { + + @Override + public boolean test(String name) { + // For example, for base org.acme.Foo we generate org.acme.Foo_InterceptorInitializer + // and possibly anonymous classes like org.acme.Foo_InterceptorInitializer$$function$$1 + name = name.replace('/', '.'); + DotName base = null; + for (Entry e : baseToGeneratedInitializer.entrySet()) { + if (e.getValue().equals(name) || name.startsWith(e.getValue())) { + base = e.getKey(); + } + } + if (base == null) { + throw new IllegalStateException("Unable to find the base class for the generated: " + name); + } + return applicationClassPredicate.test(base); + } + }); // declaring class -> intercepted static methods Map> interceptedStaticMethodsMap = new HashMap<>(); @@ -172,14 +195,14 @@ void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, // 1. registers all interceptor chains inside an "init_static_intercepted_methods" method // 2. adds static methods to invoke the interceptor chain and delegate to the copy of the original static method // declaring class -> initializer class - Map initializers = new HashMap<>(); + String initAllMethodName = "init_static_intercepted_methods"; for (Entry> entry : interceptedStaticMethodsMap.entrySet()) { String packageName = DotNames.internalPackageNameWithTrailingSlash(entry.getKey()); String initializerName = packageName.replace("/", ".") + entry.getKey().withoutPackagePrefix() + INITIALIZER_CLASS_SUFFIX; - initializers.put(entry.getKey(), initializerName); + baseToGeneratedInitializer.put(entry.getKey(), initializerName); ClassCreator initializer = ClassCreator.builder().classOutput(classOutput) .className(initializerName).setFinal(true).build(); @@ -206,16 +229,17 @@ void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, // For each intercepted static methods create a copy and modify the original method to delegate to the relevant initializer for (Entry> entry : interceptedStaticMethodsMap.entrySet()) { transformers.produce(new BytecodeTransformerBuildItem(entry.getKey().toString(), - new InterceptedStaticMethodsEnhancer(initializers.get(entry.getKey()), entry.getValue()))); + new InterceptedStaticMethodsEnhancer(baseToGeneratedInitializer.get(entry.getKey()), entry.getValue()))); } - // Generate a global initializer that calls all other initializers - ClassCreator globalInitializer = ClassCreator.builder().classOutput(classOutput) + // Generate a global initializer that calls all other initializers; this initializer must be loaded by the runtime ClassLoader + ClassCreator globalInitializer = ClassCreator.builder() + .classOutput(new GeneratedClassGizmoAdaptor(generatedClasses, true)) .className(InterceptedStaticMethodsRecorder.INITIALIZER_CLASS_NAME.replace('.', '/')).setFinal(true).build(); MethodCreator staticInit = globalInitializer.getMethodCreator("", void.class) .setModifiers(ACC_STATIC); - for (String initializerClass : initializers.values()) { + for (String initializerClass : baseToGeneratedInitializer.values()) { staticInit.invokeStaticMethod( MethodDescriptor.ofMethod(initializerClass, initAllMethodName, void.class)); } @@ -242,7 +266,8 @@ private void implementForward(ClassCreator initializer, paramTypes[i] = DescriptorUtils.typeToString(params.get(i)); } MethodCreator forward = initializer - .getMethodCreator(interceptedStaticMethod.getHash(), DescriptorUtils.typeToString(method.returnType()), + .getMethodCreator(interceptedStaticMethod.getForwardingMethodName(), + DescriptorUtils.typeToString(method.returnType()), paramTypes) .setModifiers(ACC_PUBLIC | ACC_FINAL | ACC_STATIC); ResultHandle argArray = forward.newArray(Object.class, params.size()); @@ -491,7 +516,7 @@ public void visitEnd() { paramSlot += AsmUtil.getParameterSize(paramType); } superVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, - initializerClassName.replace('.', '/'), interceptedStaticMethod.getHash(), + initializerClassName.replace('.', '/'), interceptedStaticMethod.getForwardingMethodName(), descriptor.getDescriptor().toString(), false); superVisitor.visitInsn(AsmUtil.getReturnInstruction(interceptedStaticMethod.getMethod().returnType())); diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java index 2c62ba21a5458..86a993c174a39 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java @@ -5,6 +5,7 @@ @Recorder public class InterceptedStaticMethodsRecorder { + // This class is generated and calls all generated static interceptor initializers to register metadata in the InterceptedStaticMethods class public static final String INITIALIZER_CLASS_NAME = "io.quarkus.arc.runtime.InterceptedStaticMethodsInitializer"; public void callInitializer() {