Community
Participate
Working Groups
I20050315-11 I've set a breakpoint in org.eclipse.jdt.ui.actions.OpenAction, line 125. When I press F3 on a Java element the debugger stops at that location. OK. Clicking "Resume" stops again at the same location. You can add additional breakpoints before and after line 125 to see that the method itself isn't invoked twice. When I change the code to be in one line i.e. append line 126 to 125 it only stops once.
The JDI client claims that there are two executable locations for this line number (code index 15 and 32). The JDI spec says a line may have more than one executable location "if the compiler and/or VM has mapped that line to two or more disjoint code index ranges". This information is derived from the debug attributes of the class file. We install breakpoints for all executable locations associated with a line. I'm not sure why this line has two executable locations.
javap reveals the same info, moving to JCORE for comment on debug attributes. public void run(org.eclipse.jface.text.ITextSelection); LineNumberTable: line 122: 0 line 123: 14 line 125: 15 line 126: 27 line 125: 32 line 127: 36 line 128: 40 line 129: 83 line 130: 87 line 131: 100 line 132: 110 line 134: 111 line 135: 119 line 136: 127 line 137: 145 line 138: 147 line 139: 162 line 140: 163 line 142: 168 LocalVariableTable: Start Length Slot Name Signature 0 169 0 this Lorg/eclipse/jdt/ui/actions/OpenAction; 0 169 1 selection Lorg/eclipse/jface/text/ITextSelection; 36 126 2 element Lorg/eclipse/jdt/core/IJavaElement; 83 28 3 statusLine Lorg/eclipse/ui/texteditor/IEditorStatusLine; 119 43 3 input Lorg/eclipse/jdt/core/IJavaElement; 127 35 4 type I 163 5 2 e Lorg/eclipse/jdt/core/JavaModelException;
When you have code like this: IJavaElement element= SelectionConverter.codeResolve(fEditor, ... ActionMessages.getString("OpenAction.select_element")); //$NON-NLS-1$ when fEditor is generated we add a line entry for it. Then when ActionMessages.getString("OpenAction.select_element") is generated we add a new line entry on the next line. Finally when the assignment is performed, we add a new line entry that has the same line number than the first entry. This works as expected. No action planned.
I just ran into this again (no assignment involved). Look at this example: void doit() { multiline( 1, 2, 3, 4, 5); } with a breakpoint on the line with "multiline": no matter how many arguments I have it stops twice (not once and not as many times as there are lines or arguments (if everything is on one line it works as expected). Now, if JDT Core cannot fix it then Debug should do something to fix this and prevent stopping twice for the same line breakpoint.
Olivier - shouldn't we associate the invoke to the message source end line position instead ? We should check what other compilers are doing there. This could also be surprising to users who have been used to this behavior since day 1 (like me...).
for: public class X { public static void main(String[] args) { new X().foo(1, 2, 3); } void foo(int i, int j, int k) { foo( i, j, k); } } we do produce: // Method descriptor #20 (III)V // Stack: 4, Locals: 4 void foo(int i, int j, int k); 0 aload_0 [this] 1 iload_1 [i] 2 iload_2 [j] 3 iload_3 [k] 4 invokevirtual X.foo(int, int, int) : void [17] 7 return Line numbers: [pc: 0, line: 6] [pc: 1, line: 7] [pc: 2, line: 8] [pc: 3, line: 9] [pc: 4, line: 6] [pc: 7, line: 10] javac7 produces: // Method descriptor #13 (III)V // Stack: 4, Locals: 4 void foo(int arg0, int arg1, int arg2); 0 aload_0 [this] 1 iload_1 [arg0] 2 iload_2 [arg1] 3 iload_3 [arg2] 4 invokevirtual X.foo(int, int, int) : void [4] 7 return Line numbers: [pc: 0, line: 6] [pc: 7, line: 10] Javac seems minimalistic in not offering to step on local variables... seems like they no longer generate attributes for trivial lines ?
Actually, javac seems busted. For this, they miss the nested msg send: public class X { public static void main(String[] args) { new X().foo(1, 2, false); } void foo(int i, int j, boolean b) { foo( i, j, bar( i < j)); } boolean bar(boolean b) { return b; } } We produce: // Method descriptor #20 (IIZ)V // Stack: 6, Locals: 4 void foo(int i, int j, boolean b); 0 aload_0 [this] 1 iload_1 [i] 2 iload_2 [j] 3 aload_0 [this] 4 iload_1 [i] 5 iload_2 [j] 6 if_icmpge 13 9 iconst_1 10 goto 14 13 iconst_0 14 invokevirtual X.bar(boolean) : boolean [23] 17 invokevirtual X.foo(int, int, boolean) : void [17] 20 return Line numbers: [pc: 0, line: 6] [pc: 1, line: 7] [pc: 2, line: 8] [pc: 3, line: 9] [pc: 4, line: 10] [pc: 5, line: 12] [pc: 9, line: 10] [pc: 14, line: 9] [pc: 17, line: 6] [pc: 20, line: 13] Javac7 produces: // Method descriptor #14 (IIZ)V // Stack: 6, Locals: 4 void foo(int arg0, int arg1, boolean arg2); 0 aload_0 [this] 1 iload_1 [arg0] 2 iload_2 [arg1] 3 aload_0 [this] 4 iload_1 [arg0] 5 iload_2 [arg1] 6 if_icmpge 13 9 iconst_1 10 goto 14 13 iconst_0 14 invokevirtual X.bar(boolean) : boolean [5] 17 invokevirtual X.foo(int, int, boolean) : void [4] 20 return Line numbers: [pc: 0, line: 6] [pc: 20, line: 13] Debugging the Javac output is not very friendly...
Note that javac5 (1.5.0_17) and javac6 (1.6.0_12-ea) have the same issue.
Other example: public class X { public static void main(String[] args) { new X().foo(1, 2, false); } void foo(int i, int j, boolean b) { if (b) { foo( i, j, bar( i < j)); } else { boolean b1 = bar( b); } } boolean bar(boolean b) { return b; } } I suspect javac only records one entry per statement; that's it. i.e. ecj's: // Method descriptor #20 (IIZ)V // Stack: 6, Locals: 4 void foo(int i, int j, boolean b); 0 iload_3 [b] 1 ifeq 27 4 aload_0 [this] 5 iload_1 [i] 6 iload_2 [j] 7 aload_0 [this] 8 iload_1 [i] 9 iload_2 [j] 10 if_icmpge 17 13 iconst_1 14 goto 18 17 iconst_0 18 invokevirtual X.bar(boolean) : boolean [23] 21 invokevirtual X.foo(int, int, boolean) : void [17] 24 goto 33 27 aload_0 [this] 28 iload_3 [b] 29 invokevirtual X.bar(boolean) : boolean [23] 32 pop 33 return Line numbers: [pc: 0, line: 7] [pc: 4, line: 8] [pc: 5, line: 9] [pc: 6, line: 10] [pc: 7, line: 11] [pc: 8, line: 12] [pc: 9, line: 14] [pc: 13, line: 12] [pc: 18, line: 11] [pc: 21, line: 8] [pc: 27, line: 17] [pc: 28, line: 18] [pc: 29, line: 17] [pc: 33, line: 20] javac's: // Method descriptor #14 (IIZ)V // Stack: 6, Locals: 5 void foo(int arg0, int arg1, boolean arg2); 0 iload_3 [arg2] 1 ifeq 27 4 aload_0 [this] 5 iload_1 [arg0] 6 iload_2 [arg1] 7 aload_0 [this] 8 iload_1 [arg0] 9 iload_2 [arg1] 10 if_icmpge 17 13 iconst_1 14 goto 18 17 iconst_0 18 invokevirtual X.bar(boolean) : boolean [5] 21 invokevirtual X.foo(int, int, boolean) : void [4] 24 goto 34 27 aload_0 [this] 28 iload_3 [arg2] 29 invokevirtual X.bar(boolean) : boolean [5] 32 istore 4 34 return Line numbers: [pc: 0, line: 6] [pc: 4, line: 8] [pc: 27, line: 16] [pc: 34, line: 20] So with a javac's classfile, once breakpoint (at method entrance) is hit: you'd be on line: if (if b is true) step goes to line: foo( (if b is false) step goes to line: boolean b1 = next steps is correctly going to nested msg send (courtesy of debugger) on msg return, it goes directly to method end: } (Note that for method return/end we do agree with javac) So in the end, javac is only offering statements as steps in a method, relying on the fact that statements are one liners for the most part. If not, then they treat multiple lines as noise and only retain the first part. Dani / Darin / Olivier - what do you guys think ? This feels a nice simplification of the problem.
I think there are two different issues: 1) what info the compiler produces 2) what the debugger does with it I do not care too much about 1) but for 2) I'd expect to stop once for a line breakpoint no matter how the class file info looks like (the debugger should ignore subsequent entry for the same line number). Note that I'm not talking about step-into behavior - for that having more data might be better.
So it looks like there are two locations for the line - one *before* argument evaluation and *after* argument evaluation. I think there should only be a stop before argument evaluation. When the user puts a breakpoint on a line, they intend to stop before it executes. I think we want to continue to be able to stop on local vars (to be consistent with past behavior and minimize change). If stopping on locals was not allowed, we'd also need to change our breakpoint location verification. I don't think the debugger should be guessing which location to use when there is more than one provided by the attributes - how would we know which is the right location to use? (unless we can just assume the smallest pc wins?) I would suggest to not have the post argument evaluation location. Is there a reason this is needed?
Re: comment 10 Dani, the debugger behavior for (2) is a direct consequence of the compiler generated attribute (1). Re: comment 11 The sequence of bytecode is like this: - code for receiver (associated to line of receiver, if implicit line of selector) - code for arg1 (associated to line of arg1) - ... - code for argN (associated to line of arg1) - invoke method instruction(associated to line of selector) I could change the invoke instruction to be associated to the line where the closing brace is. This would avoid the yoyo effect, so as soon as you'd dive into last argument, it would jump into the invoked method. Thoughts ? As to keeping local var references in debug attributes, how do you deal with placing breakpoint in classfiles which got produced by another compiler ? Think of rt.jar, or even the case where someone disabled the Java builder on his source project, and instead uses an Ant script invoking another compiler.
When I set a line breakpoint I'd expect to stop (only) at the very first instruction of that line i.e. I would want a resume to really resume and I would want to be able to step into each argument if desired.
The breakpoint would still stops at the first instruction on a given line, this wouldn't be a worry. Given the debugger is stopping on subsequent (nested) stackframe entrance, I believe we do not need any attribute for the invoke instruction (simply make it part of the last argument generated). Same would be true for constructor calls.
Created attachment 120174 [details] Proposed patch
Created attachment 120185 [details] Better patch
With patch, the following pattern has changed as well: x .next .next .toString(); Basically, the line #toString() is never highlighted either, it is implicitly associated to the end of the receiver evaluation. So first step goes on 'x', then 'next', then 'next' and no trace for the invocation. Not sure this is desirable.
Another interesting scenario. Today we are a bit broken if you place a breakpoint on line (*). When you hit the breakpoint, the value of 'i' is 1 already. If the method was non static, then it would stop with 'i' at 0. More confusing, if you compiled with javac it would be at 0 in all cases (but you cannot step in arguments)... public class X { static void bar(int i, int j) { } public static void main(String[] args) { int i = 0; (*) bar( i = 1, 2); } }
With patch from comment 16, the behavior described in comment 17 is even worse. It moves the breakpoint to the next line ("i = 1")...
This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet. If you have further information on the current state of the bug, please add it. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant. -- The automated Eclipse Genie.
During stepping stopping twice at the same line can be very valuable particular with code of styles like this: m(m1() .m2() .m3()); because this allows, e.g., to step over m1() and over m2() and into or over m3() and finally into m(). This implies I would not like changing the line number info for such cases. (In reply to Dani Megert from comment #10) > I think there are two different issues: > 1) what info the compiler produces > 2) what the debugger does with it > > I do not care too much about 1) but for 2) I'd expect to stop once for a > line breakpoint no matter how the class file info looks like (the debugger > should ignore subsequent entry for the same line number). If line number info is detailed, what options do we have to avoid stopping twice? I have no idea how on resume the debugger could possibly distinguish the second hit of the same execution from another hit triggered re-entering that code section. Hence I believe the feature wouldn't carry its own weight: significant code complexity plus likely a noticeable performance penalty (if, e.g., resume is simulated by several step overs plus finally resume). > Note that I'm not > talking about step-into behavior - for that having more data might be better. So here we agree. Dani, do you still want to keep this bug open? BTW: In terms of unnecessary breakpoint hits, I'd see much more value in fixing bug 284158 (if that one is feasible).
(In reply to Stephan Herrmann from comment #21) > During stepping stopping twice at the same line can be very valuable > particular with code of styles like this: > > m(m1() > .m2() > .m3()); > > because this allows, e.g., to step over m1() and over m2() and into or over > m3() and finally into m(). Yes, but this requires that you do not use the default formatter. As soon as you format your code you end up with m(m1().m2().m3()); and it will only stop once as expected.
Back to debug, just in case someone finds a super cool solution to a hard technical problem.
(In reply to Stephan Herrmann from comment #23) > Back to debug, just in case someone finds a super cool solution to a hard > technical problem. Just to be on the same page: Do you agree that the different behavior depending on the formatting is weird?