Skip to content

DCEVM reloading fails with NoSuchMethodError when changing lambda captures of lambda stored in memory and rerunning it #535

@natanfudge

Description

@natanfudge

Steps to reproduce

  1. Follow the instructions from Youtrack. In short:
  • Setup an "Enhanced Class Redefinition" launch configuration, i.e. set VM options to
-XX:+AllowEnhancedClassRedefinition -XX:HotswapAgent=fatjar

Image

  • Add the hotswap-agent-2.0.2.jar file from here into the lib/hotswap directory in the JBR, and rename it into hotswap-agent.jar.
  • You can use the -core hotswap agent, or no hotswap agent at all, and it will crash all the same.
  1. Run this code in debug mode in Intellij IDEA:
val lambdas = mutableListOf<Runnable>()
fun main() {
    val x = 10
    subcallAdd(x)
    while (true) {
        Thread.sleep(100)
        subcallRun()
    }
}

private fun subcallRun() {
    lambdas.forEach { it.run() }
}

private fun subcallAdd(x: Int) {
    val lambda = Runnable {
        println(x)
        println("Halo1")
    }
    lambdas.add(lambda)
}
  1. Comment out the line
println(x);

By doing this you have made the lambda no longer capture x, and the function signature of the lambda changes from (int) to ()
4. Reload changed classes, using a hotkey, or the new integrated Idea popup button.
5. Observe crash:

Exception in thread "main" java.lang.NoSuchMethodError: 'void MainKt.subcallAdd$lambda$1(int)'
	at MainKt.subcallRun(Main.kt:20)
	at MainKt.main(Main.kt:15)
	at MainKt.main(Main.kt)
  • You can write it in Java, or add instead of removing capture, and it will crash all the same.

Expected Behavior

The code reloads successfully; The code continues executing without running println(x); i.e. without printing x.

Actual Behavior

Program crashes with NoSuchMethodError on reload with a lambda capture change.

Environment

Using Windows 11, latest JBR Release 21.0.6b631.42. Using Gradle if the matters.

Notes

private fun subcallAdd(x: Int) {
//    val lambda = Runnable {
//        println(x)
//        println("Halo1")
//    }

    val lambda = object : Runnable {
        override fun run() {
            println(x)
            println("Halo1")
        }
    }

    lambdas.add(lambda)
}
  • Same goes with Java - lambda doesn't work; anonymous class does work.

Investigation

While the anonymous class creates a new class and adds an instance of it to the list:

public final class io/github/natanfudge/fn/MainKt$subcallAdd$lambda$1 implements java/lang/Runnable
      ...
      public run()V
      ...

... In `subcallAdd`:
NEW io/github/natanfudge/fn/MainKt$subcallAdd$lambda$1

The lambda generates a method and adds it to the list using invokedynamic:

private final static subcallAdd$lambda$1(I)V
...
... in `subcallAdd`:
INVOKEDYNAMIC run(I)Ljava/lang/Runnable; [
    // handle kind 0x6 : INVOKESTATIC
    java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    // arguments:
    ()V,
    // handle kind 0x6 : INVOKESTATIC
    io/github/natanfudge/fn/MainKt.subcallAdd$lambda$1(I)V,
    ()V
]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions