Bug 88626 - Debugger stops twice at same location
Summary: Debugger stops twice at same location
Status: NEW
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Debug (show other bugs)
Version: 3.1   Edit
Hardware: PC Windows XP
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: JDT-Debug-Inbox CLA
QA Contact:
URL:
Whiteboard: stalebug
Keywords: helpwanted
Depends on:
Blocks:
 
Reported: 2005-03-21 06:25 EST by Dani Megert CLA
Modified: 2023-02-05 16:27 EST (History)
2 users (show)

See Also:


Attachments
Proposed patch (7.09 KB, patch)
2008-12-11 06:00 EST, Philipe Mulet CLA
no flags Details | Diff
Better patch (7.09 KB, patch)
2008-12-11 07:15 EST, Philipe Mulet CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Dani Megert CLA 2005-03-21 06:25:50 EST
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.
Comment 1 Darin Wright CLA 2005-03-21 10:41:13 EST
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.
Comment 2 Darin Wright CLA 2005-03-21 10:54:40 EST
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;
Comment 3 Olivier Thomann CLA 2007-06-21 11:45:33 EDT
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.
Comment 4 Dani Megert CLA 2008-12-10 04:36:23 EST
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.
Comment 5 Philipe Mulet CLA 2008-12-10 06:32:36 EST
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...).
Comment 6 Philipe Mulet CLA 2008-12-10 07:53:06 EST
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 ?

Comment 7 Philipe Mulet CLA 2008-12-10 08:02:24 EST
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...
Comment 8 Philipe Mulet CLA 2008-12-10 08:04:00 EST
Note that javac5 (1.5.0_17) and  javac6 (1.6.0_12-ea) have the same issue.
Comment 9 Philipe Mulet CLA 2008-12-10 08:20:46 EST
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. 



Comment 10 Dani Megert CLA 2008-12-10 08:31:39 EST
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.
Comment 11 Darin Wright CLA 2008-12-10 12:54:22 EST
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?
Comment 12 Philipe Mulet CLA 2008-12-11 03:37:05 EST
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.
Comment 13 Dani Megert CLA 2008-12-11 03:45:52 EST
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.
Comment 14 Philipe Mulet CLA 2008-12-11 04:41:35 EST
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.
Comment 15 Philipe Mulet CLA 2008-12-11 06:00:09 EST
Created attachment 120174 [details]
Proposed patch
Comment 16 Philipe Mulet CLA 2008-12-11 07:15:50 EST
Created attachment 120185 [details]
Better patch
Comment 17 Philipe Mulet CLA 2008-12-11 09:00:46 EST
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.
Comment 18 Philipe Mulet CLA 2008-12-11 09:09:00 EST
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);

	}
}
Comment 19 Philipe Mulet CLA 2008-12-11 09:42:48 EST
With patch from comment 16, the behavior described in comment 17 is even worse. It moves the breakpoint to the next line ("i = 1")...
Comment 20 Eclipse Genie CLA 2019-02-19 06:46:20 EST
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.
Comment 21 Stephan Herrmann CLA 2019-02-19 08:41:10 EST
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).
Comment 22 Dani Megert CLA 2019-02-19 10:43:13 EST
(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.
Comment 23 Stephan Herrmann CLA 2019-02-19 11:29:29 EST
Back to debug, just in case someone finds a super cool solution to a hard technical problem.
Comment 24 Dani Megert CLA 2019-02-19 11:38:05 EST
(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?
Comment 25 Eclipse Genie CLA 2021-02-09 12:28:42 EST
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.
Comment 26 Eclipse Genie CLA 2023-02-05 16:27:25 EST
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.