You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
next stops in the wrong place when blocks are called #763
defwith_blockyieldendwith_blockdo1endputs("`next` should stop here")
Add a breakpoint in line 5
Try to move to next
See the debugger stop at line 6 (and then 7, and 3)
For more complex cases:
Create a file like:
classBardefine_method("dynamic")dotrueenddefregular;false;endenddeffoo(a,b,c);endbar=Bar.newfoo(bar.regular,bar.dynamic,# `next` stops herebar.regular)puts("`next` should stop here")
Add a breakpoint in line 12 (where the call to foo is)
Try to move to next
See the debugger stop at line 14
Expected behavior
The debugger should stop at line 8 in the simple example, and 17 in the complex one
Additional context
I included non-dynamically-defined methods, to show that next only behaves incorrectly for the dynamically-defined ones (as it doesn't stop at line 13 nor 15)
changed the title [-]`next` stops in the wrong place when dynamically-defined methods are called[/-][+]`next` stops in the wrong place when blocks are called[/+]on Sep 25, 2022
cc @WillHalto. Just a heads up that #743 doesn't seem to make this worse, but given that you are already there and have some context, you might find it interesting.
After some investigation, I think the simple and complicated examples have different causes.
I can't really tell the cause of the simple case yet. But for the complicated example, I think it's caused by a bug in older Ruby versions.
Example
TracePoint.new(:b_return,:return)do |tp|
puts[tp.event,caller_locations(1,1).first].to_send.enableclassBardefine_method("dynamic")dotrueend# line 8endBar.new.dynamic# line 11
If you execute this script with Ruby < 3.1, you'll get
And you can see that b_return and return events' line numbers don't match. Ideally, they should both be 8.
However, if you run it with Ruby 3.1, they do match
❯ ruby test.rb
[:b_return, "test.rb:8:in `block in <class:Bar>'"]
[:return, "test.rb:8:in `block in <class:Bar>'"]
If you run the complicated case with Ruby 3.1, it actually works as expected:
❯ exe/rdbg -e "b 12 ;; c ;; n ;; n ;; n" target.rb
[1, 10] in target.rb
=> 1| class Bar
2| define_method("dynamic") do
3| true
4| end
5|
6| def regular; false; end
7| end
8|
9| def foo(a, b, c); end
10|
=>#0 <main> at target.rb:1
(rdbg:commands) b 12
#0 BP - Line /Users/st0012/projects/debug/target.rb:12 (line)
(rdbg:commands) c
[7, 16] in target.rb
7| end
8|
9| def foo(a, b, c); end
10|
11| bar = Bar.new
=> 12| foo(
13| bar.regular,
14| bar.dynamic, # `next` stops here
15| bar.regular
16| )
=>#0 <main> at target.rb:12
Stop by #0 BP - Line /Users/st0012/projects/debug/target.rb:12 (line)
(rdbg:commands) n
[12, 17] in target.rb
12| foo(
13| bar.regular,
14| bar.dynamic, # `next` stops here
15| bar.regular
16| )
=> 17| puts("`next` should stop here")
=>#0 <main> at target.rb:17
(rdbg:commands) n
`next` should stop here
So I think it's more like a bug in older Ruby versions, but we may workaround it in debug.
I don't know what your policy is in supporting Ruby < 3.1.0... but, as it is, with this issue and #760, the debugger has a really poor experience.
Given that we still have 6 months until the end of life of Ruby 2.6 (and no date for 2.7), I'd argue for at least keeping this open, and try to fix it.
Last time I tested, the simple case's issue also happened with Ruby 3.1+ so it needs more investigation.
❯ruby -vruby3.1.2p20(2022-04-12revision4491bb740a)[x86_64-darwin21]❯beexe/rdbgtarget.rb[1,8]intarget.rb=> 1| defwith_block2| yield3| end4|
5| with_blockdo6| 17| end8| puts("`next` should stop here")=>#0 <main> at target.rb:1(rdbg)b5# break command#0 BP - Line /Users/st0012/projects/debug/target.rb:5 (line)(rdbg)c# continue command[1,8]intarget.rb1| defwith_block2| yield3| end4|
=>5| with_blockdo6| 17| end8| puts("`next` should stop here")=>#0 <main> at target.rb:5Stopby#0 BP - Line /Users/st0012/projects/debug/target.rb:5 (line)(rdbg)n# next command[1,8]intarget.rb1| defwith_block2| yield3| end4|
5| with_blockdo=>6| 17| end8| puts("`next` should stop here")=>#0 block in <main> at target.rb:6#1 Object#with_block at target.rb:2# and 1 frames (use `bt' command for all frames)(rdbg)n# next command[2,8]intarget.rb2| yield3| end4|
5| with_blockdo6| 1=>7| end8| puts("`next` should stop here")=>#0 block in <main> at target.rb:7 #=> 1#1 Object#with_block at target.rb:2# and 1 frames (use `bt' command for all frames)
<=====Ideallythisstepshouldn'thappen =====>
(rdbg)n# next command [1,8]intarget.rb1| defwith_block2| yield=>3| end4|
5| with_blockdo6| 17| end8| puts("`next` should stop here")=>#0 Object#with_block at target.rb:3 #=> 1#1 <main> at target.rb:5(rdbg)n# next command[3,8]intarget.rb3| end4|
5| with_blockdo6| 17| end=>8| puts("`next` should stop here")=>#0 <main> at target.rb:8(rdbg)n# next command`next`shouldstophere
So based on that I think we should keep this issue opened until we're clear on both cases' causes.
It's also worth mentioning that, for anyone using Sorbet, trying to step with next always requires two steps. This is because Sorbet signatures are essentially wrapping methods in a block. Take the following example:
sig{returns(T::Boolean)}defdoes_it_work?falseendvalue=does_it_work?puts("Does it work? #{value}")
There are actually 2 problems (as identified by @st0012 above):
Extra steps when a block is involved (as in Sorbet). This might not seem like much, but if you are debugging a codebase where everything has a signature, it's essentially 2 nexts for each line you want to step over
next going to the wrong place
They are definitely a very bad experience when debugging anything but a trivial codebase
defiteryieldend# (iter/end)iterdo1end# (*) Now stopping hereputs("`next` should stop here")# (putline)
For a person who think the block is function, the next line of (*) should be (iter/end). They have interest how iter is implemented.
For a person who think the block is a loop (or code block) of caller context (top on this case), the next line should be (putline). They don't have interest how iter is implemented.
Options:
next should ignore the iter internal and jump to (putline) (maybe the author of this issue wants)
next should not ignore iter internal and jump to (iter/end) (current behavior)
Now, next command consider the block is literally next, so option 1 seems preferable.
In the case above, 2 (at the line with end) is never what I'd expect to happen.
What I would expect is 1 (thus this ticket), but I'd be OK (as in: "I don't like this much, but at least doesn't surprise me") with either stopping inside the iter function (the line with yield) or inside the block (the line with 1).
(1) The first version of debug.gem skips the block.
(2) However, Ruby's block is considered as program lines and it is natural to stop inside the block literally. This is why I can't accept your request ("stop at line 8 from line 5 with next").
I can confirm that, at least in ruby 2.7, this is still not fixed. I've simplified the test enough to this:
classNextDynamicMethodTest < ProtocolTestCasePROGRAM=<<~RUBY 1| class Bar 2| define_method("dynamic") do 3| "dynamic" 4| end 5| 6| def regular 7| "regular" 8| end 9| end 10| 11| def foo(a, b, c); end 12| 13| bar = Bar.new 14| foo( 15| bar.regular, 16| bar.dynamic, 17| bar.regular 18| ) 19| bar RUBYdeftest_next_goes_to_the_next_statementrun_protocol_scenarioPROGRAMdoreq_add_breakpoint14req_continueassert_line_num14req_nextassert_line_num19# When `TracePoint` returns the wrong number for :return after :b_return, this is 16req_terminate_debuggeeendendend
Activity
marianosimone commentedon Sep 25, 2022
Here's a protocol test that should pass when the issue is fixed:
[-]`next` stops in the wrong place when dynamically-defined methods are called[/-][+]`next` stops in the wrong place when blocks are called[/+]marianosimone commentedon Sep 25, 2022
cc @WillHalto. Just a heads up that #743 doesn't seem to make this worse, but given that you are already there and have some context, you might find it interesting.
st0012 commentedon Sep 26, 2022
A small tip: you can write the reproduction steps down like:
In this case, we only need to run
And the reproduction steps can be executed automatically.
If you don't want to modify the script directly, you can achieve it with
rdbg -etoo:$ rdbg -e "b 5 ;; c ;; n ;; n ;; n" target.rbst0012 commentedon Sep 26, 2022
After some investigation, I think the simple and complicated examples have different causes.
I can't really tell the cause of the simple case yet. But for the complicated example, I think it's caused by a bug in older Ruby versions.
Example
If you execute this script with Ruby < 3.1, you'll get
And you can see that
b_returnandreturnevents' line numbers don't match. Ideally, they should both be8.However, if you run it with Ruby 3.1, they do match
If you run the complicated case with Ruby 3.1, it actually works as expected:
So I think it's more like a bug in older Ruby versions, but we may workaround it in
debug.ko1 commentedon Oct 4, 2022
It solved on Ruby 3.1.0 and later.
I agree it is an issue, but not so big problem I think so I left it.
Please reopen it if it is so important issue.
marianosimone commentedon Oct 4, 2022
I don't know what your policy is in supporting Ruby < 3.1.0... but, as it is, with this issue and #760, the debugger has a really poor experience.
Given that we still have 6 months until the end of life of Ruby 2.6 (and no date for 2.7), I'd argue for at least keeping this open, and try to fix it.
st0012 commentedon Oct 4, 2022
Last time I tested, the simple case's issue also happened with
Ruby 3.1+so it needs more investigation.So based on that I think we should keep this issue opened until we're clear on both cases' causes.
marianosimone commentedon Oct 4, 2022
It's also worth mentioning that, for anyone using Sorbet, trying to step with
nextalways requires two steps. This is because Sorbet signatures are essentially wrapping methods in a block. Take the following example:Trying to step over 6 requires two
nextko1 commentedon Oct 5, 2022
I think additional
nextis not big problem but you think it is a big problem?marianosimone commentedon Oct 5, 2022
There are actually 2 problems (as identified by @st0012 above):
nextgoing to the wrong placeThey are definitely a very bad experience when debugging anything but a trivial codebase
ko1 commentedon Oct 28, 2022
I understand the issue.
nextline of(*)should be(iter/end). They have interest howiteris implemented.nextline should be(putline). They don't have interest howiteris implemented.Options:
nextshould ignore theiterinternal and jump to(putline)(maybe the author of this issue wants)nextshould not ignoreiterinternal and jump to(iter/end)(current behavior)Now,
nextcommand consider the block is literallynext, so option 1 seems preferable.marianosimone commentedon Oct 28, 2022
In the case above, 2 (at the line with
end) is never what I'd expect to happen.What I would expect is 1 (thus this ticket), but I'd be OK (as in: "I don't like this much, but at least doesn't surprise me") with either stopping inside the
iterfunction (the line withyield) or inside the block (the line with1).ko1 commentedon Oct 31, 2022
When stopping at line
(*), it never stop atyieldor1because they are passed lines.ko1 commentedon Oct 31, 2022
ah, I see on the original issue:
You want to skip block call at all.
next").nextat the end of block is inconsistent with (2) because (2) skipsitermethod butnextfrom the end of the block jump to insideitermethod as described innextstops in the wrong place when blocks are called #763 (comment)(3) seems valuable to reconsidered. But now I don't have correct way to fix...
ko1 commentedon Dec 8, 2022
v1.7.0 may fix this issue.
marianosimone commentedon Dec 8, 2022
I can confirm that, at least in ruby 2.7, this is still not fixed. I've simplified the test enough to this:
ko1 commentedon Dec 22, 2022
Thank you I verified
marianosimone commentedon Oct 10, 2023
Coming back to this after I'm using Ruby 3.1, and I can verify that the issue is solved