2323import net .bytebuddy .description .type .TypeDescription ;
2424import net .bytebuddy .implementation .Implementation ;
2525import net .bytebuddy .implementation .StubMethod ;
26+ import net .bytebuddy .jar .asm .AnnotationVisitor ;
2627import net .bytebuddy .jar .asm .ClassVisitor ;
2728import net .bytebuddy .jar .asm .MethodVisitor ;
29+ import net .bytebuddy .jar .asm .TypePath ;
30+ import net .bytebuddy .jar .asm .TypeReference ;
2831import net .bytebuddy .pool .TypePool ;
2932import net .bytebuddy .utility .OpenedClassReader ;
3033
3134public class KotlinTypeAnnotationWrongTypePathTest {
3235 private static final String TEST_CLASS = "org.jboss.jandex.test.TestClass" ;
3336
34- // emulates a Kotlin bug in emitting a wrong type annotation path
35- //
37+ // the tests below emulate Kotlin bugs in emitting wrong type annotation paths
38+
3639 // for a method declaration `fun foo(bar: List<List<@Valid String>>) {}`,
3740 // the Kotlin compiler emits the following signature:
3841 // `(Ljava/util/List<+Ljava/util/List<Ljava/lang/String;>;>;)V`
@@ -47,10 +50,9 @@ public class KotlinTypeAnnotationWrongTypePathTest {
4750 // when the first Java declaration above is compiled with a Java compiler,
4851 // it emits the following type annotation path:
4952 // `location=[TYPE_ARGUMENT(0), WILDCARD, TYPE_ARGUMENT(0)]`
50-
5153 @ Test
52- public void test () throws IOException {
53- // List<List<String>>
54+ public void test1 () throws IOException {
55+ // List<List<@MyAnnotation("foobar") String>>
5456 TypeDescription .Generic annotatedString = TypeDescription .Generic .Builder .of (String .class )
5557 .annotate (AnnotationDescription .Builder .ofType (MyAnnotation .class )
5658 .define ("value" , "foobar" ).build ())
@@ -102,4 +104,86 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str
102104
103105 assertEquals ("java.util.List<? extends java.util.List<java.lang.String>>" , type .toString ());
104106 }
107+
108+ // at least with Kotlin 2.2.21, compiling the following code with `-Xemit-jvm-type-annotations`:
109+ //
110+ // ```
111+ // typealias Consumer<E> = (E) -> Unit
112+ // class Foo<E> {
113+ // fun Consumer<E>.bar() = ::baz
114+ // fun baz(a: Int, b: Int) {
115+ // }
116+ // }
117+ // ```
118+ //
119+ // leads to the following bytecode for the `bar` method:
120+ //
121+ // public final kotlin.reflect.KFunction<kotlin.Unit> bar(kotlin.jvm.functions.Function1<? super E, kotlin.Unit>);
122+ // ...
123+ // Signature: (Lkotlin/jvm/functions/Function1<-TE;Lkotlin/Unit;>;)Lkotlin/reflect/KFunction<Lkotlin/Unit;>;
124+ // RuntimeVisibleTypeAnnotations:
125+ // 0: #16(#17=s#18): METHOD_RETURN, location=[TYPE_ARGUMENT(0)]
126+ // kotlin.ParameterName(name="a")
127+ // 1: #16(#17=s#19): METHOD_RETURN, location=[TYPE_ARGUMENT(1)]
128+ // kotlin.ParameterName(name="b")
129+ //
130+ // the return type is parameterized with 1 type argument, but the type annotations
131+ // expect that there are 2 type arguments
132+ @ Test
133+ public void test2 () throws IOException {
134+ // List<String>
135+ TypeDescription .Generic listOfString = TypeDescription .Generic .Builder .parameterizedType (
136+ TypeDescription .ForLoadedType .of (List .class ), TypeDescription .ForLoadedType .of (String .class )).build ();
137+
138+ byte [] bytes = new ByteBuddy ()
139+ .subclass (Object .class )
140+ .name (TEST_CLASS )
141+ .defineMethod ("foo" , listOfString )
142+ .intercept (StubMethod .INSTANCE )
143+ .visit (new AsmVisitorWrapper .AbstractBase () {
144+ @ Override
145+ public ClassVisitor wrap (TypeDescription instrumentedType , ClassVisitor classVisitor ,
146+ Implementation .Context implementationContext , TypePool typePool ,
147+ FieldList <FieldDescription .InDefinedShape > fields , MethodList <?> methods ,
148+ int writerFlags , int readerFlags ) {
149+ return new ClassVisitor (OpenedClassReader .ASM_API , classVisitor ) {
150+ @ Override
151+ public MethodVisitor visitMethod (int access , String name , String descriptor , String signature ,
152+ String [] exceptions ) {
153+ MethodVisitor mv = super .visitMethod (access , name , descriptor , signature , exceptions );
154+ if ("foo" .equals (name )) {
155+ AnnotationVisitor av = mv .visitTypeAnnotation (
156+ TypeReference .newTypeReference (TypeReference .METHOD_RETURN ).getValue (),
157+ TypePath .fromString ("0;" ),
158+ "Lorg/jboss/jandex/test/MyAnnotation;" , true );
159+ av .visit ("value" , "000" );
160+ av .visitEnd ();
161+
162+ av = mv .visitTypeAnnotation (
163+ TypeReference .newTypeReference (TypeReference .METHOD_RETURN ).getValue (),
164+ TypePath .fromString ("1;" ),
165+ "Lorg/jboss/jandex/test/MyAnnotation;" , true );
166+ av .visit ("value" , "111" );
167+ av .visitEnd ();
168+ }
169+ return mv ;
170+ }
171+ };
172+ }
173+ })
174+ .make ()
175+ .getBytes ();
176+
177+ Indexer indexer = new Indexer ();
178+ indexer .index (new ByteArrayInputStream (bytes ));
179+ Index index = indexer .complete ();
180+
181+ ClassInfo clazz = index .getClassByName (TEST_CLASS );
182+ assertNotNull (clazz );
183+ MethodInfo method = clazz .firstMethod ("foo" );
184+ assertNotNull (method );
185+ Type type = method .returnType ();
186+ assertNotNull (type );
187+ assertEquals ("java.util.List<java.lang.@MyAnnotation(\" 000\" ) String>" , type .toString ());
188+ }
105189}
0 commit comments