Skip to content

Commit

Permalink
ArC: use the ClassTransformer API from gizmo
Browse files Browse the repository at this point in the history
Co-authored-by: Ladislav Thon <[email protected]>
  • Loading branch information
mkouba and Ladicek committed Sep 6, 2023
1 parent 9da2016 commit b9f7aed
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 222 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import io.quarkus.arc.InjectableInterceptor;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
Expand All @@ -61,13 +59,12 @@
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem;
import io.quarkus.deployment.util.AsmUtil;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.ClassTransformer;
import io.quarkus.gizmo.DescriptorUtils;
import io.quarkus.gizmo.FunctionCreator;
import io.quarkus.gizmo.Gizmo;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
Expand Down Expand Up @@ -445,85 +442,33 @@ public InterceptedStaticMethodsEnhancer(String initializerClassName, List<Interc

@Override
public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) {
return new InterceptedStaticMethodsClassVisitor(initializerClassName, outputClassVisitor, methods);
}

}

static class InterceptedStaticMethodsClassVisitor extends ClassVisitor {

private final String initializerClassName;
private final List<InterceptedStaticMethodBuildItem> methods;

public InterceptedStaticMethodsClassVisitor(String initializerClassName, ClassVisitor outputClassVisitor,
List<InterceptedStaticMethodBuildItem> methods) {
super(Gizmo.ASM_API_VERSION, outputClassVisitor);
this.methods = methods;
this.initializerClassName = initializerClassName;
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
InterceptedStaticMethodBuildItem method = findMatchingMethod(access, name, descriptor);
if (method != null) {
MethodVisitor copy = super.visitMethod(access,
name + ORIGINAL_METHOD_COPY_SUFFIX,
descriptor,
signature,
exceptions);
MethodVisitor superVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
return new InterceptedStaticMethodsMethodVisitor(superVisitor, copy, initializerClassName, method);
} else {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}

private InterceptedStaticMethodBuildItem findMatchingMethod(int access, String name, String descriptor) {
if (Modifier.isStatic(access)) {
for (InterceptedStaticMethodBuildItem method : methods) {
if (method.getMethod().name().equals(name)
&& MethodDescriptor.of(method.getMethod()).getDescriptor().equals(descriptor)) {
return method;
}
ClassTransformer transformer = new ClassTransformer(className);
for (InterceptedStaticMethodBuildItem interceptedStaticMethod : methods) {
MethodInfo interceptedMethod = interceptedStaticMethod.getMethod();
MethodDescriptor originalDescriptor = MethodDescriptor.of(interceptedMethod);
// Rename the intercepted method
transformer.modifyMethod(originalDescriptor)
.rename(interceptedMethod.name() + ORIGINAL_METHOD_COPY_SUFFIX);

// Add the intercepted method again - invoke the initializer in the body, e.g. Foo_InterceptorInitializer.hash("ping")
MethodCreator newMethod = transformer.addMethod(originalDescriptor)
.setModifiers(interceptedMethod.flags())
.setSignature(interceptedMethod.genericSignatureIfRequired());
for (Type exceptionType : interceptedMethod.exceptions()) {
newMethod.addException(exceptionType.name().toString());
}
ResultHandle[] args = new ResultHandle[interceptedMethod.parametersCount()];
for (int i = 0; i < interceptedMethod.parametersCount(); ++i) {
args[i] = newMethod.getMethodParam(i);
}
ResultHandle ret = newMethod.invokeStaticMethod(MethodDescriptor.ofMethod(initializerClassName,
interceptedStaticMethod.getForwardingMethodName(),
interceptedMethod.returnType().descriptor(),
interceptedMethod.parameterTypes().stream().map(Type::descriptor).toArray()),
args);
newMethod.returnValue(ret);
}
return null;
}

}

static class InterceptedStaticMethodsMethodVisitor extends MethodVisitor {

private final String initializerClassName;
private final InterceptedStaticMethodBuildItem interceptedStaticMethod;
private final MethodVisitor superVisitor;

public InterceptedStaticMethodsMethodVisitor(MethodVisitor superVisitor, MethodVisitor copyVisitor,
String initializerClassName, InterceptedStaticMethodBuildItem interceptedStaticMethod) {
super(Gizmo.ASM_API_VERSION, copyVisitor);
this.superVisitor = superVisitor;
this.initializerClassName = initializerClassName;
this.interceptedStaticMethod = interceptedStaticMethod;
}

@Override
public void visitEnd() {
// Invoke the initializer, i.e. Foo_InterceptorInitializer.hash("ping")
MethodDescriptor descriptor = MethodDescriptor.of(interceptedStaticMethod.getMethod());
int paramSlot = 0;
for (Type paramType : interceptedStaticMethod.getMethod().parameterTypes()) {
superVisitor.visitIntInsn(AsmUtil.getLoadOpcode(paramType), paramSlot);
paramSlot += AsmUtil.getParameterSize(paramType);
}
superVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
initializerClassName.replace('.', '/'), interceptedStaticMethod.getForwardingMethodName(),
descriptor.getDescriptor().toString(),
false);
superVisitor.visitInsn(AsmUtil.getReturnInstruction(interceptedStaticMethod.getMethod().returnType()));
superVisitor.visitMaxs(0, 0);
superVisitor.visitEnd();

super.visitEnd();
return transformer.applyTo(outputClassVisitor);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@
import org.jboss.jandex.Type.Kind;
import org.jboss.logging.Logger;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import io.quarkus.arc.processor.InjectionPointInfo.TypeAndQualifiers;
import io.quarkus.gizmo.Gizmo;
import io.quarkus.gizmo.ClassTransformer;
import io.quarkus.gizmo.FieldDescriptor;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;

public final class Beans {

Expand Down Expand Up @@ -696,7 +697,7 @@ static void validateInterceptorDecorator(BeanInfo bean, List<Throwable> errors,
if (injection.isField() && Modifier.isPrivate(injection.getTarget().asField().flags())) {
bytecodeTransformerConsumer
.accept(new BytecodeTransformer(bean.getTarget().get().asClass().name().toString(),
new PrivateInjectedFieldTransformFunction(injection.getTarget().asField().name())));
new PrivateInjectedFieldTransformFunction(injection.getTarget().asField())));
}
}
}
Expand Down Expand Up @@ -791,7 +792,7 @@ static void validateBean(BeanInfo bean, List<Throwable> errors, Consumer<Bytecod
for (Injection injection : bean.getInjections()) {
if (injection.isField() && Modifier.isPrivate(injection.getTarget().asField().flags())) {
bytecodeTransformerConsumer.accept(new BytecodeTransformer(beanClass.name().toString(),
new PrivateInjectedFieldTransformFunction(injection.getTarget().asField().name())));
new PrivateInjectedFieldTransformFunction(injection.getTarget().asField())));
}
}
}
Expand Down Expand Up @@ -1046,8 +1047,7 @@ private static Integer initAlternativePriority(AnnotationTarget target, Integer
Integer computedPriority = deployment.computeAlternativePriority(target, stereotypes);
if (computedPriority != null) {
if (alternativePriority != null) {
LOGGER.infof(
"Computed priority [%s] overrides the priority [%s] declared via @Priority",
LOGGER.infof("Computed priority [%s] overrides the priority [%s] declared via @Priority",
computedPriority, alternativePriority);
}
alternativePriority = computedPriority;
Expand All @@ -1059,17 +1059,10 @@ static class FinalClassTransformFunction implements BiFunction<String, ClassVisi

@Override
public ClassVisitor apply(String className, ClassVisitor classVisitor) {
return new ClassVisitor(Gizmo.ASM_API_VERSION, classVisitor) {

@Override
public void visit(int version, int access, String name, String signature,
String superName,
String[] interfaces) {
LOGGER.debugf("Final flag removed from bean class %s", className);
super.visit(version, access = access & (~Opcodes.ACC_FINAL), name, signature,
superName, interfaces);
}
};
ClassTransformer transformer = new ClassTransformer(className);
transformer.removeModifiers(Opcodes.ACC_FINAL);
LOGGER.debugf("Final flag removed from bean class %s", className);
return transformer.applyTo(classVisitor);
}
}

Expand All @@ -1083,26 +1076,13 @@ public NoArgConstructorTransformFunction(String superClassName) {

@Override
public ClassVisitor apply(String className, ClassVisitor classVisitor) {
return new ClassVisitor(Gizmo.ASM_API_VERSION, classVisitor) {

@Override
public void visit(int version, int access, String name, String signature,
String superName,
String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
MethodVisitor mv = visitMethod(Modifier.PUBLIC | Opcodes.ACC_SYNTHETIC, Methods.INIT, "()V", null,
null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName, Methods.INIT, "()V",
false);
// NOTE: it seems that we do not need to handle final fields?
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
LOGGER.debugf("Added a no-args constructor to bean class: %s", className);
}
};
ClassTransformer transformer = new ClassTransformer(className);
MethodCreator constructor = transformer.addMethod(MethodDescriptor.ofConstructor(className));
constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(superClassName), constructor.getThis());
// NOTE: it seems that we do not need to handle final fields
constructor.returnVoid();
LOGGER.debugf("Added a no-args constructor to bean class: %s", className);
return transformer.applyTo(classVisitor);
}

}
Expand All @@ -1111,53 +1091,31 @@ static class PrivateNoArgsConstructorTransformFunction implements BiFunction<Str

@Override
public ClassVisitor apply(String className, ClassVisitor classVisitor) {
return new ClassVisitor(Gizmo.ASM_API_VERSION, classVisitor) {

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
if (name.equals(Methods.INIT)) {
access = access & (~Opcodes.ACC_PRIVATE);
LOGGER.debugf(
"Changed visibility of a private no-args constructor to package-private: %s",
className);
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
};
ClassTransformer transformer = new ClassTransformer(className);
transformer.modifyMethod(MethodDescriptor.ofConstructor(className)).removeModifiers(Opcodes.ACC_PRIVATE);
LOGGER.debugf("Changed visibility of a private no-args constructor to package-private: %s",
className);
return transformer.applyTo(classVisitor);
}

}

// alters an injected field modifier from private to package private
static class PrivateInjectedFieldTransformFunction implements BiFunction<String, ClassVisitor, ClassVisitor> {

public PrivateInjectedFieldTransformFunction(String fieldName) {
this.fieldName = fieldName;
public PrivateInjectedFieldTransformFunction(FieldInfo field) {
this.field = field;
}

private String fieldName;
private FieldInfo field;

@Override
public ClassVisitor apply(String className, ClassVisitor classVisitor) {
return new ClassVisitor(Gizmo.ASM_API_VERSION, classVisitor) {

@Override
public FieldVisitor visitField(
int access,
String name,
String descriptor,
String signature,
Object value) {
if (name.equals(fieldName)) {
access = access & (~Opcodes.ACC_PRIVATE);
LOGGER.debugf(
"Changed visibility of an injected private field to package-private. Field name: %s in class: %s",
name, className);
}
return super.visitField(access, name, descriptor, signature, value);
}
};
ClassTransformer transformer = new ClassTransformer(className);
transformer.modifyField(FieldDescriptor.of(field)).removeModifiers(Opcodes.ACC_PRIVATE);
LOGGER.debugf("Changed visibility of an injected private field to package-private. Field name: %s in class: %s",
field.name(), className);
return transformer.applyTo(classVisitor);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.quarkus.arc.InjectableContext;
import io.quarkus.arc.impl.Mockable;
import io.quarkus.arc.processor.BeanGenerator.ProviderType;
import io.quarkus.arc.processor.Methods.MethodKey;
import io.quarkus.arc.processor.ResourceOutput.Resource;
import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType;
import io.quarkus.gizmo.BytecodeCreator;
Expand Down Expand Up @@ -332,18 +333,18 @@ Collection<MethodInfo> getDelegatingMethods(BeanInfo bean, Consumer<BytecodeTran
IndexView index = bean.getDeployment().getBeanArchiveIndex();

if (bean.isClassBean()) {
Map<String, Set<Methods.NameAndDescriptor>> methodsFromWhichToRemoveFinal = new HashMap<>();
Map<String, Set<MethodKey>> methodsFromWhichToRemoveFinal = new HashMap<>();
ClassInfo classInfo = bean.getTarget().get().asClass();
addDelegatesAndTrasformIfNecessary(bytecodeTransformerConsumer, transformUnproxyableClasses, methods, index,
methodsFromWhichToRemoveFinal, classInfo);
} else if (bean.isProducerMethod()) {
Map<String, Set<Methods.NameAndDescriptor>> methodsFromWhichToRemoveFinal = new HashMap<>();
Map<String, Set<MethodKey>> methodsFromWhichToRemoveFinal = new HashMap<>();
MethodInfo producerMethod = bean.getTarget().get().asMethod();
ClassInfo returnTypeClass = getClassByName(index, producerMethod.returnType());
addDelegatesAndTrasformIfNecessary(bytecodeTransformerConsumer, transformUnproxyableClasses, methods, index,
methodsFromWhichToRemoveFinal, returnTypeClass);
} else if (bean.isProducerField()) {
Map<String, Set<Methods.NameAndDescriptor>> methodsFromWhichToRemoveFinal = new HashMap<>();
Map<String, Set<MethodKey>> methodsFromWhichToRemoveFinal = new HashMap<>();
FieldInfo producerField = bean.getTarget().get().asField();
ClassInfo fieldClass = getClassByName(index, producerField.type());
addDelegatesAndTrasformIfNecessary(bytecodeTransformerConsumer, transformUnproxyableClasses, methods, index,
Expand All @@ -359,15 +360,15 @@ Collection<MethodInfo> getDelegatingMethods(BeanInfo bean, Consumer<BytecodeTran
private void addDelegatesAndTrasformIfNecessary(Consumer<BytecodeTransformer> bytecodeTransformerConsumer,
boolean transformUnproxyableClasses,
Map<Methods.MethodKey, MethodInfo> methods, IndexView index,
Map<String, Set<Methods.NameAndDescriptor>> methodsFromWhichToRemoveFinal,
Map<String, Set<MethodKey>> methodsFromWhichToRemoveFinal,
ClassInfo fieldClass) {
Methods.addDelegatingMethods(index, fieldClass, methods, methodsFromWhichToRemoveFinal,
transformUnproxyableClasses);
if (!methodsFromWhichToRemoveFinal.isEmpty()) {
for (Map.Entry<String, Set<Methods.NameAndDescriptor>> entry : methodsFromWhichToRemoveFinal.entrySet()) {
for (Map.Entry<String, Set<MethodKey>> entry : methodsFromWhichToRemoveFinal.entrySet()) {
String className = entry.getKey();
bytecodeTransformerConsumer.accept(new BytecodeTransformer(className,
new Methods.RemoveFinalFromMethod(className, entry.getValue())));
new Methods.RemoveFinalFromMethod(entry.getValue())));
}
}
}
Expand Down
Loading

0 comments on commit b9f7aed

Please sign in to comment.