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
27 changes: 21 additions & 6 deletions lib/debug/breakpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -263,22 +263,29 @@ class CatchBreakpoint < Breakpoint
attr_reader :last_exc
include SkipPathHelper

def initialize pat, cond: nil, command: nil
def initialize pat, cond: nil, command: nil, path: nil
@pat = pat.freeze
@key = [:catch, @pat].freeze
@last_exc = nil

@cond = cond
@command = command
@path = path

super()
end

def setup
@tp = TracePoint.new(:raise){|tp|
next if skip_path?(tp.path)
exc = tp.raised_exception
next if SystemExit === exc

if @path
next if !tp.path.match?(@path)
elsif skip_path?(tp.path)
next
end

next if !safe_eval(tp.binding, @cond) if @cond
should_suspend = false

Expand All @@ -303,9 +310,10 @@ def description
end

class CheckBreakpoint < Breakpoint
def initialize expr
def initialize expr, path
@expr = expr.freeze
@key = [:check, @expr].freeze
@path = path

super()
end
Expand All @@ -315,6 +323,7 @@ def setup
next if tp.path.start_with? __dir__
next if tp.path.start_with? '<internal:'
next if ThreadClient.current.management?
next if @path && !tp.path.match?(@path)

if safe_eval tp.binding, @expr
suspend
Expand All @@ -328,7 +337,7 @@ def to_s
end

class WatchIVarBreakpoint < Breakpoint
def initialize ivar, object, current, cond: nil, command: nil
def initialize ivar, object, current, cond: nil, command: nil, path: nil
@ivar = ivar.to_sym
@object = object
@key = [:watch, object.object_id, @ivar].freeze
Expand All @@ -337,6 +346,7 @@ def initialize ivar, object, current, cond: nil, command: nil

@cond = cond
@command = command
@path = path
super()
end

Expand All @@ -362,6 +372,7 @@ def setup
@tp = TracePoint.new(:line, :return, :b_return){|tp|
next if tp.path.start_with? __dir__
next if tp.path.start_with? '<internal:'
next if @path && !tp.path.match?(@path)

watch_eval
}
Expand All @@ -381,7 +392,7 @@ def to_s
class MethodBreakpoint < Breakpoint
attr_reader :sig_method_name, :method

def initialize b, klass_name, op, method_name, cond: nil, command: nil
def initialize b, klass_name, op, method_name, cond: nil, command: nil, path: nil
@sig_klass_name = klass_name
@sig_op = op
@sig_method_name = method_name
Expand All @@ -393,6 +404,7 @@ def initialize b, klass_name, op, method_name, cond: nil, command: nil
@cond = cond
@cond_class = nil
@command = command
@path = path
@key = "#{klass_name}#{op}#{method_name}".freeze

super(false)
Expand All @@ -402,7 +414,10 @@ def setup
@tp = TracePoint.new(:call){|tp|
next if !safe_eval(tp.binding, @cond) if @cond
next if @cond_class && !tp.self.kind_of?(@cond_class)
next if @override_method ? (caller_locations(2, 1).first.to_s.start_with?(__dir__)) : tp.path.start_with?(__dir__)

caller_location = caller_locations(2, 1).first.to_s
next if @path && !caller_location.match?(@path)
next if caller_location.start_with?(__dir__)

suspend
}
Expand Down
17 changes: 10 additions & 7 deletions lib/debug/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1251,7 +1251,7 @@ def delete_bp arg = nil
end
end

BREAK_KEYWORDS = %w(if: do: pre:).freeze
BREAK_KEYWORDS = %w(if: do: pre: path:).freeze

def parse_break arg
mode = :sig
Expand All @@ -1271,17 +1271,18 @@ def repl_add_breakpoint arg
expr = parse_break arg.strip
cond = expr[:if]
cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
path = Regexp.compile(expr[:path]) if expr[:path]

case expr[:sig]
when /\A(\d+)\z/
add_line_breakpoint @tc.location.path, $1.to_i, cond: cond, command: cmd
when /\A(.+)[:\s+](\d+)\z/
add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
when /\A(.+)([\.\#])(.+)\z/
@tc << [:breakpoint, :method, $1, $2, $3, cond, cmd]
@tc << [:breakpoint, :method, $1, $2, $3, cond, cmd, path]
return :noretry
when nil
add_check_breakpoint cond
add_check_breakpoint cond, path
else
@ui.puts "Unknown breakpoint format: #{arg}"
@ui.puts
Expand All @@ -1293,26 +1294,28 @@ def repl_add_catch_breakpoint arg
expr = parse_break arg.strip
cond = expr[:if]
cmd = ['catch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
path = Regexp.compile(expr[:path]) if expr[:path]

bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd)
bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd, path: path)
add_bp bp
end

def repl_add_watch_breakpoint arg
expr = parse_break arg.strip
cond = expr[:if]
cmd = ['watch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
path = Regexp.compile(expr[:path]) if expr[:path]

@tc << [:breakpoint, :watch, expr[:sig], cond, cmd]
@tc << [:breakpoint, :watch, expr[:sig], cond, cmd, path]
end

def add_catch_breakpoint pat
bp = CatchBreakpoint.new(pat)
add_bp bp
end

def add_check_breakpoint expr
bp = CheckBreakpoint.new(expr)
def add_check_breakpoint expr, path
bp = CheckBreakpoint.new(expr, path)
add_bp bp
end

Expand Down
12 changes: 6 additions & 6 deletions lib/debug/thread_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -635,8 +635,8 @@ def class_method_map(classes)
def make_breakpoint args
case args.first
when :method
klass_name, op, method_name, cond, cmd = args[1..]
bp = MethodBreakpoint.new(current_frame.binding, klass_name, op, method_name, cond: cond, command: cmd)
klass_name, op, method_name, cond, cmd, path = args[1..]
bp = MethodBreakpoint.new(current_frame.binding, klass_name, op, method_name, cond: cond, command: cmd, path: path)
begin
bp.enable
rescue Exception => e
Expand All @@ -646,8 +646,8 @@ def make_breakpoint args

bp
when :watch
ivar, object, result, cond, command = args[1..]
WatchIVarBreakpoint.new(ivar, object, result, cond: cond, command: command)
ivar, object, result, cond, command, path = args[1..]
WatchIVarBreakpoint.new(ivar, object, result, cond: cond, command: command, path: path)
else
raise "unknown breakpoint: #{args}"
end
Expand Down Expand Up @@ -890,7 +890,7 @@ def wait_next_action_
bp = make_breakpoint args
event! :result, :method_breakpoint, bp
when :watch
ivar, cond, command = args[1..]
ivar, cond, command, path = args[1..]
result = frame_eval(ivar)

if @success_last_eval
Expand All @@ -900,7 +900,7 @@ def wait_next_action_
else
current_frame.self
end
bp = make_breakpoint [:watch, ivar, object, result, cond, command]
bp = make_breakpoint [:watch, ivar, object, result, cond, command, path]
event! :result, :watch_breakpoint, bp
else
event! :result, nil
Expand Down
143 changes: 122 additions & 21 deletions test/debug/break_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,37 @@ def test_debugger_doesnt_stop_when_other_instance_calls_the_inherited_method
type "c"
end
end

class PathOptionTest < TestCase
def extra_file
<<~RUBY
Foo.new.bar
RUBY
end

def program(extra_file_path)
<<~RUBY
1| class Foo
2| def bar; end
3| end
4|
5| Foo.new.bar
6|
7| load "#{extra_file_path}"
RUBY
end

def test_break_only_stops_when_path_matches
with_extra_tempfile do |extra_file|
debug_code(program(extra_file.path)) do
type "break Foo#bar path: #{extra_file.path}"
type 'c'
assert_line_text(/#{extra_file.path}/)
type 'c'
end
end
end
end
end

class BreakAtCMethodsTest < TestCase
Expand Down Expand Up @@ -275,6 +306,32 @@ def test_break_C_method_with_singleton_method
type 'c'
end
end

class PathOptionTest < TestCase
def extra_file
<<~RUBY
1.abs
RUBY
end

def program(extra_file_path)
<<~RUBY
1| load "#{extra_file_path}"
2| 1.abs
RUBY
end

def test_break_only_stops_when_path_matches
with_extra_tempfile do |extra_file|
debug_code(program(extra_file.path)) do
type "break Integer#abs path: #{extra_file.path}"
type 'c'
assert_line_text(/#{extra_file.path}/)
type 'c'
end
end
end
end
end

class BreakAtCMethod2Test < TestCase
Expand Down Expand Up @@ -477,27 +534,6 @@ def test_conditional_breakpoint_stops_for_repeated_iterations
end
end

def test_conditional_breakpoint_stops_if_condition_is_true
debug_code program do
type 'break if: n == 1'
assert_line_text(/#0 BP - Check n == 1/)
type 'continue'
assert_line_num 8
type 'quit'
type 'y'
end
end

def test_conditional_breakpoint_shows_error
debug_code(program) do
type 'break if: xyzzy'
type 'b 23'
type 'c'
assert_debuggee_line_text(/EVAL ERROR/)
type 'c'
end
end

def test_conditional_breakpoint_stops_at_specified_location_if_condition_is_true
debug_code(program) do
type 'break 16 if: d == 1'
Expand Down Expand Up @@ -543,4 +579,69 @@ def test_break_with_space_between_file_and_line_stops_at_correct_place
end
end
end

class ConditionalBreakTest < TestCase
def program
<<~RUBY
1| a = 1
2| a += 1
3| a += 2
4| a += 3
5|
6| binding.b
RUBY
end


def test_conditional_breakpoint_stops_if_condition_is_true
debug_code program do
type 'break if: a == 4'
assert_line_text(/#0 BP - Check a == 4/)
type 'c'
assert_line_num 4
type 'c'
type 'c'
end
end

def test_conditional_breakpoint_shows_error
debug_code(program) do
type 'break if: xyzzy'
type 'c'
assert_debuggee_line_text(/EVAL ERROR/)
type 'c'
end
end

class PathOptionTest < TestCase
def extra_file
<<~RUBY
a = 100
b = 1
_ = 0
RUBY
end

def program(extra_file_path)
<<~RUBY
1| a = 200
2| b = 1
3| _ = 0
4| load "#{extra_file_path}"
RUBY
end

def test_conditional_breakpoint_only_stops_when_path_matches
with_extra_tempfile do |extra_file|
debug_code(program(extra_file.path)) do
type "break if: b == 1 path: #{extra_file.path}"
type 'c'
type 'a + b'
assert_line_text(/101/)
type 'c'
end
end
end
end
end
end
Loading