Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 52 additions & 30 deletions lib/debug/breakpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,17 @@ def enable
end

class LineBreakpoint < Breakpoint
attr_reader :path, :line, :iseq
attr_reader :path, :line, :iseq, :cond, :oneshot, :hook_call, :command

def initialize path, line, cond: nil, oneshot: false, hook_call: true, command: nil
def self.copy bp, root_iseq
nbp = LineBreakpoint.new bp.path, bp.line,
cond: bp.cond, oneshot: bp.oneshot, hook_call: bp.hook_call,
command: bp.command, skip_activate: true
nbp.try_activate root_iseq
nbp
end

def initialize path, line, cond: nil, oneshot: false, hook_call: true, command: nil, skip_activate: false
@line = line
@oneshot = oneshot
@hook_call = hook_call
Expand All @@ -146,7 +154,7 @@ def initialize path, line, cond: nil, oneshot: false, hook_call: true, command:

super(cond, command, path)

try_activate
try_activate unless skip_activate
@pending = !@iseq
end

Expand Down Expand Up @@ -216,42 +224,56 @@ def duplicable?

NearestISeq = Struct.new(:iseq, :line, :events)

def try_activate
nearest = nil # NearestISeq

ObjectSpace.each_iseq{|iseq|
if DEBUGGER__.compare_path((iseq.absolute_path || iseq.path), self.path) &&
iseq.first_lineno <= self.line &&
iseq.type != :ensure # ensure iseq is copied (duplicated)
def iterate_iseq root_iseq
if root_iseq
is = [root_iseq]
while iseq = is.pop
yield iseq
iseq.each_child do |child_iseq|
is << child_iseq
end
end
else
ObjectSpace.each_iseq do |iseq|
if DEBUGGER__.compare_path((iseq.absolute_path || iseq.path), self.path) &&
iseq.first_lineno <= self.line &&
iseq.type != :ensure # ensure iseq is copied (duplicated)
yield iseq
end
end
end
end

iseq.traceable_lines_norec(line_events = {})
lines = line_events.keys.sort
def try_activate root_iseq = nil
nearest = nil # NearestISeq
iterate_iseq root_iseq do |iseq|
iseq.traceable_lines_norec(line_events = {})
lines = line_events.keys.sort

if !lines.empty? && lines.last >= line
nline = lines.bsearch{|l| line <= l}
events = line_events[nline]
if !lines.empty? && lines.last >= line
nline = lines.bsearch{|l| line <= l}
events = line_events[nline]

next if events == [:RUBY_EVENT_B_CALL]
next if events == [:RUBY_EVENT_B_CALL]

if @hook_call &&
events.include?(:RUBY_EVENT_CALL) &&
self.line == iseq.first_lineno
nline = iseq.first_lineno
end
if @hook_call &&
events.include?(:RUBY_EVENT_CALL) &&
self.line == iseq.first_lineno
nline = iseq.first_lineno
end

if !nearest || ((line - nline).abs < (line - nearest.line).abs)
nearest = NearestISeq.new(iseq, nline, events)
else
if @hook_call && nearest.iseq.first_lineno <= iseq.first_lineno
if (nearest.line > line && !nearest.events.include?(:RUBY_EVENT_CALL)) ||
(events.include?(:RUBY_EVENT_CALL))
nearest = NearestISeq.new(iseq, nline, events)
end
if !nearest || ((line - nline).abs < (line - nearest.line).abs)
nearest = NearestISeq.new(iseq, nline, events)
else
if @hook_call && nearest.iseq.first_lineno <= iseq.first_lineno
if (nearest.line > line && !nearest.events.include?(:RUBY_EVENT_CALL)) ||
(events.include?(:RUBY_EVENT_CALL))
nearest = NearestISeq.new(iseq, nline, events)
end
end
end
end
}
end

if nearest
activate_exact nearest.iseq, nearest.events, nearest.line
Expand Down
10 changes: 10 additions & 0 deletions lib/debug/server_dap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,16 @@ def puts result

def event type, *args
case type
when :load
file_path, reloaded = *args

if file_path
send_event 'loadedSource',
reason: (reloaded ? :changed : :new),
source: {
path: file_path,
}
end
when :suspend_bp
_i, bp, tid = *args
if bp.kind_of?(CatchBreakpoint)
Expand Down
18 changes: 15 additions & 3 deletions lib/debug/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,6 @@ def process_event evt
when :load
iseq, src = ev_args
on_load iseq, src
@ui.event :load
request_tc :continue

when :trace
Expand Down Expand Up @@ -1590,15 +1589,28 @@ def in_subsession?

def on_load iseq, src
DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
@sr.add iseq, src

file_path, reloaded = @sr.add(iseq, src)
@ui.event :load, file_path, reloaded

pending_line_breakpoints = @bps.find_all do |key, bp|
LineBreakpoint === bp && !bp.iseq
end

pending_line_breakpoints.each do |_key, bp|
if DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
bp.try_activate
bp.try_activate iseq
end
end

if reloaded
@bps.find_all do |key, bp|
LineBreakpoint === bp && DEBUGGER__.compare_path(bp.path, file_path)
end.each do |_key, bp|
@bps.delete bp.key # to allow duplicate
if nbp = LineBreakpoint.copy(bp, iseq)
add_bp nbp
end
end
end
end
Expand Down
13 changes: 13 additions & 0 deletions lib/debug/source_repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,19 @@ class SourceRepository
def initialize
# cache
@cmap = ObjectSpace::WeakMap.new
@loaded_file_map = {} # path => nil
end

def add iseq, src
# do nothing
if (path = (iseq.absolute_path || iseq.path)) && File.exist?(path)
if @loaded_file_map.has_key? path
return path, true # reloaded
else
@loaded_file_map[path] = path
return path, false
end
end
end

def get iseq
Expand Down Expand Up @@ -55,10 +64,14 @@ def initialize

def add iseq, src
if (path = (iseq.absolute_path || iseq.path)) && File.exist?(path)
reloaded = @files.has_key? path
add_path path
return path, reloaded
elsif src
add_iseq iseq, src
end

nil
end

private def all_iseq iseq, rs = []
Expand Down
40 changes: 40 additions & 0 deletions test/console/break_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -748,4 +748,44 @@ def test_the_path_option_supersede_skip_path_config
end
end
end

class BreakAtLinesReloadTest < ConsoleTestCase
def extra_file
<<~RUBY
def foo # 1
a = 10 # 2
b = 20 # 3
c = 30 # 4
end
RUBY
end

def program path
<<~RUBY
1| load #{path.dump}
2| foo()
3| load #{path.dump}
4| foo()
RUBY
end

def test_break_on_realoded_file
with_extra_tempfile do |extra_file|
debug_code(program(extra_file.path)) do
type "break #{extra_file.path}:2 do: p :xyzzy"
type "break #{extra_file.path}:3"

type 'c'
assert_line_num 3
assert_line_text(/xyzzy/)

type 'c'
assert_line_num 3 # should stop at reloaded file
assert_line_text(/xyzzy/) # should do at line 2

type 'c'
end
end
end
end
end