Is JVM reflection broken?

I noticed an inconsistent behavior in reflection methods when trying to access some static properties of types in java.lang via reflection.
I’ll provide further details below.
The main question here is: am I missing something or is there some bug in the reflection library?

Details

On Kotlin JVM Kotlin, some basic types (e.g. Long, Int, etc) come with a companion object letting developers access static properties and methods of the corresponding JVM types.
Accordingly, Kotlin reflection lets developers access such a companion object, as it would for any Kotlin class exposing a companion object.
However, when accessing some properties, exceptions are thrown unexpectedly.

Consider for instance the case of Int.MAX_VALUE:

val member = Int::class.companionObject?.members?.firstOrNull { it.name == "MAX_VALUE" }
assertEquals(Int.MAX_VALUE, member?.call()) // throws kotlin.reflect.jvm.internal.KotlinReflectionInternalError:
// No accessor found for property val kotlin.Int.Companion.MAX_VALUE: kotlin.Int

A similar issue occurs when accessing Int.MIN_VALUE as a property:

val property = Int::class.companionObject?.memberProperties?.firstOrNull { it.name == "MIN_VALUE" }
assertEquals(Int.MIN_VALUE, property?.call()) // throws kotlin.reflect.jvm.internal.KotlinReflectionInternalError: 
// No accessor found for property val kotlin.Int.Companion.MIN_VALUE: kotlin.Int

Notably, a similar issue occurs when accessing Array.size, even if it does not corresponds to a static method:

val member = Array::class.members.firstOrNull { it.name == "size" }
val instance = arrayOf("A")
assertEquals(instance.size, member?.call(instance)) // kotlin.reflect.jvm.internal.KotlinReflectionInternalError: 
// No accessor found for property val kotlin.Array<T>.size: kotlin.Int
1 Like

While reproducing this and trying to change the code minimally I stumbled upon the following few examples with varying success.

Array<T>::size.get(instance: Array<T>) breaking on the JVM exclusively with a NoSuchMethodError:

println(Array<String>::lastIndex.get(arrayOf("A")))
println(Array<String>::size.get(arrayOf("A")))

Running this in the JVM on the Kotlin Playground (or locally) produces the following output:

0
Exception in thread "main" java.lang.NoSuchMethodError: [Ljava.lang.Object;.getSize()I
 at FileKt$main$2.get (File.kt:8) 
 at FileKt.main (File.kt:8) 
 at FileKt.main (File.kt:-1) 

Running this in JS on the Kotlin Playground works as expected.

Calling the getters for Int.Companion.MIN_VALUE and Int.Companion.MAX_VALUE works by calling KProperty0<T>.get(), but not by KProperty0<T>.getter.call(), which I would have assumed to produce identical output:

val minValue: KProperty0<Int> = Int.Companion::MIN_VALUE
println(minValue.get())
println(minValue.getter.call())

Running this in the JVM produces the following output:

-2147483648

kotlin.reflect.jvm.internal.KotlinReflectionInternalError: No accessor found for property val kotlin.Int.Companion.MIN_VALUE: kotlin.Int
 at kotlin.reflect.jvm.internal.KPropertyImplKt.computeCallerForAccessor(KPropertyImpl.kt:273)
 at kotlin.reflect.jvm.internal.KPropertyImplKt.access$computeCallerForAccessor(KPropertyImpl.kt:1)
 at kotlin.reflect.jvm.internal.KPropertyImpl$Getter$caller$2.invoke(KPropertyImpl.kt:156)
 at kotlin.reflect.jvm.internal.KPropertyImpl$Getter$caller$2.invoke(KPropertyImpl.kt:147)
 at kotlin.reflect.jvm.internal.ReflectProperties$LazyVal.invoke(ReflectProperties.java:62)
 at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:31)
 at kotlin.reflect.jvm.internal.KPropertyImpl$Getter.getCaller(KPropertyImpl.kt)
 at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:108)
 ...

I would assume a bug in either the compiler or the reflection library - have you created an issue yet?

1 Like

Here it is https://youtrack.jetbrains.com/issue/KT-44418

2 Likes