4. 2. 2014

Small Eclipse breakpoint pitfall


I was caught in interesting trap in Eclipse today. It was my mistake in the beginning, however I suppose Eclipse could have better handled this situation.

Look at methods m1() and m2() in following minimalistic example. What's the difference between them?

public class StrangeBreakpoint {

  public static void main(String[] args) {
    m1();
    m2();
  }

  private static void m1() {   // line 8
    int x = 0;
    while (true) {             // line 10
      System.out.println(x++);
      if (x == 2) break;
      int dummy = 0;           // line 13
    }
  }
 
  private static void m2() {   // line 17
    int x = 0;
    while (true) {             // line 19
      System.out.println(x++);
      if (x == 2) break;
    }
  }

}

Certainly none in output. But try to enable breakpoints at lines 10 and 19. Method m1() runs loop once before stopping at breakpoint, method m2() never stops at breakpoint at all.

I guess the reason of m1()'s behavior is the additional code after break which results in additional bytecode delimited with jumps. Eclipse debugger treats breakpoint as stopping point on first instruction after break (see instruction offset 20 paired with line 13). In m2() case, the debugger has no instruction to stop at, so it silently ignores breakpoint.

>javap -private -c -l StrangeBreakpoint
Compiled from "StrangeBreakpoint.java"
public class StrangeBreakpoint extends java.lang.Object{

...

private static void m1();
  Code:
   0:   iconst_0
   1:   istore_0
   2:   getstatic       #24; //Field java/lang/System.out:Ljava/io/PrintStream;
   5:   iload_0
   6:   iinc    0, 1
   9:   invokevirtual   #30; //Method java/io/PrintStream.println:(I)V
   12:  iload_0
   13:  iconst_2
   14:  if_icmpne       20
   17:  goto    25
   20:  iconst_0
   21:  istore_1
   22:  goto    2
   25:  return

  LineNumberTable:
   line 9: 0
   line 11: 2
   line 12: 12
   line 13: 20
   line 10: 22
   line 15: 25

...

private static void m2();
  Code:
   0:   iconst_0
   1:   istore_0
   2:   getstatic       #24; //Field java/lang/System.out:Ljava/io/PrintStream;
   5:   iload_0
   6:   iinc    0, 1
   9:   invokevirtual   #30; //Method java/io/PrintStream.println:(I)V
   12:  iload_0
   13:  iconst_2
   14:  if_icmpne       2
   17:  return

  LineNumberTable:
   line 18: 0
   line 20: 2
   line 21: 12
   line 23: 17

...
}

I ran into this issue debugging real application, not noticing first loop run and wondering why the value of x seems different than the one just assigned.

This behaviour was reproduced in Eclipse Juno, JDT 3.7.101.v20120725. Intellij Idea disallowed breakpoint in both cases, which I consider more fair and less surprising.

Moral: If you must use breakpoints at all, be aware of bytecode in the background. Avoid usage in places where semantic gap between high-level language and bytecode is not apparent.