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) {