Skip to content

Build error processing type annotation on nested generic method parameters #29376

Closed
@grasfrosch

Description

@grasfrosch

Describe the bug

In a kotlin quarkus project compiled with the compiler argument -Xemit-jvm-type-annotations, the build fails if a nested type parameter of a method parameter is annotated with a type parameter, for example

fun foo(bar: List<List<@Valid String>>) {

}

Expected behavior

The build should complete without exceptions

Actual behavior

The build fails with the following exception:

    Failed to execute goal io.quarkus.platform:quarkus-maven-plugin:2.14.0.Final:build (default) on project (...): Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
        [error]: Build step io.quarkus.deployment.steps.ApplicationIndexBuildStep#build threw an exception:   java.lang.IllegalArgumentException: Not a parameterized type!
        at org.jboss.jandex.Type.asParameterizedType(Type.java:248)
        at org.jboss.jandex.Indexer.resolveTypePath(Indexer.java:1227)
        at org.jboss.jandex.Indexer.resolveTypePath(Indexer.java:1234)
        at org.jboss.jandex.Indexer.resolveTypeAnnotation(Indexer.java:1098)
        at org.jboss.jandex.Indexer.resolveTypeAnnotations(Indexer.java:950)
        at org.jboss.jandex.Indexer.indexWithSummary(Indexer.java:2323)
        at org.jboss.jandex.Indexer.index(Indexer.java:2277)
        at io.quarkus.deployment.steps.ApplicationIndexBuildStep$1.visitFile(ApplicationIndexBuildStep.java:49)
        at io.quarkus.deployment.steps.ApplicationIndexBuildStep$1.visitFile(ApplicationIndexBuildStep.java:34)
        at java.base/java.nio.file.Files.walkFileTree(Files.java:2811)
        at java.base/java.nio.file.Files.walkFileTree(Files.java:2882)
        at io.quarkus.deployment.steps.ApplicationIndexBuildStep.build(ApplicationIndexBuildStep.java:34)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:909)
        at io.quarkus.builder.BuildContext.run(BuildContext.java:281)
        at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
        at java.base/java.lang.Thread.run(Thread.java:833)
        at org.jboss.threads.JBossThread.run(JBossThread.java:501)

How to Reproduce?

Try to build the following example project:

code-with-quarkus.zip

Output of uname -a or ver

No response

Output of java -version

17.0.4

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.14.0.Final

Build tool (ie. output of mvnw --version or gradlew --version)

No response

Additional information

The corresponding java code does not seem to trigger the same problem, even though the annotation information shown in javap -v of the generated class files doesn't look different to me.

Activity

quarkus-bot

quarkus-bot commented on Nov 21, 2022

@quarkus-bot
geoand

geoand commented on Nov 21, 2022

@geoand
Contributor

@Ladicek I am sure you'll have fun with this one 😉

self-assigned this
on Nov 21, 2022
Ladicek

Ladicek commented on Nov 21, 2022

@Ladicek
Contributor

Looks like a Jandex bug indeed.

Ladicek

Ladicek commented on Nov 22, 2022

@Ladicek
Contributor

Okay so this one is interesting. The method

fun foo(bar: List<List<@Valid String>>) {
}

is compiled to a JVM method with the following Signature attribute:

(Ljava/util/List<+Ljava/util/List<Ljava/lang/String;>;>;)V

In Java syntax, this corresponds to

public final void foo(List<? extends List<@Valid String>> bar) {
}

The type annotation is compiled down to this:

    RuntimeVisibleTypeAnnotations:
      0: #15(): METHOD_FORMAL_PARAMETER, param_index=0, location=[TYPE_ARGUMENT(0), TYPE_ARGUMENT(0)]
        javax.validation.Valid

So the annotation is correctly attributed to the 1st (or 0th if you wish) parameter of the method, but the type annotation path is wrong.

The type annotation path is basically:

  1. take the type of the 0th parameter, it must be a parameterized type, and take the 0th type argument
  2. take the previously obtained type, it must be a parameterized type, and take the 0th type argument

The 2nd step fails -- because the type obtained in step 1 is a wildcard type, not a parameterized type. Remember that the type is effectively List<? extends List<@Valid String>>.

For the Java snippet above, javac emits the annotation like this:

    RuntimeVisibleTypeAnnotations:
      0: #38(): METHOD_FORMAL_PARAMETER, param_index=0, location=[TYPE_ARGUMENT(0), WILDCARD, TYPE_ARGUMENT(0)]
        javax.validation.Valid

Where the type annotation path is basically:

  1. take the type of the 0th parameter, it must be a parameterized type, and take the 0th type argument
  2. take the previously obtained type, it must be a wildcard type which is an argument of a parameterized type, and take its bound
  3. take the previously obtained type, it must be a parameterized type, and take the 0th type argument

That navigates to the String type correctly.

Ladicek

Ladicek commented on Nov 22, 2022

@Ladicek
Contributor

TBH, I'm not sure what's the best solution here. I agree Jandex shouldn't fail like this, but it can't be denied that the Kotlin compiler emits internally inconsistent bytecode. I'm thinking Jandex should probably just skip this type annotation.

Ladicek

Ladicek commented on Nov 22, 2022

@Ladicek
Contributor

So this will be fixed in Jandex 3.0.4, but note that the annotation will be just ignored. Kotlin needs to fix their compiler to emit the type annotation path correctly.

geoand

geoand commented on Nov 22, 2022

@geoand
Contributor

@Ladicek maybe open a Kotlin issue?

Ladicek

Ladicek commented on Nov 22, 2022

@Ladicek
Contributor

Yea, I should do that. Tomorrow :-)

grasfrosch

grasfrosch commented on Nov 23, 2022

@grasfrosch
Author

Awesome, thanks for the quick fix! Just ignoring the annotation should be good enough for most cases. It's certainly good enough for our case where that bug got triggered by the implicit Continuation parameter of a suspending method - I would expect that's probably the easiest way to trigger it.

Ladicek

Ladicek commented on Nov 23, 2022

@Ladicek
Contributor

@grasfrosch if you have some other code that also fails with this error, especially if it includes suspending functions, please share, I'd like to check my fix with it. And thanks for the report!

grasfrosch

grasfrosch commented on Nov 23, 2022

@grasfrosch
Author

@Ladicek I can't share the original code, but effectively we had a suspending lambda looking something like this:

val pair = runBlocking {
    Pair(JavaClass().notNullAnnotatedFunction(), "sth else")
}

When Quarkus 2.14 updated the jetbrains annotations dependency, TYPE_USE got included as target for the @NotNull-Annotation and so the inferred return type changed from Pair<String,String> to Pair<@NotNull String, String>. The generated bytecode for the lambda's class has a method public final java.lang.Object invoke(kotlinx.coroutines.CoroutineScope, kotlin.coroutines.Continuation<? super kotlin.Pair<java.lang.String, java.lang.String>>); with the

RuntimeInvisibleTypeAnnotations:
  0: #32(): METHOD_FORMAL_PARAMETER, param_index=1, location=[TYPE_ARGUMENT(0), TYPE_ARGUMENT(0)]
    org.jetbrains.annotations.NotNull

and the whole thing blew up in our faces. That one took a while to find.

From playing around a bit, some other functions that produce the error in the current version:

  • suspend fun foo(): List<@NotNull String> = listOf()
  • suspend fun foo(): Pair<String, @NotNull String>? = null
  • fun foo(bar: List<List<List<@NotNull String>>>) = Unit

Both runtime visible and invisible type annotations (for example @Valid and @NotNull) seem to trigger the bug.
Interesting to note is that the generic types so far seem to be declared as List<out E> and Continuation<in T>, which seems to get translated to the wildcard during compilation. I can't reproduce the issue using classes like Optional or Array.
Thanks again for looking into this!

Ladicek

Ladicek commented on Nov 23, 2022

@Ladicek
Contributor

Thanks, I'll take a look at the additional cases, but I expect my fix I merged yesterday should cover them.

Both runtime visible and invisible type annotations (for example @Valid and @NotNull) seem to trigger the bug.

That would be expected, because Jandex since version 3.0 looks at class-retained (aka runtime-invisible) annotations as well.

Interesting to note is that the generic types so far seem to be declared as List<out E> and Continuation<in T>, which seems to get translated to the wildcard during compilation. I can't reproduce the issue using classes like Optional or Array.

That sounds about right. Invariant declaration sites in Kotlin would naturally lead to invariant use sites in the bytecode -- it's declaration sites that use variance that end up being compiled as wildcards.

Thanks again!

Ladicek

Ladicek commented on Nov 24, 2022

@Ladicek
Contributor

OK so the fix in Jandex seems to work fine with the other cases. I'll release 3.0.4 today.

I also reported the issue in https://youtrack.jetbrains.com/issue/KT-55128

Ladicek

Ladicek commented on Nov 24, 2022

@Ladicek
Contributor

It also turns out that this bug was discovered before: https://youtrack.jetbrains.com/issue/KT-13228/Generate-annotations-on-types-in-JVM-8-target-mode#focus=Comments-27-4762338.0-0 Unfortunately the reporter didn't file a Jandex bug, so I didn't learn about it sooner.

mschorsch

mschorsch commented on Nov 24, 2022

@mschorsch
Contributor

It also turns out that this bug was discovered before: https://youtrack.jetbrains.com/issue/KT-13228/Generate-annotations-on-types-in-JVM-8-target-mode#focus=Comments-27-4762338.0-0 Unfortunately the reporter didn't file a Jandex bug, so I didn't learn about it sooner.

Seems there is a workaround:

Forcing Kotlin to generate code without wildcards (via @JvmSuppressWildcards) produces valid code, if one can make do without wildcards.

added this to the 2.15 - main milestone on Nov 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

    Development

    Participants

    @Ladicek@gsmet@geoand@mschorsch@grasfrosch

    Issue actions

      Build error processing type annotation on nested generic method parameters · Issue #29376 · quarkusio/quarkus