Found nasty reflection core bug related to inline classes holding Java types

I have a codebase of 10k lines and this bug was very hard to find, after hours of debugging
I finally created a minimal example where this bug is reproducible… Please, I’m in need to resolve this
as soon as possible since I need to go to the production, but this bug is a roadblocking deploy.
If I switch UUID with Int the NullPointerException will not occur.

This code…

@JvmInline
value class Id<T>(val value: UUID = UUID.randomUUID()) {
    override fun toString(): String = this.value.toString()
}

data class Parent(val id: Id<Parent>?)

fun main() {
    val parent = Parent(id = null)
    val kProperty1 = Parent::class.memberProperties.first()

    assert(kProperty1 == Parent::id)
    println("Key '${Parent::id}' has value: '${Parent::id.get(parent)}'")
    println("Key '$kProperty1' has value: ...")
    println(kProperty1.get(parent))
}

Creates this output…

Key 'val org.example.Parent.id: org.example.Id<org.example.Parent>?' has value: 'null'
Key 'val org.example.Parent.id: org.example.Id<org.example.Parent>?' has value: ...
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.UUID.toString()" because "arg0" is null
	at org.example.Id.toString-impl(Main.kt:8)
	at org.example.Id.toString(Main.kt:8)
	at java.base/java.lang.String.valueOf(String.java:4993)
	at java.base/java.io.PrintStream.println(PrintStream.java:1186)
	at org.example.MainKt.main(Main.kt:20)
	at org.example.MainKt.main(Main.kt)

Can this be resolved in other ways that I’m not aware off? The API of the program must stay the same since this is a deploy requirements…

It seems that memberProperties has bad implementation… getting property over memberProperties creates inline object with null value inside and not the null value for the object itself…

I definitely can’t help you fix this because I don’t know Kotlin well. I only subscribe to emails for new topics here because I’ve interested in Kotlin lately. But I wanted to jump in and let you know that as far as I know, bugs should always be reported on the YouTrack (https://youtrack.jetbrains.com/issues/KT). I saw some GitHub repos before where maintainers mentioned issues would be migrated there. I think that’s the best ways to get eyes on this bug report.

Thank you for repply, yes I did report the bug right away as always, I joust created the post here also since the response from developers is a little bit faster here.

https://youtrack.jetbrains.com/issue/KT-67026/KClass.memberProperties-is-returning-properties-which-are-extracting-invalid-inline-values-from-extractor.

I’m trying to understand your code here. It looks like it should be throwing a null pointer because value is null because id is null?

val parent = Parent(id = null)

Not sure if the devs will see it as a bug since it falls in the java interoperability realm where null pointer exceptions are allowed.

The only possibility I see around it is perhaps by implementing your toString method as an extension instead of, or in addition to the method override on Id<T>?. Something like:

Untested

@JvmInline
value class Id<T>(val value: UUID = UUID.randomUUID()) {
   override fun toString(): String = this.value.toString() //perhaps unnecessary? not sure
}

fun <T> Id<T>?.toString() : String  = if (this != null) value.toString() else "null"

Parent::class.memberProperties.first() method return the same object instant as Parent::id… That’s why I put assert(Parent::class.memberProperties.first() == Parent::id) assertion… But can you please tell me why if those 2 things are the same they are behaving differently if i’m trying to extract value with get method…

    println("Key '${Parent::id}' has value: '${Parent::id.get(parent)}'")
    println("Key '$kProperty1' has value: ${kProperty1.get(parent)}")

${Parent::id.get(parent)} is returning null value which is totaly correct but kProperty1.get(parent) is returning null pointer exception… and this is wrong because it should return null joust like ${Parent::id.get(parent)} since this is correct behavior…

Actually, your code is slightly different in your println’s.

do you get the same exception if you do:

println("Key '$kProperty1' has value: '${kProperty1.get(parent)}'")

Actually, this is not true. They not only return different instances, they even return entirely different types of objects:

println(kProperty1::class) // class kotlin.reflect.jvm.internal.KProperty1Impl
println(Parent::id::class) // class TestKt$main$3

Only because == returns true doesn’t meant they are exactly same objects. It returns false if using ===. Also, I wouldn’t generally assume they have to always behave exactly the same, because they are quite different properties - one of them is more “precise”, another is more generic.

But… I think you’re generally right, something is wrong here. We shouldn’t get a boxed Id object with null inside. We should get null instead.

2 Likes

I agree that object instances can be different, but underlying behavior should be the same. I would probably conclude that something is wrong with the implementation of memberProperties, it probably
returns objects that are having incomplete implementation inside?