Skip to content

Commit

Permalink
ArC - static method interception fix
Browse files Browse the repository at this point in the history
- fix the problem with dev mode and external libraries
- also make sure the generated method does not start with a number
- fixes quarkusio#32990
  • Loading branch information
mkouba authored and manofthepeace committed May 16, 2023
1 parent 5459803 commit 0b09b8c
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,12 @@ public String getHash() {
return hash;
}

/**
*
* @return the name of the generated forwarding method
*/
public String getForwardingMethodName() {
return "_" + hash;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -146,6 +148,7 @@ void collectInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex,
void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex,
BeanRegistrationPhaseBuildItem phase,
List<InterceptedStaticMethodBuildItem> interceptedStaticMethods,
CompletedApplicationClassPredicateBuildItem applicationClassPredicate,
BuildProducer<GeneratedClassBuildItem> generatedClasses,
BuildProducer<BytecodeTransformerBuildItem> transformers,
BuildProducer<ReflectiveMethodBuildItem> reflectiveMethods) {
Expand All @@ -154,7 +157,27 @@ void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex,
return;
}

ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, true);
// org.acme.Foo -> org.acme.Foo_InterceptorInitializer
Map<DotName, String> baseToGeneratedInitializer = new HashMap<>();
ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, new Predicate<String>() {

@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<DotName, String> 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<DotName, List<InterceptedStaticMethodBuildItem>> interceptedStaticMethodsMap = new HashMap<>();
Expand All @@ -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<DotName, String> initializers = new HashMap<>();

String initAllMethodName = "init_static_intercepted_methods";
for (Entry<DotName, List<InterceptedStaticMethodBuildItem>> 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();
Expand All @@ -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<DotName, List<InterceptedStaticMethodBuildItem>> 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("<clinit>", void.class)
.setModifiers(ACC_STATIC);
for (String initializerClass : initializers.values()) {
for (String initializerClass : baseToGeneratedInitializer.values()) {
staticInit.invokeStaticMethod(
MethodDescriptor.ofMethod(initializerClass, initAllMethodName, void.class));
}
Expand All @@ -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());
Expand Down Expand Up @@ -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()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 0b09b8c

Please sign in to comment.