'IntelliJ IDEA breakpoints do not hit in dynamically loaded anonymous inner class

This is not your usual "my breakpoints don't work" question.

Consider the following code:

Runnable runnable = new Runnable()
{
    @Override public void run()
    {
        Log.debug( "in run()" ); // <-- place one breakpoint here
    }
};

@Test public void test()
{
    Log.debug( "in test()" ); // <-- place another breakpoint here
    runnable.run();
}

If you were to run this test from within IntellijIdea, using IntellijIdea's built-in JUnit support, the following things would happen:

  1. Both logging statements produce output.
  2. Both breakpoints hit.

However:

If you were to run this test from within some other framework, (e.g. Testana) which discovers the test class at runtime, loads it dynamically, and executes each test method in it, then the following happens:

  1. Both logging statements produce output.
  2. The breakpoint in the test() method hits.
  3. The breakpoint in the run() method does not hit.

As a matter of fact, when the breakpoint in the test() method hits, you can see that the breakpoint in the run() method remains a red circle without a checkmark, which means that IntellijIdea does not recognize it as being on executable code.

Just in case it matters, I am currently using macOS, in a few days I am hoping to be able to try under Windows.

There were a couple of similar issues in IntellijIdea reported and fixed a long time ago: https://youtrack.jetbrains.com/issue/IDEA-79268 (10 years ago) https://youtrack.jetbrains.com/issue/IDEA-133881 (7 years ago)

Judging by a comment by CrazyCoder (a well known JetBrainiac on Stackoverflow) from May 29 '13 at 13:11 on this question Line breakpoints don't work in some classes which mentions some "debug scope" I suspect that the problem is something along these lines:

  1. The testing framework is launched with its own classpath which does not include the test class.
  2. The testing framework discovers the module containing the test class, creates a new ClassLoader with the class path of the module, uses that ClassLoader to load the test class, and runs the test methods in it.
  3. The IntellijIdea debugger somehow detects that the test class was dynamically loaded, and includes it in whatever that "debug scope" is, so the breakpoint in the test() method hits.
  4. The IntellijIdea debugger fails to detect that the anonymous inner class is also loaded, so it fails to include it in the "debug scope", so the breakpoint in the run() method does not hit.

And now the question:

Is there any workaround that would make the IntellijIdea debugger hit the breakpoint in the anonymous inner class?

Ideally, the workaround would be a general-purpose solution that can be implemented in the testing framework to take care of any similar situation.

A workaround that would make breakpoints work in anonymous inner classes by extra bureaucracy on the side of the test class would also be (barely) acceptable.

(But if you were going to suggest that I convert my anonymous inner class to a separate top-level class, please don't.)

EDIT

Behavior is same on Windows.

Steps to reproduce:

  • Check out this project: https://github.com/mikenakis/Public
  • Go to class T01_CompilingIntertwine
  • Place a breakpoint on line 57 (first line of function run())
  • Hit your Debug key to bring up the run configurations dialog
  • There will be a run configuration called Testana - All; launch it.
  • The breakpoint will not hit.
  • Make a minor modification to the file and save it (because testana does not run tests that have not changed.)
  • Place a breakpoint on line 53 (new Runnable())
  • Relaunch (Debug) Testana - All.
  • The breakpoint on line 53 will hit. If you resume, then the breakpoint on line 57 will also hit.


Solution 1:[1]

So, Konstantin Annikov from JetBrains found this worthy of creating an new issue for it on the IntelliJ IDEA issue tracker, see https://youtrack.jetbrains.com/issue/IDEA-287858

Then, Egor Ushakov from JetBrains looked into it, and found that this is happening because the testing framework is loading the classes in a non-standard order, while IntelliJ IDEA contains some logic that relies on the assumption that the classes will be loaded in the standard order.

  • The standard order of loading classes (when classes are being loaded by their classloader as they are being executed) is to have the outermost class loaded first, and the inner classes loaded afterwards.

  • Testana was loading the classes in the order in which the class files are yielded by java.nio.file.Files.walkFileTree(), which is apparently alphabetic, and it just so happens that $ sorts before ., so Testana was loading the inner classes first, and the outermost class last.

I fixed the problem in Testana, and Egor expressed the intention to try and implement a workaround for this case in IntelliJ IDEA. (This is such an edge case that I am not sure it is worth fixing, but anyway, it is his call.)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mike Nakis