Integrating Java Dataflow Analysis and the Debugger

Tagir Valeev

We have Java Dataflow Analysis (DFA), which is able to derive facts about your program: possible exceptions, conditions that are always true/always false, and more. It performs an abstract interpretation of the source code, allowing it to gather information about the code execution before the code is executed. However, it knows almost nothing about the inputs to the code. Well technically, if the method parameter is annotated as `@NotNull` the analysis trusts this annotation and assumes that `null` cannot appear here, but this is only a tiny bit of information.

On the other hand, we have the debugger. When it’s stopped on a breakpoint, it knows pretty much everything about the program: the exact value of every variable and field, the content of every array, and so on. So the debugger knows the present, and DFA can predict the future. Why not feed the present data to DFA and see what will happen?

We have created just such an experimental enhancement to the Java debugger. If you debug the Java source and stay on the breakpoint, it runs the dataflow analysis based on the current program state and sees what will happen next. It doesn’t affect the state of your process, and nothing is executed inside the debugging session. You could think of it loosely as a virtual fork of your process.

What are the benefits of this? The most useful thing is the condition evaluation. It looks like this:

See the “= false” and “= true” inlay hints? They are added by DFA. Now you know that the first `if` won’t be visited while the second one will be. It looks like the debugger merely evaluates the expression it sees in the code and displays it. But that’s not exactly the case. See the line `if (exception == null) return false;`? The debugger knows that now the `exception` is null, so `exception == null` is true. However, DFA also knows that the `exception` variable may be reassigned by the time this condition is executed. So it doesn’t quickly jump to conclusions, and it shows instead the results only for the conditions that should have the displayed value when they are actually executed. Often it knows that the value of a variable may change, but sometimes it even knows how exactly it will change:

Here the `size` has not yet been calculated, but DFA sees into the future and knows that as `cst` is neither `Long` nor `Double`, the `size` will be initialized to 1, thus `size == 1` is true on the next line. Another example:

The current value of steps is zero, so if you evaluate `steps % 128 == 0` then it will be `true`. However, DFA displays that it’s `false`. Why? Because `steps++` is about to be executed.

DFA can also forewarn you about some known exceptions before they actually happen (unlike static DFA it doesn’t report “possible NPEs”, only the ones it’s sure about), for example:

As `conf` is null and clearly doesn’t change before the dereference operator, an NPE is inevitable here and DFA tells you about this. It’s much more convenient to be informed in advance than to end up in a finally block, ten frames above, and not understand why it happened. Here’s another sample:

We don’t see it in the editor, but `preConf.insnIndex` is known to be negative at this point. As such, we already know that `dfsTree.loopEnters[insnIndex]` will fail with AIOOBE. Additionally, DFA currently reports `ClassCastException`, `ArrayStoreException`, passing a guaranteed null to a parameter annotated as `@NotNull` and a method contract violation:

In the future we may experiment with displaying more values (currently only `true` and `false` are displayed). Also, there’s been a suggestion to gray out the blocks of code (e.g. `if` branches) that are not going to be executed.

Please note that DFA may sometimes be wrong, as it doesn’t actually execute your program. To make the analysis more useful, it makes some reasonable, but not always true, assumptions:

  • It believes that final fields never change.
  • It trusts method contracts (e.g. if `@NotNull` return value is written, it assumes that the method won’t return null).
  • It believes that the non-volatile field that is read in the current thread is never changed in another thread, at least until some synchronization point occurs (e.g. a `synchronized {}` block starts).
  • It believes that methods annotated as pure don’t change visible fields, arrays, etc.
  • And so on.

So sometimes it’s possible that the wrong hint will be displayed, though it should be a rare case.

This feature is available starting with the v2020.1 EAP. If you decide you don’t like this feature, you can switch it off by unchecking the Predict future condition values… option in Preferences / Settings | Build, Execution, Deployment | Debugger | Data Views | Java:

However, please let us know what you didn’t like about it. Additionally, you can easily switch DFA off temporarily for the current debug session by right-clicking on any displayed hint:

Your feedback is very welcome. Feel free to add your comments here or to this YouTrack ticket.

Happy Developing!

Comments below can no longer be edited.

11 Responses to Integrating Java Dataflow Analysis and the Debugger

  1. Dmitry says:

    January 30, 2020

    It would be nice to make the DFA-predicted exceptions more noticeable. Maybe write them in red or use some red/orange tint for the inlay background.
    I think it would help avoiding “end[ing] up in a finally block, ten frames above”.

    • Dmitry says:

      January 30, 2020

      You should also consider using a different text color for the inlays on the current line.
      The code color changes to white. But the inlays stay grey. And the “Method will fail” text is hardly readable on the last screenshot.

    • Jan Sch. says:

      February 13, 2020

      Yes I think the same, if the DFA is very sure there will be a exception it should be more noticable then the current grey tone.

  2. Dmitry says:

    January 30, 2020

    COSMETIC
    Showing the inlay _after_ the trailing comma (as long as it stays unambiguous) would look nicer.

  3. Fudge says:

    January 30, 2020

    Are such improvements planned for Kotlin?

    • Dmitry Jemerov says:

      February 3, 2020

      We’d like to support this for Kotlin too, but we don’t have an exact timeframe. Please feel free to vote for https://youtrack.jetbrains.com/issue/KT-36318

      • Mladen says:

        February 3, 2020

        What about JavaScript/TypeScript?

  4. Mark Vedder says:

    January 31, 2020

    This is a GREAT enhancement. I think we’ve all been in those looooooong debugging sessions where your eyes are blurry and your brain has turned to mush from exhaustion. So you no longer trust your ability to apply the Boolean truth table from week 2 of Comp Sci 101 to the expression you are looking at. Now rather than highlighting the condition and opening the “evaluate expression” dialog as a sanity check, the answer will already be there for you. To me, this is a feature that will quickly turn into a unsung hero.

  5. Yugandhar says:

    April 12, 2020

    This DFA is not working for this simple condition the inline hint is not working
    String a = “10”;
    if (a instanceof String) {
    System.out.println(“ture->”);
    }

    • Tagir Valeev says:

      April 13, 2020

      DFAAssist doesn’t show hints when the same information could be obtained via static analysis (without running the program at all). In this case, the inspection ‘Constant conditions & exceptions’ should highlight your condition saying that it’s always true. Please check whether you have the inspection turned on.

  6. Carfield YIm says:

    April 14, 2020

    Look nice but sound like the evaluation is disabled when I RDP to my office computer and run Intellij, can I enable that even in RDP?

Subscribe

Subscribe for updates