Closed milgner closed 2 months ago
I am afraid that that is a JVM bug. If you update the JVM, the error will likely go away. Could you validate that? I submitted a long row of patched to OpenJDK myself, and this looks familiar. Could you give it a try?
Will give it a try. This happened on the last OpenJDK 21. Is there any specific version I should try? Otherwise I'll just try 22 GA first.
Should be any latest version. The exception occurs in code of the JDK. So either the generic type information is faulty, or there is a bug in the JDK. Even with a bug, there should not be a nullpointer, though.
I have tried this with an updated OpenJDK 21 as well as the latest OpenJDK 22 but they produce the same exception with the same null
element in actualTypeArguments
.
I looked into whether it would be possible to work around the issue by changing the mockk code. Unfortunately this is a critical path where the redefine
method is being used to create the mock itself. And in order to instantiate the instrumented type, it has to transform the declared methods into the token list which is where it fails down the line.
So I delved into the bytebuddy code a bit and found the following place which seems to cause the issue:
in TypeDescription.Generic.Visitor.Substitutor.onWildcard
, an instance of OfWildcardType.Latent
is constructed. For this, it queries the upper bounds of the wildcard which in fact is a list with only one null
element in it.
I am quite clueless about these JVM internals but it seems to contradict the API documentation which says
If no upper bound is explicitly declared, the upper bound is Object
So I guess this is probably an issue with Kotlin that this library cannot do anything about. Consequently I have opened an issue in the official Kotlin tracker: https://youtrack.jetbrains.com/issue/KT-70235/WildcardTypeupperBounds-for-Kotlin-based-code-returns-null-element
Let me know if anyone has further insights. It might be a good idea to keep this open as a reference.
Could you create me a quick reproducer? I am wondering if this might be a bug in the Kotlin compiler which triggers a bug in the JDK that is not yet resolved. Naturally, the JVM is tested with javac compiled classes, so wrongful metadata is not always handled correctly. It might be that the class in question is compiled with an outdated Kotlin compiler and that the problem would be solved with a compiler update.
There is a reproducer at https://github.com/milgner/Repro-Kotlin-WildcardType-UpperBounds-Null - does that help? There are two tests with mockk which both trigger the same issue.
This seems to me like a bug in the Kotlin compiler which generates an invalid generic signature which again is not handled properly in the Java reflection utility. The MockkMePlease
class looks like this in Kotlin:
class MockkMePlease(val foo: NonEmptySet<Int>) { }
which generates a class file similar to the following:
public final class com.marcusilgner.mockk_repro_1130.MockkMePlease {
private final java.util.Set<A> foo;
private com.marcusilgner.mockk_repro_1130.MockkMePlease(java.util.Set<? extends A>);
public final java.util.Set<A> getFoo-5sCjGKo();
public com.marcusilgner.mockk_repro_1130.MockkMePlease(java.util.Set, kotlin.jvm.internal.DefaultConstructorMarker);
}
The type variable A
is nowhere defined and as such it cannot be resolved. This results in the variable being represented as null
, which should not be the case.
I can see if I can handle this more gracefully in Byte Buddy, and I will report the bug to OpenJDK. Would you like to take it up with the Kotlin team? To really solve this error, Kotlin needs to fix its compiler and then the library has to be recompiled with an updated version of that compiler.
OpenJDK issue: https://bugs.openjdk.org/browse/JDK-8337302
In a project with mockk, I encountered a curious issue when trying to mock some code that uses Arrows
NonEmptySet
, which is a JVM-inlined class with a type parameter which apparently gets erased during compilation.This triggers the
IllegalArgumentException
inTypeDefinition
.The field in question,
MockkMePlease.foo
, has the following definition:class MockkMePlease(val foo: NonEmptySet<Int>) {}
Which results in a
FieldRepository
whosegenericType
has arawType
ofjava.util.Set
and a list ofactualTypeArguments
with onenull
element.Here is a screenshot of the debugger which might help to grasp that information:
The resulting mockk issue is https://github.com/mockk/mockk/issues/1130