Quick Bites: Discovering a Bug in the Kotlin Compiler

Quick Bites: Discovering a Bug in the Kotlin Compiler

Sometimes Java interop brings bugs along with the benefits

Summary

I was working with a Kotlin class which extended a Java class and the Kotlin class defined a less-accessible property with the same name as a more-accessible field of the Java class and I ran into an IllegalAccessError.

It's now filed as a bug to the Kotlin team at JetBrains.

Background

About Kotlin

Kotlin is a JVM-based language and one of its selling points is that it seamlessly works with any existing Java code. This interoperability of Kotlin lets you start using it without the worry of rewriting or throwing away the millions of lines of Java which your organization has already written and depends on.

Kotlin Properties

Kotlin's properties feature is a succinct way of defining getters and setters for a variable in a class.


class Foo {
  public val fooProperty: String
    get() = "Fooooo"
}

Another shortcut available in Kotlin is to define the properties of a class by specifying them in the constructor:


// The Kotlin way
class FooBar(private val foo: String, private val bar: String) { }

// The above is equivalent to this in Java
class FooBar {
  private String foo;
  private String bar;

  public FooBar(String foo, String bar) {
    this.foo = foo;
    this.bar = bar;
  }
}

A bad Cocktail of Kotlin and Java

Now, let's say you have the following Java class:

class JavaClass {
  public String injectedField;
}

and your Kotlin class extended this class as:

 class KotlinClass(private val injectedField: String): JavaClass() {
  fun printInjectedField(): String { 
    println(injectedField)
  }
}

You would expect the following behavior:

  • JavaClass superclass has a field with the same name injectedField as KotlinClass's private property,
  • The Kotlin property will be accessed in printInjectedField as there is no usage of the super keyword.

When you run the above code you will be met with an IllegalAccessError as the printInjectedField function will try to access the injectedField value in the Java superclass.

How it was caught

Guice's Field Injection is Ugly

The setup for the error is contrived and you would only run into in few special cases. One such case is with Guice's field injection.

The Java class which our Kotlin class extended from was using field injection in many places (which is a code smell). We were passing these dependencies to the Kotlin class via its constructor and we were puzzled whenever we ran into the IllegalAccessError as we were sure that we were referring to the correct instance.

A Little Help from the Compiler Team

Our first action was to rely on our Googling skills to find something on Stack Overflow and we failed to find anything useful for us. After asking this question in Google's version of an internal Stack Overflow, we received a response from the people working on the Kotlin compiler at Google and they suggested to rename the property name (i.e. don't keep the same as the field of the Java superclass).

And that fix worked!

That wasn't the end of the investigation as they followed up with the JetBrains team to identify the root cause of the issue as they were unsure of the behavior themselves and this is now being tracked as a possible bug in the Kotlin compiler: youtrack.jetbrains.com/issue/KT-54393/Chang..

Lessons

  • Java interoperability is good but there might be edge cases which you might unfortunately run into.
  • Don't be afraid to reach out to folks working on Kotlin.
  • Don't use Guice's field injection if you can avoid it.