Skip to content

Commit 73d3eb7

Browse files
committed
fix handling of wrong type annotation path emitted by Kotlin
1 parent 059cacc commit 73d3eb7

File tree

2 files changed

+121
-9
lines changed

2 files changed

+121
-9
lines changed

core/src/main/java/org/jboss/jandex/Indexer.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,13 +1439,28 @@ private Type resolveTypePath(Type type, TypeAnnotationState typeAnnotationState)
14391439
return intern(arrayType.copyType(nested, arrayType.dimensions() - dimensions));
14401440
}
14411441
case PARAMETERIZED: {
1442-
// hack for Kotlin which emits a wrong type annotation path
1443-
// (see KotlinTypeAnnotationWrongTypePathTest)
1442+
// hacks for Kotlin which emits a wrong type annotation path
1443+
// see KotlinTypeAnnotationWrongTypePathTest.test1()
14441444
if (type.kind() == Type.Kind.WILDCARD_TYPE
14451445
&& type.asWildcardType().bound() != null
14461446
&& type.asWildcardType().bound().kind() == Type.Kind.PARAMETERIZED_TYPE) {
14471447
return type;
14481448
}
1449+
// see KotlinTypeAnnotationWrongTypePathTest.test2()
1450+
if (type.kind() == Type.Kind.PARAMETERIZED_TYPE) {
1451+
ParameterizedType toCheck = type.asParameterizedType();
1452+
if (elements.noNestedBeforeThisParameterizedAfterPreviousParameterized()) {
1453+
// we need the _outermost_ parameterized type
1454+
while (toCheck.owner() != null && toCheck.owner().kind() == Type.Kind.PARAMETERIZED_TYPE) {
1455+
toCheck = toCheck.owner().asParameterizedType();
1456+
}
1457+
}
1458+
if (element.pos >= toCheck.argumentsArray().length) {
1459+
return type;
1460+
}
1461+
// checks below for type argument position are no longer necessary,
1462+
// but we keep them in case this hack is deleted in the future
1463+
}
14491464

14501465
ParameterizedType parameterizedType = type.asParameterizedType();
14511466

@@ -1585,13 +1600,26 @@ private Type searchTypePath(Type type, TypeAnnotationState typeAnnotationState)
15851600
return searchTypePath(arrayType.component(), typeAnnotationState);
15861601
}
15871602
case PARAMETERIZED: {
1588-
// hack for Kotlin which emits a wrong type annotation path
1589-
// (see KotlinTypeAnnotationWrongTypePathTest)
1603+
// hacks for Kotlin which emits a wrong type annotation path
1604+
// see KotlinTypeAnnotationWrongTypePathTest.test1()
15901605
if (type.kind() == Type.Kind.WILDCARD_TYPE
15911606
&& type.asWildcardType().bound() != null
15921607
&& type.asWildcardType().bound().kind() == Type.Kind.PARAMETERIZED_TYPE) {
15931608
return type;
15941609
}
1610+
// see KotlinTypeAnnotationWrongTypePathTest.test2()
1611+
if (type.kind() == Type.Kind.PARAMETERIZED_TYPE) {
1612+
ParameterizedType toCheck = type.asParameterizedType();
1613+
if (elements.noNestedBeforeThisParameterizedAfterPreviousParameterized()) {
1614+
// we need the _outermost_ parameterized type
1615+
while (toCheck.owner() != null && toCheck.owner().kind() == Type.Kind.PARAMETERIZED_TYPE) {
1616+
toCheck = toCheck.owner().asParameterizedType();
1617+
}
1618+
}
1619+
if (element.pos >= toCheck.argumentsArray().length) {
1620+
return type;
1621+
}
1622+
}
15951623

15961624
ParameterizedType parameterizedType = type.asParameterizedType();
15971625
if (elements.noNestedBeforeThisParameterizedAfterPreviousParameterized()) {

core/src/test/java/org/jboss/jandex/test/KotlinTypeAnnotationWrongTypePathTest.java

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,19 @@
2323
import net.bytebuddy.description.type.TypeDescription;
2424
import net.bytebuddy.implementation.Implementation;
2525
import net.bytebuddy.implementation.StubMethod;
26+
import net.bytebuddy.jar.asm.AnnotationVisitor;
2627
import net.bytebuddy.jar.asm.ClassVisitor;
2728
import net.bytebuddy.jar.asm.MethodVisitor;
29+
import net.bytebuddy.jar.asm.TypePath;
30+
import net.bytebuddy.jar.asm.TypeReference;
2831
import net.bytebuddy.pool.TypePool;
2932
import net.bytebuddy.utility.OpenedClassReader;
3033

3134
public 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

Comments
 (0)