Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add workaround for wrong type annotation path generated by Kotlin #285

Merged
merged 1 commit into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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