From 0948e42c0522308bd81e5461f722b27e515a13d0 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 17 Jun 2021 17:40:16 +0200 Subject: [PATCH] Generic signature reconstruction for methods The algorithm for reconstructing generic signature for methods in `AsmUtil` had some unimplemented parts, which are now completed. Gizmo is also updated to the latest version that has complete support for generating annotations. Finally, RestClient Reactive now properly copies the method generic signature when building RestClient interface implementation. --- bom/application/pom.xml | 2 +- .../io/quarkus/deployment/util/AsmUtil.java | 228 ++++++++++++++--- ...hodGenericSignatureReconstructionTest.java | 121 +++++++++ .../quarkus/deployment/util/OuterParam.java | 63 +++++ .../deployment/util/OuterParamBound.java | 63 +++++ .../io/quarkus/deployment/util/OuterRaw.java | 63 +++++ .../RestClientReactiveProcessor.java | 12 +- .../reactive/common/processor/AsmUtil.java | 237 ++++++++++++++---- 8 files changed, 694 insertions(+), 95 deletions(-) create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/util/MethodGenericSignatureReconstructionTest.java create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/util/OuterParam.java create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/util/OuterParamBound.java create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/util/OuterRaw.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 6275726f078f2..66788839195b7 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -87,7 +87,7 @@ 2.1.0 21.1.0 - 1.0.8.Final + 1.0.9.Final 2.12.3 1.0.0.Final 3.12.0 diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java index e02454a4ca58b..254438f8a301c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java @@ -12,6 +12,7 @@ import static org.objectweb.asm.Type.VOID_TYPE; import static org.objectweb.asm.Type.getType; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -19,6 +20,7 @@ import java.util.function.Function; import org.jboss.jandex.ArrayType; +import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.ParameterizedType; @@ -26,6 +28,8 @@ import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; import org.jboss.jandex.TypeVariable; +import org.jboss.jandex.UnresolvedTypeVariable; +import org.jboss.jandex.WildcardType; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -68,49 +72,168 @@ public static org.objectweb.asm.Type autobox(org.objectweb.asm.Type primitive) { return WRAPPERS.get(primitive.getSort()); } + /** + * Returns the Java bytecode signature of a given Jandex MethodInfo. + * If the Java compiler doesn't have to emit a signature for the method, {@code null} is returned instead. + * + * @param method the method you want the signature for + * @return a bytecode signature for that method, or {@code null} if signature is not required + */ + public static String getSignatureIfRequired(MethodInfo method) { + return getSignatureIfRequired(method, ignored -> null); + } + + /** + * Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings. + * If the Java compiler doesn't have to emit a signature for the method, {@code null} is returned instead. + * + * @param method the method you want the signature for + * @param typeArgMapper a mapping between type argument names and their bytecode signatures + * @return a bytecode signature for that method, or {@code null} if signature is not required + */ + public static String getSignatureIfRequired(MethodInfo method, Function typeArgMapper) { + if (!hasSignature(method)) { + return null; + } + + return getSignature(method, typeArgMapper); + } + + private static boolean hasSignature(MethodInfo method) { + // JVMS 16, chapter 4.7.9.1. Signatures: + // + // Java compiler must emit ... + // + // A method signature for any method or constructor declaration which is either generic, + // or has a type variable or parameterized type as the return type or a formal parameter type, + // or has a type variable in a throws clause, or any combination thereof. + + if (!method.typeParameters().isEmpty()) { + return true; + } + + { + Type type = method.returnType(); + if (type.kind() == Kind.TYPE_VARIABLE + || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE + || type.kind() == Kind.PARAMETERIZED_TYPE) { + return true; + } + } + + for (Type type : method.parameters()) { + if (type.kind() == Kind.TYPE_VARIABLE + || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE + || type.kind() == Kind.PARAMETERIZED_TYPE) { + return true; + } + } + + if (hasThrowsSignature(method)) { + return true; + } + + return false; + } + + private static boolean hasThrowsSignature(MethodInfo method) { + // JVMS 16, chapter 4.7.9.1. Signatures: + // + // If the throws clause of a method or constructor declaration does not involve type variables, + // then a compiler may treat the declaration as having no throws clause for the purpose of + // emitting a method signature. + + // also, no need to check if an exception type is of kind PARAMETERIZED_TYPE, because + // + // JLS 16, chapter 8.1.2. Generic Classes and Type Parameters: + // + // It is a compile-time error if a generic class is a direct or indirect subclass of Throwable. + + for (Type type : method.exceptions()) { + if (type.kind() == Kind.TYPE_VARIABLE + || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE) { + return true; + } + } + return false; + } + /** * Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings. * For example, given this method: - * + * *
      * {@code
      * public class Foo {
-     *  public  List method(int a, T t){...} 
+     *  public  List method(int a, T t){...}
      * }
      * }
      * 
- * + * * This will return <R:Ljava/lang/Object;>(ILjava/lang/Integer;)Ljava/util/List<TR;>; if * your {@code typeArgMapper} contains {@code T=Ljava/lang/Integer;}. - * + * * @param method the method you want the signature for. * @param typeArgMapper a mapping between type argument names and their bytecode signature. * @return a bytecode signature for that method. */ public static String getSignature(MethodInfo method, Function typeArgMapper) { - List parameters = method.parameters(); + // for grammar, see JVMS 16, chapter 4.7.9.1. Signatures - StringBuilder signature = new StringBuilder(""); - for (TypeVariable typeVariable : method.typeParameters()) { - if (signature.length() == 0) - signature.append("<"); - else - signature.append(","); - signature.append(typeVariable.identifier()).append(":"); - // FIXME: only use the first bound - toSignature(signature, typeVariable.bounds().get(0), typeArgMapper, false); + StringBuilder signature = new StringBuilder(); + + if (!method.typeParameters().isEmpty()) { + signature.append('<'); + for (TypeVariable typeParameter : method.typeParameters()) { + typeParameter(typeParameter, signature, typeArgMapper); + } + signature.append('>'); } - if (signature.length() > 0) - signature.append(">"); - signature.append("("); - for (Type type : parameters) { + + signature.append('('); + for (Type type : method.parameters()) { toSignature(signature, type, typeArgMapper, false); } - signature.append(")"); + signature.append(')'); + toSignature(signature, method.returnType(), typeArgMapper, false); + + if (hasThrowsSignature(method)) { + for (Type exception : method.exceptions()) { + signature.append('^'); + toSignature(signature, exception, typeArgMapper, false); + } + } + return signature.toString(); } + private static void typeParameter(TypeVariable typeParameter, StringBuilder result, + Function typeArgMapper) { + result.append(typeParameter.identifier()); + + if (hasImplicitObjectBound(typeParameter)) { + result.append(':'); + } + for (Type bound : typeParameter.bounds()) { + result.append(':'); + toSignature(result, bound, typeArgMapper, false); + } + } + + private static boolean hasImplicitObjectBound(TypeVariable typeParameter) { + // TODO is there a better way? :-/ + boolean result = false; + try { + Method method = TypeVariable.class.getDeclaredMethod("hasImplicitObjectBound"); + method.setAccessible(true); + result = (Boolean) method.invoke(typeParameter); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + return result; + } + /** * Returns the Java bytecode descriptor of a given Jandex MethodInfo using the given type argument mappings. * For example, given this method: @@ -176,30 +299,35 @@ private static void toSignature(StringBuilder sb, Type type, Function arguments = parameterizedType.arguments(); - for (int i = 0; i < arguments.size(); i++) { - Type argType = arguments.get(i); - toSignature(sb, argType, typeArgMapper, erased); + sb.append('<'); + for (Type argument : parameterizedType.arguments()) { + toSignature(sb, argument, typeArgMapper, erased); } - sb.append(">"); + sb.append('>'); } - sb.append(";"); + sb.append(';'); break; case PRIMITIVE: Primitive primitive = type.asPrimitiveType().primitive(); @@ -233,22 +361,40 @@ private static void toSignature(StringBuilder sb, Type type, Function;U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;Lio/quarkus/deployment/util/OuterRaw;)TT;"); + assertSignature(OuterRaw.NestedRaw.class, "ccc", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lio/quarkus/deployment/util/OuterRaw$NestedRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertSignature(OuterRaw.NestedParam.class, "ddd", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lio/quarkus/deployment/util/OuterRaw$NestedParam;)TT;"); + assertSignature(OuterRaw.NestedParamBound.class, "eee", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lio/quarkus/deployment/util/OuterRaw$NestedParamBound;)TT;^TV;"); + assertSignature(OuterRaw.InnerRaw.class, "fff", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lio/quarkus/deployment/util/OuterRaw$InnerRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertSignature(OuterRaw.InnerParam.class, "ggg", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lio/quarkus/deployment/util/OuterRaw$InnerParam;)TT;"); + assertSignature(OuterRaw.InnerParamBound.class, "hhh", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lio/quarkus/deployment/util/OuterRaw$InnerParamBound;)TT;^TV;"); + assertSignature(OuterRaw.InnerParamBound.DoubleInner.class, "iii", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TY;TX;Lio/quarkus/deployment/util/OuterRaw$InnerParamBound.DoubleInner;)TT;^TV;"); + + assertSignature(OuterParam.class, "aaa", + "(Ljava/lang/String;TW;Lio/quarkus/deployment/util/OuterParam;)Ljava/lang/String;"); + assertSignature(OuterParam.class, "bbb", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;TW;Lio/quarkus/deployment/util/OuterParam;)TT;"); + assertSignature(OuterParam.NestedRaw.class, "ccc", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lio/quarkus/deployment/util/OuterParam$NestedRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertSignature(OuterParam.NestedParam.class, "ddd", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lio/quarkus/deployment/util/OuterParam$NestedParam;)TT;"); + assertSignature(OuterParam.NestedParamBound.class, "eee", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lio/quarkus/deployment/util/OuterParam$NestedParamBound;)TT;^TV;"); + assertSignature(OuterParam.InnerRaw.class, "fff", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;TW;Lio/quarkus/deployment/util/OuterParam.InnerRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertSignature(OuterParam.InnerParam.class, "ggg", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;TW;Lio/quarkus/deployment/util/OuterParam.InnerParam;)TT;"); + assertSignature(OuterParam.InnerParamBound.class, "hhh", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;TW;Lio/quarkus/deployment/util/OuterParam.InnerParamBound;)TT;^TV;"); + assertSignature(OuterParam.InnerParamBound.DoubleInner.class, "iii", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TY;TX;TW;Lio/quarkus/deployment/util/OuterParam.InnerParamBound.DoubleInner;)TT;^TV;"); + + assertSignature(OuterParamBound.class, "aaa", + "(Ljava/lang/String;TW;Lio/quarkus/deployment/util/OuterParamBound;)Ljava/lang/String;"); + assertSignature(OuterParamBound.class, "bbb", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;TW;Lio/quarkus/deployment/util/OuterParamBound;)TT;"); + assertSignature(OuterParamBound.NestedRaw.class, "ccc", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lio/quarkus/deployment/util/OuterParamBound$NestedRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertSignature(OuterParamBound.NestedParam.class, "ddd", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lio/quarkus/deployment/util/OuterParamBound$NestedParam;)TT;"); + assertSignature(OuterParamBound.NestedParamBound.class, "eee", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lio/quarkus/deployment/util/OuterParamBound$NestedParamBound;)TT;^TV;"); + assertSignature(OuterParamBound.InnerRaw.class, "fff", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;TW;Lio/quarkus/deployment/util/OuterParamBound.InnerRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;"); + assertSignature(OuterParamBound.InnerParam.class, "ggg", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;TW;Lio/quarkus/deployment/util/OuterParamBound.InnerParam;)TT;"); + assertSignature(OuterParamBound.InnerParamBound.class, "hhh", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;TW;Lio/quarkus/deployment/util/OuterParamBound.InnerParamBound;)TT;^TV;"); + assertSignature(OuterParamBound.InnerParamBound.DoubleInner.class, "iii", + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TY;TX;TW;Lio/quarkus/deployment/util/OuterParamBound.InnerParamBound.DoubleInner;)TT;^TV;"); + } + + private static void assertSignature(Class clazz, String method, String expectedSignature) { + DotName name = DotName.createSimple(clazz.getName()); + for (MethodInfo methodInfo : index.getClassByName(name).methods()) { + if (method.equals(methodInfo.name())) { + String actualSignature = AsmUtil.getSignatureIfRequired(methodInfo); + assertEquals(expectedSignature, actualSignature); + return; + } + } + + fail("Couldn't find method " + clazz.getName() + "#" + method + " in test index"); + } + + private static Index index(Class... classes) { + Indexer indexer = new Indexer(); + for (Class clazz : classes) { + try { + try (InputStream stream = MethodGenericSignatureReconstructionTest.class.getClassLoader() + .getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) { + indexer.index(stream); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return indexer.complete(); + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParam.java b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParam.java new file mode 100644 index 0000000000000..2a849c8e8120d --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParam.java @@ -0,0 +1,63 @@ +package io.quarkus.deployment.util; + +import java.util.List; + +public class OuterParam { + public String aaa(String arg, W arg2, OuterParam self) throws IllegalArgumentException { + return null; + } + + public , U extends Comparable, V extends Exception> T bbb( + U arg, W arg2, OuterParam self) { + return null; + } + + public static class NestedRaw { + public , U extends Comparable, V extends Exception> T ccc( + List arg, NestedRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public static class NestedParam { + public , U extends Comparable, V extends Exception> T ddd( + List arg, X arg2, NestedParam self) throws IllegalArgumentException { + return null; + } + } + + public static class NestedParamBound> { + public , U extends Comparable, V extends Exception> T eee( + List arg, X arg2, NestedParamBound self) throws V { + return null; + } + } + + public class InnerRaw { + public , U extends Comparable, V extends Exception> T fff( + List arg, W arg2, InnerRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public class InnerParam { + public , U extends Comparable, V extends Exception> T ggg( + List arg, X arg2, W arg3, InnerParam self) throws IllegalArgumentException { + return null; + } + } + + public class InnerParamBound> { + public , U extends Comparable, V extends Exception> T hhh( + List arg, X arg2, W arg3, InnerParamBound self) throws V { + return null; + } + + public class DoubleInner { + public , U extends Comparable, V extends Exception> T iii( + List arg, Y arg2, X arg3, W arg4, DoubleInner self) throws V { + return null; + } + } + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParamBound.java b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParamBound.java new file mode 100644 index 0000000000000..64097bc389245 --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParamBound.java @@ -0,0 +1,63 @@ +package io.quarkus.deployment.util; + +import java.util.List; + +public class OuterParamBound> { + public String aaa(String arg, W arg2, OuterParamBound self) throws IllegalArgumentException { + return null; + } + + public , U extends Comparable, V extends Exception> T bbb( + U arg, W arg2, OuterParamBound self) { + return null; + } + + public static class NestedRaw { + public , U extends Comparable, V extends Exception> T ccc( + List arg, NestedRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public static class NestedParam { + public , U extends Comparable, V extends Exception> T ddd( + List arg, X arg2, NestedParam self) throws IllegalArgumentException { + return null; + } + } + + public static class NestedParamBound> { + public , U extends Comparable, V extends Exception> T eee( + List arg, X arg2, NestedParamBound self) throws V { + return null; + } + } + + public class InnerRaw { + public , U extends Comparable, V extends Exception> T fff( + List arg, W arg2, InnerRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public class InnerParam { + public , U extends Comparable, V extends Exception> T ggg( + List arg, X arg2, W arg3, InnerParam self) throws IllegalArgumentException { + return null; + } + } + + public class InnerParamBound> { + public , U extends Comparable, V extends Exception> T hhh( + List arg, X arg2, W arg3, InnerParamBound self) throws V { + return null; + } + + public class DoubleInner { + public , U extends Comparable, V extends Exception> T iii( + List arg, Y arg2, X arg3, W arg4, DoubleInner self) throws V { + return null; + } + } + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/OuterRaw.java b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterRaw.java new file mode 100644 index 0000000000000..e78160420b0ca --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterRaw.java @@ -0,0 +1,63 @@ +package io.quarkus.deployment.util; + +import java.util.List; + +public class OuterRaw { + public String aaa(String arg, OuterRaw self) throws IllegalArgumentException { + return null; + } + + public , U extends Comparable, V extends Exception> T bbb( + U arg, OuterRaw self) { + return null; + } + + public static class NestedRaw { + public , U extends Comparable, V extends Exception> T ccc( + List arg, NestedRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public static class NestedParam { + public , U extends Comparable, V extends Exception> T ddd( + List arg, X arg2, NestedParam self) throws IllegalArgumentException { + return null; + } + } + + public static class NestedParamBound> { + public , U extends Comparable, V extends Exception> T eee( + List arg, X arg2, NestedParamBound self) throws V { + return null; + } + } + + public class InnerRaw { + public , U extends Comparable, V extends Exception> T fff( + List arg, InnerRaw self) throws IllegalArgumentException, V { + return null; + } + } + + public class InnerParam { + public , U extends Comparable, V extends Exception> T ggg( + List arg, X arg2, InnerParam self) throws IllegalArgumentException { + return null; + } + } + + public class InnerParamBound> { + public , U extends Comparable, V extends Exception> T hhh( + List arg, X arg2, InnerParamBound self) throws V { + return null; + } + + public class DoubleInner { + public , U extends Comparable, V extends Exception> T iii( + List arg, Y arg2, X arg3, DoubleInner self) throws V { + return null; + } + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java index 8894a15c756b1..626a5cc8a028f 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java @@ -58,6 +58,7 @@ import io.quarkus.deployment.builditem.ConfigurationTypeBuildItem; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.util.AsmUtil; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; @@ -307,21 +308,14 @@ void addRestClientBeans(Capabilities capabilities, // return ((InterfaceClass)this.getDelegate()).get(); // } MethodCreator methodCreator = classCreator.getMethodCreator(MethodDescriptor.of(method)); + methodCreator.setSignature(AsmUtil.getSignatureIfRequired(method)); // copy method annotations, there can be interceptors bound to them: for (AnnotationInstance annotation : method.annotations()) { if (annotation.target().kind() == AnnotationTarget.Kind.METHOD && !BUILTIN_HTTP_ANNOTATIONS_TO_METHOD.containsKey(annotation.name()) && !ResteasyReactiveDotNames.PATH.equals(annotation.name())) { - AnnotationValue value = annotation.value(); - if (value != null && value.kind() == AnnotationValue.Kind.ARRAY - && value.componentKind() == AnnotationValue.Kind.NESTED) { - for (AnnotationInstance annotationInstance : value.asNestedArray()) { - methodCreator.addAnnotation(annotationInstance); - } - } else { - methodCreator.addAnnotation(annotation); - } + methodCreator.addAnnotation(annotation); } } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java index dc66f3b6bb255..750b68ef5e3e6 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java @@ -4,6 +4,7 @@ import static org.objectweb.asm.Type.BOOLEAN_TYPE; import static org.objectweb.asm.Type.BYTE_TYPE; import static org.objectweb.asm.Type.CHAR_TYPE; +import static org.objectweb.asm.Type.DOUBLE_TYPE; import static org.objectweb.asm.Type.FLOAT_TYPE; import static org.objectweb.asm.Type.INT_TYPE; import static org.objectweb.asm.Type.LONG_TYPE; @@ -11,12 +12,14 @@ import static org.objectweb.asm.Type.VOID_TYPE; import static org.objectweb.asm.Type.getType; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import org.jboss.jandex.ArrayType; +import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.ParameterizedType; @@ -24,6 +27,8 @@ import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; import org.jboss.jandex.TypeVariable; +import org.jboss.jandex.UnresolvedTypeVariable; +import org.jboss.jandex.WildcardType; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -42,7 +47,8 @@ public class AsmUtil { SHORT_TYPE, INT_TYPE, FLOAT_TYPE, - LONG_TYPE); + LONG_TYPE, + DOUBLE_TYPE); public static final List WRAPPERS = asList( getType(Void.class), getType(Boolean.class), @@ -51,7 +57,8 @@ public class AsmUtil { getType(Short.class), getType(Integer.class), getType(Float.class), - getType(Long.class)); + getType(Long.class), + getType(Double.class)); public static final Map WRAPPER_TO_PRIMITIVE = new HashMap<>(); static { @@ -64,49 +71,168 @@ public static org.objectweb.asm.Type autobox(org.objectweb.asm.Type primitive) { return WRAPPERS.get(primitive.getSort()); } + /** + * Returns the Java bytecode signature of a given Jandex MethodInfo. + * If the Java compiler doesn't have to emit a signature for the method, {@code null} is returned instead. + * + * @param method the method you want the signature for + * @return a bytecode signature for that method, or {@code null} if signature is not required + */ + public static String getSignatureIfRequired(MethodInfo method) { + return getSignatureIfRequired(method, ignored -> null); + } + + /** + * Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings. + * If the Java compiler doesn't have to emit a signature for the method, {@code null} is returned instead. + * + * @param method the method you want the signature for + * @param typeArgMapper a mapping between type argument names and their bytecode signatures + * @return a bytecode signature for that method, or {@code null} if signature is not required + */ + public static String getSignatureIfRequired(MethodInfo method, Function typeArgMapper) { + if (!hasSignature(method)) { + return null; + } + + return getSignature(method, typeArgMapper); + } + + private static boolean hasSignature(MethodInfo method) { + // JVMS 16, chapter 4.7.9.1. Signatures: + // + // Java compiler must emit ... + // + // A method signature for any method or constructor declaration which is either generic, + // or has a type variable or parameterized type as the return type or a formal parameter type, + // or has a type variable in a throws clause, or any combination thereof. + + if (!method.typeParameters().isEmpty()) { + return true; + } + + { + Type type = method.returnType(); + if (type.kind() == Kind.TYPE_VARIABLE + || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE + || type.kind() == Kind.PARAMETERIZED_TYPE) { + return true; + } + } + + for (Type type : method.parameters()) { + if (type.kind() == Kind.TYPE_VARIABLE + || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE + || type.kind() == Kind.PARAMETERIZED_TYPE) { + return true; + } + } + + if (hasThrowsSignature(method)) { + return true; + } + + return false; + } + + private static boolean hasThrowsSignature(MethodInfo method) { + // JVMS 16, chapter 4.7.9.1. Signatures: + // + // If the throws clause of a method or constructor declaration does not involve type variables, + // then a compiler may treat the declaration as having no throws clause for the purpose of + // emitting a method signature. + + // also, no need to check if an exception type is of kind PARAMETERIZED_TYPE, because + // + // JLS 16, chapter 8.1.2. Generic Classes and Type Parameters: + // + // It is a compile-time error if a generic class is a direct or indirect subclass of Throwable. + + for (Type type : method.exceptions()) { + if (type.kind() == Kind.TYPE_VARIABLE + || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE) { + return true; + } + } + return false; + } + /** * Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings. * For example, given this method: - * + * *
      * {@code
      * public class Foo {
-     *  public  List method(int a, T t){...} 
+     *  public  List method(int a, T t){...}
      * }
      * }
      * 
- * + * * This will return <R:Ljava/lang/Object;>(ILjava/lang/Integer;)Ljava/util/List<TR;>; if * your {@code typeArgMapper} contains {@code T=Ljava/lang/Integer;}. - * + * * @param method the method you want the signature for. * @param typeArgMapper a mapping between type argument names and their bytecode signature. * @return a bytecode signature for that method. */ public static String getSignature(MethodInfo method, Function typeArgMapper) { - List parameters = method.parameters(); + // for grammar, see JVMS 16, chapter 4.7.9.1. Signatures - StringBuilder signature = new StringBuilder(""); - for (TypeVariable typeVariable : method.typeParameters()) { - if (signature.length() == 0) - signature.append("<"); - else - signature.append(","); - signature.append(typeVariable.identifier()).append(":"); - // FIXME: only use the first bound - toSignature(signature, typeVariable.bounds().get(0), typeArgMapper, false); + StringBuilder signature = new StringBuilder(); + + if (!method.typeParameters().isEmpty()) { + signature.append('<'); + for (TypeVariable typeParameter : method.typeParameters()) { + typeParameter(typeParameter, signature, typeArgMapper); + } + signature.append('>'); } - if (signature.length() > 0) - signature.append(">"); - signature.append("("); - for (Type type : parameters) { + + signature.append('('); + for (Type type : method.parameters()) { toSignature(signature, type, typeArgMapper, false); } - signature.append(")"); + signature.append(')'); + toSignature(signature, method.returnType(), typeArgMapper, false); + + if (hasThrowsSignature(method)) { + for (Type exception : method.exceptions()) { + signature.append('^'); + toSignature(signature, exception, typeArgMapper, false); + } + } + return signature.toString(); } + private static void typeParameter(TypeVariable typeParameter, StringBuilder result, + Function typeArgMapper) { + result.append(typeParameter.identifier()); + + if (hasImplicitObjectBound(typeParameter)) { + result.append(':'); + } + for (Type bound : typeParameter.bounds()) { + result.append(':'); + toSignature(result, bound, typeArgMapper, false); + } + } + + private static boolean hasImplicitObjectBound(TypeVariable typeParameter) { + // TODO is there a better way? :-/ + boolean result = false; + try { + Method method = TypeVariable.class.getDeclaredMethod("hasImplicitObjectBound"); + method.setAccessible(true); + result = (Boolean) method.invoke(typeParameter); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + return result; + } + /** * Returns the Java bytecode descriptor of a given Jandex MethodInfo using the given type argument mappings. * For example, given this method: @@ -172,30 +298,35 @@ private static void toSignature(StringBuilder sb, Type type, Function arguments = parameterizedType.arguments(); - for (int i = 0; i < arguments.size(); i++) { - Type argType = arguments.get(i); - toSignature(sb, argType, typeArgMapper, erased); + sb.append('<'); + for (Type argument : parameterizedType.arguments()) { + toSignature(sb, argument, typeArgMapper, erased); } - sb.append(">"); + sb.append('>'); } - sb.append(";"); + sb.append(';'); break; case PRIMITIVE: Primitive primitive = type.asPrimitiveType().primitive(); @@ -229,22 +360,40 @@ private static void toSignature(StringBuilder sb, Type type, FunctionIRETURN, LRETURN, FRETURN, DRETURN, RETURN for primitives/void, * and ARETURN otherwise; * - * @param typeDescriptor the return Jandex Type. + * @param jandexType the return Jandex Type. * @return the correct bytecode return instruction for that return type descriptor. */ public static int getReturnInstruction(Type jandexType) {