Skip to content

Commit

Permalink
Merge pull request #285 from Ladicek/kotlin-wrong-type-annotation-path
Browse files Browse the repository at this point in the history
Add workaround for wrong type annotation path generated by Kotlin
  • Loading branch information
Ladicek committed Nov 22, 2022
2 parents 9dee4e6 + b8bf570 commit 8ab183e
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 12 deletions.
20 changes: 18 additions & 2 deletions core/src/main/java/org/jboss/jandex/Indexer.java
Original file line number Diff line number Diff line change
Expand Up @@ -1223,9 +1223,9 @@ private Type resolveTypePath(Type type, TypeAnnotationState typeAnnotationState)
}

// the annotation targets the type itself
// clone the annotation instance with a null target so that it can be interned
type = intern(type.addAnnotation(AnnotationInstance.create(typeAnnotationState.annotation, null)));
typeAnnotationState.target.setTarget(type); // FIXME
// Clone the instance with a null target so that it can be interned
typeAnnotationState.target.setTarget(type);
return type;
}

Expand All @@ -1243,6 +1243,14 @@ private Type resolveTypePath(Type type, TypeAnnotationState typeAnnotationState)
return intern(arrayType.copyType(nested, arrayType.dimensions() - dimensions));
}
case PARAMETERIZED: {
// hack for Kotlin which emits a wrong type annotation path
// (see KotlinTypeAnnotationWrongTypePathTest)
if (type.kind() == Type.Kind.WILDCARD_TYPE
&& type.asWildcardType().bound() != null
&& type.asWildcardType().bound().kind() == Type.Kind.PARAMETERIZED_TYPE) {
return type;
}

ParameterizedType parameterizedType = type.asParameterizedType();
Type[] arguments = parameterizedType.argumentsArray().clone();
int pos = element.pos;
Expand Down Expand Up @@ -1375,6 +1383,14 @@ private Type searchTypePath(Type type, TypeAnnotationState typeAnnotationState)
return searchTypePath(arrayType.component(), typeAnnotationState);
}
case PARAMETERIZED: {
// hack for Kotlin which emits a wrong type annotation path
// (see KotlinTypeAnnotationWrongTypePathTest)
if (type.kind() == Type.Kind.WILDCARD_TYPE
&& type.asWildcardType().bound() != null
&& type.asWildcardType().bound().kind() == Type.Kind.PARAMETERIZED_TYPE) {
return type;
}

ParameterizedType parameterizedType = type.asParameterizedType();
return searchTypePath(parameterizedType.argumentsArray()[element.pos], typeAnnotationState);
}
Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/org/jboss/jandex/ParameterizedType.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
* returns the type of the enclosing class. Such inner type may itself be parameterized.
* <p>
* For example, assume the following declarations:
*
* <pre class="brush:java">
* class A&lt;T&gt; {
* class B {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.jboss.jandex.test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.Index;
import org.jboss.jandex.Indexer;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.junit.jupiter.api.Test;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.StubMethod;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.OpenedClassReader;

public class KotlinTypeAnnotationWrongTypePathTest {
private static final String TEST_CLASS = "org.jboss.jandex.test.TestClass";

// emulates a Kotlin bug in emitting a wrong type annotation path
//
// for a method declaration `fun foo(bar: List<List<@Valid String>>) {}`,
// the Kotlin compiler emits the following signature:
// `(Ljava/util/List<+Ljava/util/List<Ljava/lang/String;>;>;)V`
// which essentially corresponds to the following Java method declaration:
// `public final void foo(List<? extends List<@Valid String>> bar) {}`
//
// the Kotlin compiler emits the following type annotation path:
// `location=[TYPE_ARGUMENT(0), TYPE_ARGUMENT(0)]`, which is incorrect,
// because it corresponds to a Java method declaration of:
// `public final void foo(List<List<@Valid String>> bar) {}`
//
// when the first Java declaration above is compiled with a Java compiler,
// it emits the following type annotation path:
// `location=[TYPE_ARGUMENT(0), WILDCARD, TYPE_ARGUMENT(0)]`

@Test
public void test() throws IOException {
// List<List<String>>
TypeDescription.Generic annotatedString = TypeDescription.Generic.Builder.of(String.class)
.annotate(AnnotationDescription.Builder.ofType(MyAnnotation.class)
.define("value", "foobar").build())
.build();
TypeDescription.Generic listOfAnnotatedString = TypeDescription.Generic.Builder.parameterizedType(
TypeDescription.Generic.Builder.of(List.class).build().asErasure(), annotatedString).build();
TypeDescription.Generic listOfListOfAnnotatedString = TypeDescription.Generic.Builder.parameterizedType(
TypeDescription.Generic.Builder.of(List.class).build().asErasure(), listOfAnnotatedString).build();

byte[] bytes = new ByteBuddy()
.subclass(Object.class)
.name(TEST_CLASS)
.defineMethod("foo", void.class)
.withParameter(listOfListOfAnnotatedString, "bar")
.intercept(StubMethod.INSTANCE)
.visit(new AsmVisitorWrapper.AbstractBase() {
@Override
public ClassVisitor wrap(TypeDescription instrumentedType, ClassVisitor classVisitor,
Implementation.Context implementationContext, TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fields, MethodList<?> methods,
int writerFlags, int readerFlags) {
return new ClassVisitor(OpenedClassReader.ASM_API, classVisitor) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
if ("foo".equals(name)) {
// List<? extends List<String>>
signature = "(Ljava/util/List<+Ljava/util/List<Ljava/lang/String;>;>;)V";
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
};
}
})
.make()
.getBytes();

Indexer indexer = new Indexer();
indexer.index(new ByteArrayInputStream(bytes));
Index index = indexer.complete();

ClassInfo clazz = index.getClassByName(TEST_CLASS);
assertNotNull(clazz);
MethodInfo method = clazz.firstMethod("foo");
assertNotNull(method);
assertEquals(1, method.parametersCount());
Type type = method.parameterType(0);
assertNotNull(type);

assertEquals("java.util.List<? extends java.util.List<java.lang.String>>", type.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
import org.junit.jupiter.api.Test;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.NamingStrategy;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.StubMethod;

Expand All @@ -36,13 +34,8 @@ public class TooManyMethodParametersTest {
@Test
public void test() throws IOException {
DynamicType.Builder.MethodDefinition.ParameterDefinition<Object> builder = new ByteBuddy()
.with(new NamingStrategy.AbstractBase() {
@Override
protected String name(TypeDescription superClass) {
return TEST_CLASS;
}
})
.subclass(Object.class)
.name(TEST_CLASS)
.defineMethod("hugeMethod", void.class)
.withParameter(String.class, "p0")
.annotateParameter(AnnotationDescription.Builder.ofType(MyAnnotation.class).define("value", "0").build());
Expand All @@ -51,13 +44,13 @@ protected String name(TypeDescription superClass) {
.annotateParameter(
AnnotationDescription.Builder.ofType(MyAnnotation.class).define("value", "" + i).build());
}
byte[] syntheticClass = builder
byte[] bytes = builder
.intercept(StubMethod.INSTANCE)
.make()
.getBytes();

Indexer indexer = new Indexer();
indexer.index(new ByteArrayInputStream(syntheticClass));
indexer.index(new ByteArrayInputStream(bytes));
Index index = indexer.complete();

ClassInfo clazz = index.getClassByName(DotName.createSimple(TEST_CLASS));
Expand Down

0 comments on commit 8ab183e

Please sign in to comment.