Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin IllegalAccessError with += and -= for delegated Interface

Tags:

kotlin

I've defined this class:

class NeverNullMap<K, V>(private val backing: MutableMap<K, V> = mutableMapOf(), val default: () -> V): MutableMap<K, V> by backing {
    override operator fun get(key: K): V = backing.getOrPut(key, default)
}

And I can use it perfectly fine like this:

fun main(args: Array<String>) {
    val myMap = NeverNullMap<String, Int> {0}
    println(myMap["test"])
    myMap["test"] = myMap["test"] + 10
    println(myMap["test"])
}

as expected the output is:

0
10

But when I try to change it to:

fun main(args: Array<String>) {
    val myMap = NeverNullMap<String, Int> {0}
    println(myMap["test"])
    myMap["test"] += 10
    println(myMap["test"])
}

I get:

Exception in thread "main" java.lang.IllegalAccessError: tried to access method kotlin.collections.MapsKt__MapsKt.set(Ljava/util/Map;Ljava/lang/Object;Ljava/lang/Object;)V from class Day08Kt
    at Day08Kt.main(Day08.kt:10)

Why is this happening?

Edit:

Digging a bit into decompiled code both get compiled to completly diffrent code.

In the working version without the += it gets compiled to:

  Map var2 = (Map)myMap;
  String var3 = "test";
  Integer var4 = ((Number)myMap.get("test")).intValue() + 10;
  var2.put(var3, var4);

The non working version gets compiled to:

MapsKt.set(myMap, "test", ((Number)myMap.get("test")).intValue() + 10);

So it calles this function: https://github.com/JetBrains/kotlin/blob/1.2.0/libraries/stdlib/src/kotlin/collections/Maps.kt#L175

I still have no idea why that produces the Error, just why the first version behaves diffrently.

Edit: YouTrack link to the report

like image 816
usbpc102 Avatar asked Dec 08 '17 15:12

usbpc102


1 Answers

Edit: yes, this is a bug, it has been merged with KT-14227:

Incorrect code is generated when using MutableMap.set with plusAssign operator


After compilation (or decompilation, in this case), MapsKt.set is turned into a private method:

@InlineOnly
private static final void set(@NotNull Map $receiver, Object key, Object value) {
    Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
    $receiver.put(key, value);
}

This explains the IllegalAccessError.

Now, as to why it is private, I'm only speculating, but I feel like it may be due to this:

@usbpc102 pointed out that @InlineOnly is indeed the reason for the method being private.

@InlineOnly specifies that the method should never be called directly:

Specifies that this function should not be called directly without inlining

so I feel like this is a case where the call to set should have been inlined, but it was not.

Had the call been inlined, you would have ended up with compiled code that is practically identical to the working version, since the method only contains a call to put.

I suspect this is due to a compiler bug.

like image 110
Salem Avatar answered Oct 20 '22 21:10

Salem