debug 1.0.0.alpha1 → 1.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +20 -19
- data/README.md +353 -51
- data/Rakefile +21 -1
- data/debug.gemspec +6 -11
- data/exe/rdbg +32 -1
- data/ext/debug/debug.c +118 -0
- data/ext/debug/extconf.rb +2 -0
- data/ext/debug/iseq_collector.c +91 -0
- data/lib/debug.rb +1 -1
- data/lib/debug/breakpoint.rb +310 -36
- data/lib/debug/client.rb +4 -11
- data/lib/debug/config.rb +100 -7
- data/lib/debug/console.rb +89 -0
- data/lib/debug/open.rb +10 -0
- data/lib/debug/run.rb +2 -0
- data/lib/debug/server.rb +128 -21
- data/lib/debug/session.rb +605 -170
- data/lib/debug/source_repository.rb +27 -20
- data/lib/debug/thread_client.rb +294 -119
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +325 -0
- metadata +22 -42
- data/lib/debug/repl.rb +0 -69
- data/lib/debug/tcpserver.rb +0 -22
- data/lib/debug/unixserver.rb +0 -18
data/Rakefile
CHANGED
@@ -7,4 +7,24 @@ Rake::TestTask.new(:test) do |t|
|
|
7
7
|
t.test_files = FileList["test/**/*_test.rb"]
|
8
8
|
end
|
9
9
|
|
10
|
-
|
10
|
+
require "rake/extensiontask"
|
11
|
+
|
12
|
+
task :build => :compile
|
13
|
+
|
14
|
+
Rake::ExtensionTask.new("debug") do |ext|
|
15
|
+
ext.lib_dir = "lib/debug"
|
16
|
+
end
|
17
|
+
|
18
|
+
task :default => [:clobber, :compile, :test, 'README.md']
|
19
|
+
|
20
|
+
file 'README.md' => ['lib/debug/session.rb', 'exe/rdbg', 'misc/README.md.erb'] do
|
21
|
+
require_relative 'lib/debug/session'
|
22
|
+
require 'erb'
|
23
|
+
File.write 'README.md', ERB.new(File.read('misc/README.md.erb')).result
|
24
|
+
puts 'README.md is updated.'
|
25
|
+
end
|
26
|
+
|
27
|
+
task :run => :compile do
|
28
|
+
system(RbConfig.ruby, *%w(-I ./lib test.rb))
|
29
|
+
end
|
30
|
+
|
data/debug.gemspec
CHANGED
@@ -6,17 +6,14 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.authors = ["Koichi Sasada"]
|
7
7
|
spec.email = ["ko1@atdot.net"]
|
8
8
|
|
9
|
-
spec.summary = %q{
|
10
|
-
spec.description = %q{debug.rb}
|
11
|
-
spec.homepage = "https://github.com/
|
12
|
-
spec.
|
9
|
+
spec.summary = %q{Debugging functionality for Ruby}
|
10
|
+
spec.description = %q{Debugging functionality for Ruby. This is completely rewritten debug.rb which was contained by the encient Ruby versions.}
|
11
|
+
spec.homepage = "https://github.com/ruby/debug"
|
12
|
+
spec.licenses = ["Ruby", "BSD-2-Clause"]
|
13
13
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
|
14
14
|
|
15
|
-
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
16
|
-
|
17
15
|
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
-
spec.metadata["source_code_uri"] =
|
19
|
-
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
16
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
20
17
|
|
21
18
|
# Specify which files should be added to the gem when it is released.
|
22
19
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -26,7 +23,5 @@ Gem::Specification.new do |spec|
|
|
26
23
|
spec.bindir = "exe"
|
27
24
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
25
|
spec.require_paths = ["lib"]
|
29
|
-
|
30
|
-
spec.add_runtime_dependency 'debug_inspector'
|
31
|
-
spec.add_runtime_dependency 'iseq_collector'
|
26
|
+
spec.extensions = ['ext/debug/extconf.rb']
|
32
27
|
end
|
data/exe/rdbg
CHANGED
@@ -1,3 +1,34 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
3
|
+
require_relative '../lib/debug/client'
|
4
|
+
|
5
|
+
config = DEBUGGER__.parse_argv(ARGV)
|
6
|
+
|
7
|
+
case config[:mode]
|
8
|
+
when :start
|
9
|
+
require 'rbconfig'
|
10
|
+
|
11
|
+
if config[:remote]
|
12
|
+
start_mode = "debug/open"
|
13
|
+
else
|
14
|
+
start_mode = "debug/run"
|
15
|
+
end
|
16
|
+
|
17
|
+
::DEBUGGER__.config_to_env(config)
|
18
|
+
exec("#{RbConfig.ruby}", "-I#{File.expand_path(File.dirname(__dir__))}/lib", "-r" + start_mode, *ARGV)
|
19
|
+
|
20
|
+
when :attach
|
21
|
+
require_relative "../lib/debug/client"
|
22
|
+
|
23
|
+
begin
|
24
|
+
if ARGV.empty? && config[:port]
|
25
|
+
connect [config[:host], config[:port]].compact
|
26
|
+
else
|
27
|
+
connect ARGV
|
28
|
+
end
|
29
|
+
rescue DEBUGGER__::CommandLineOptionError
|
30
|
+
puts opt.help
|
31
|
+
end
|
32
|
+
else
|
33
|
+
raise # assert
|
34
|
+
end
|
data/ext/debug/debug.c
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
|
2
|
+
#include "ruby/ruby.h"
|
3
|
+
#include "ruby/debug.h"
|
4
|
+
#include "ruby/encoding.h"
|
5
|
+
|
6
|
+
//
|
7
|
+
static VALUE rb_mDebugger;
|
8
|
+
|
9
|
+
// iseq
|
10
|
+
typedef struct rb_iseq_struct rb_iseq_t;
|
11
|
+
VALUE rb_iseq_realpath(const rb_iseq_t *iseq);
|
12
|
+
|
13
|
+
static VALUE
|
14
|
+
iseq_realpath(VALUE iseqw)
|
15
|
+
{
|
16
|
+
rb_iseq_t *iseq = DATA_PTR(iseqw);
|
17
|
+
return rb_iseq_realpath(iseq);
|
18
|
+
}
|
19
|
+
|
20
|
+
static VALUE rb_cFrameInfo;
|
21
|
+
|
22
|
+
static VALUE
|
23
|
+
di_entry(VALUE loc, VALUE self, VALUE binding, VALUE iseq, VALUE klass, VALUE depth)
|
24
|
+
{
|
25
|
+
return rb_struct_new(rb_cFrameInfo, loc, self, binding, iseq, klass, depth, Qnil, Qnil, Qnil);
|
26
|
+
}
|
27
|
+
|
28
|
+
static int
|
29
|
+
str_start_with(VALUE str, VALUE prefix)
|
30
|
+
{
|
31
|
+
StringValue(prefix);
|
32
|
+
rb_enc_check(str, prefix);
|
33
|
+
if (RSTRING_LEN(str) >= RSTRING_LEN(prefix) &&
|
34
|
+
memcmp(RSTRING_PTR(str), RSTRING_PTR(prefix), RSTRING_LEN(prefix)) == 0) {
|
35
|
+
return 1;
|
36
|
+
}
|
37
|
+
else {
|
38
|
+
return 0;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
static VALUE
|
43
|
+
di_body(const rb_debug_inspector_t *dc, void *ptr)
|
44
|
+
{
|
45
|
+
VALUE skip_path_prefix = (VALUE)ptr;
|
46
|
+
VALUE locs = rb_debug_inspector_backtrace_locations(dc);
|
47
|
+
VALUE ary = rb_ary_new();
|
48
|
+
long len = RARRAY_LEN(locs);
|
49
|
+
long i;
|
50
|
+
|
51
|
+
for (i=1; i<len; i++) {
|
52
|
+
VALUE iseq = rb_debug_inspector_frame_iseq_get(dc, i);
|
53
|
+
|
54
|
+
if (!NIL_P(iseq)) {
|
55
|
+
VALUE path = iseq_realpath(iseq);
|
56
|
+
if (!NIL_P(path) && str_start_with(path, skip_path_prefix)) continue;
|
57
|
+
}
|
58
|
+
|
59
|
+
VALUE loc = RARRAY_AREF(locs, i);
|
60
|
+
VALUE e = di_entry(loc,
|
61
|
+
rb_debug_inspector_frame_self_get(dc, i),
|
62
|
+
rb_debug_inspector_frame_binding_get(dc, i),
|
63
|
+
iseq,
|
64
|
+
rb_debug_inspector_frame_class_get(dc, i),
|
65
|
+
INT2FIX(len - i));
|
66
|
+
rb_ary_push(ary, e);
|
67
|
+
}
|
68
|
+
|
69
|
+
return ary;
|
70
|
+
}
|
71
|
+
|
72
|
+
static VALUE
|
73
|
+
capture_frames(VALUE self, VALUE skip_path_prefix)
|
74
|
+
{
|
75
|
+
return rb_debug_inspector_open(di_body, (void *)skip_path_prefix);
|
76
|
+
}
|
77
|
+
|
78
|
+
static VALUE
|
79
|
+
frame_depth(VALUE self)
|
80
|
+
{
|
81
|
+
// TODO: more efficient API
|
82
|
+
VALUE bt = rb_make_backtrace();
|
83
|
+
return INT2FIX(RARRAY_LEN(bt));
|
84
|
+
}
|
85
|
+
|
86
|
+
static void
|
87
|
+
method_added_tracker(VALUE tpval, void *ptr)
|
88
|
+
{
|
89
|
+
rb_trace_arg_t *arg = rb_tracearg_from_tracepoint(tpval);
|
90
|
+
VALUE mid = rb_tracearg_callee_id(arg);
|
91
|
+
|
92
|
+
if (RB_UNLIKELY(mid == ID2SYM(rb_intern("method_added")) ||
|
93
|
+
mid == ID2SYM(rb_intern("singleton_method_method_added")))) {
|
94
|
+
VALUE args[] = {
|
95
|
+
tpval,
|
96
|
+
};
|
97
|
+
rb_funcallv(rb_mDebugger, rb_intern("method_added"), 1, args);
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
static VALUE
|
102
|
+
create_method_added_tracker(VALUE self)
|
103
|
+
{
|
104
|
+
return rb_tracepoint_new(0, RUBY_EVENT_CALL, method_added_tracker, NULL);
|
105
|
+
}
|
106
|
+
|
107
|
+
void Init_iseq_collector(void);
|
108
|
+
|
109
|
+
void
|
110
|
+
Init_debug(void)
|
111
|
+
{
|
112
|
+
rb_mDebugger = rb_const_get(rb_cObject, rb_intern("DEBUGGER__"));
|
113
|
+
rb_cFrameInfo = rb_const_get(rb_mDebugger, rb_intern("FrameInfo"));
|
114
|
+
rb_define_singleton_method(rb_mDebugger, "capture_frames", capture_frames, 1);
|
115
|
+
rb_define_singleton_method(rb_mDebugger, "frame_depth", frame_depth, 0);
|
116
|
+
rb_define_singleton_method(rb_mDebugger, "create_method_added_tracker", create_method_added_tracker, 0);
|
117
|
+
Init_iseq_collector();
|
118
|
+
}
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#include <ruby/ruby.h>
|
2
|
+
|
3
|
+
VALUE rb_iseqw_new(VALUE v);
|
4
|
+
void rb_objspace_each_objects(
|
5
|
+
int (*callback)(void *start, void *end, size_t stride, void *data),
|
6
|
+
void *data);
|
7
|
+
size_t rb_obj_memsize_of(VALUE);
|
8
|
+
|
9
|
+
// implementation specific.
|
10
|
+
enum imemo_type {
|
11
|
+
imemo_iseq = 7,
|
12
|
+
imemo_mask = 0x07
|
13
|
+
};
|
14
|
+
|
15
|
+
static inline enum imemo_type
|
16
|
+
imemo_type(VALUE imemo)
|
17
|
+
{
|
18
|
+
return (RBASIC(imemo)->flags >> FL_USHIFT) & imemo_mask;
|
19
|
+
}
|
20
|
+
|
21
|
+
static inline int
|
22
|
+
rb_obj_is_iseq(VALUE iseq)
|
23
|
+
{
|
24
|
+
return RB_TYPE_P(iseq, T_IMEMO) && imemo_type(iseq) == imemo_iseq;
|
25
|
+
}
|
26
|
+
|
27
|
+
struct iseq_i_data {
|
28
|
+
void (*func)(VALUE v, void *data);
|
29
|
+
void *data;
|
30
|
+
};
|
31
|
+
|
32
|
+
int
|
33
|
+
iseq_i(void *vstart, void *vend, size_t stride, void *ptr)
|
34
|
+
{
|
35
|
+
VALUE v;
|
36
|
+
struct iseq_i_data *data = (struct iseq_i_data *)ptr;
|
37
|
+
|
38
|
+
for (v = (VALUE)vstart; v != (VALUE)vend; v += stride) {
|
39
|
+
if (RBASIC(v)->flags) {
|
40
|
+
switch (BUILTIN_TYPE(v)) {
|
41
|
+
case T_IMEMO:
|
42
|
+
if (rb_obj_is_iseq(v)) {
|
43
|
+
data->func(v, data->data);
|
44
|
+
}
|
45
|
+
continue;
|
46
|
+
default:
|
47
|
+
continue;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
return 0;
|
53
|
+
}
|
54
|
+
|
55
|
+
static void
|
56
|
+
each_iseq_i(VALUE v, void *ptr)
|
57
|
+
{
|
58
|
+
rb_yield(rb_iseqw_new(v));
|
59
|
+
}
|
60
|
+
|
61
|
+
static VALUE
|
62
|
+
each_iseq(VALUE self)
|
63
|
+
{
|
64
|
+
struct iseq_i_data data = {each_iseq_i, NULL};
|
65
|
+
rb_objspace_each_objects(iseq_i, &data);
|
66
|
+
return Qnil;
|
67
|
+
}
|
68
|
+
|
69
|
+
static void
|
70
|
+
count_iseq_i(VALUE v, void *ptr)
|
71
|
+
{
|
72
|
+
size_t *sizep = (size_t *)ptr;
|
73
|
+
*sizep += 1;
|
74
|
+
}
|
75
|
+
|
76
|
+
static VALUE
|
77
|
+
count_iseq(VALUE self)
|
78
|
+
{
|
79
|
+
size_t size = 0;
|
80
|
+
struct iseq_i_data data = {count_iseq_i, &size};
|
81
|
+
rb_objspace_each_objects(iseq_i, &data);
|
82
|
+
return SIZET2NUM(size);
|
83
|
+
}
|
84
|
+
|
85
|
+
void
|
86
|
+
Init_iseq_collector(void)
|
87
|
+
{
|
88
|
+
VALUE rb_mObjSpace = rb_const_get(rb_cObject, rb_intern("ObjectSpace"));
|
89
|
+
rb_define_singleton_method(rb_mObjSpace, "each_iseq", each_iseq, 0);
|
90
|
+
rb_define_singleton_method(rb_mObjSpace, "count_iseq", count_iseq, 0);
|
91
|
+
}
|
data/lib/debug.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require_relative 'debug/
|
1
|
+
require_relative 'debug/run'
|
data/lib/debug/breakpoint.rb
CHANGED
@@ -1,17 +1,12 @@
|
|
1
1
|
module DEBUGGER__
|
2
|
-
class
|
3
|
-
attr_reader :
|
2
|
+
class Breakpoint
|
3
|
+
attr_reader :key
|
4
|
+
|
5
|
+
def initialize do_enable = true
|
6
|
+
@deleted = false
|
4
7
|
|
5
|
-
def initialize type, iseq, line, cond = nil, oneshot: false
|
6
|
-
@iseq = iseq
|
7
|
-
@path = iseq.path
|
8
|
-
@line = line
|
9
|
-
@type = type
|
10
|
-
@cond = cond
|
11
|
-
@oneshot = oneshot
|
12
|
-
@key = [@path, @line].freeze
|
13
8
|
setup
|
14
|
-
enable
|
9
|
+
enable if do_enable
|
15
10
|
end
|
16
11
|
|
17
12
|
def safe_eval b, expr
|
@@ -20,46 +15,178 @@ module DEBUGGER__
|
|
20
15
|
puts "[EVAL ERROR]"
|
21
16
|
puts " expr: #{expr}"
|
22
17
|
puts " err: #{e} (#{e.class})"
|
18
|
+
puts "Error caused by #{self}."
|
23
19
|
nil
|
24
20
|
end
|
25
21
|
|
26
22
|
def setup
|
23
|
+
raise "not implemented..."
|
24
|
+
end
|
25
|
+
|
26
|
+
def enable
|
27
|
+
@tp.enable
|
28
|
+
end
|
29
|
+
|
30
|
+
def disable
|
31
|
+
@tp.disable
|
32
|
+
end
|
33
|
+
|
34
|
+
def enabled?
|
35
|
+
@tp.enabled?
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete
|
39
|
+
disable
|
40
|
+
@deleted = true
|
41
|
+
end
|
42
|
+
|
43
|
+
def deleted?
|
44
|
+
@deleted
|
45
|
+
end
|
46
|
+
|
47
|
+
def suspend
|
48
|
+
ThreadClient.current.on_breakpoint @tp, self
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
if @cond
|
53
|
+
" if #{@cond}"
|
54
|
+
else
|
55
|
+
""
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class LineBreakpoint < Breakpoint
|
61
|
+
attr_reader :path, :line, :iseq
|
62
|
+
|
63
|
+
def initialize path, line, cond: nil, oneshot: false, hook_call: true
|
64
|
+
@path = path
|
65
|
+
@line = line
|
66
|
+
@cond = cond
|
67
|
+
@oneshot = oneshot
|
68
|
+
@hook_call = hook_call
|
69
|
+
|
70
|
+
@iseq = nil
|
71
|
+
@type = nil
|
72
|
+
|
73
|
+
@key = [@path, @line].freeze
|
74
|
+
|
75
|
+
super()
|
76
|
+
try_activate
|
77
|
+
end
|
78
|
+
|
79
|
+
def setup
|
80
|
+
return unless @type
|
81
|
+
|
27
82
|
if !@cond
|
28
83
|
@tp = TracePoint.new(@type) do |tp|
|
29
|
-
|
30
|
-
|
84
|
+
delete if @oneshot
|
85
|
+
suspend
|
31
86
|
end
|
32
87
|
else
|
33
88
|
@tp = TracePoint.new(@type) do |tp|
|
34
89
|
next unless safe_eval tp.binding, @cond
|
35
|
-
|
36
|
-
|
90
|
+
delete if @oneshot
|
91
|
+
suspend
|
37
92
|
end
|
38
93
|
end
|
39
94
|
end
|
40
95
|
|
41
96
|
def enable
|
97
|
+
return unless @iseq
|
98
|
+
|
42
99
|
if @type == :line
|
43
100
|
@tp.enable(target: @iseq, target_line: @line)
|
44
101
|
else
|
45
102
|
@tp.enable(target: @iseq)
|
46
103
|
end
|
104
|
+
|
47
105
|
rescue ArgumentError
|
48
106
|
puts @iseq.disasm # for debug
|
49
107
|
raise
|
50
108
|
end
|
51
109
|
|
52
|
-
def
|
53
|
-
@
|
110
|
+
def activate iseq, event, line
|
111
|
+
@iseq = iseq
|
112
|
+
@type = event
|
113
|
+
@line = line
|
114
|
+
@path = iseq.absolute_path
|
115
|
+
|
116
|
+
@key = [@path, @line].freeze
|
117
|
+
SESSION.rehash_bps
|
118
|
+
|
119
|
+
setup
|
120
|
+
enable
|
54
121
|
end
|
55
122
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
123
|
+
def activate_exact iseq, events, line
|
124
|
+
case
|
125
|
+
when events.include?(:RUBY_EVENT_CALL)
|
126
|
+
# "def foo" line set bp on the beggining of method foo
|
127
|
+
activate(iseq, :call, line)
|
128
|
+
when events.include?(:RUBY_EVENT_LINE)
|
129
|
+
activate(iseq, :line, line)
|
130
|
+
when events.include?(:RUBY_EVENT_RETURN)
|
131
|
+
activate(iseq, :return, line)
|
132
|
+
when events.include?(:RUBY_EVENT_B_RETURN)
|
133
|
+
activate(iseq, :b_return, line)
|
134
|
+
when events.include?(:RUBY_EVENT_END)
|
135
|
+
activate(iseq, :end, line)
|
136
|
+
else
|
137
|
+
# not actiavated
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
NearestISeq = Struct.new(:iseq, :line, :events)
|
142
|
+
|
143
|
+
def try_activate
|
144
|
+
nearest = nil # NearestISeq
|
145
|
+
|
146
|
+
ObjectSpace.each_iseq{|iseq|
|
147
|
+
if (iseq.absolute_path || iseq.path) == self.path && iseq.first_lineno <= self.line
|
148
|
+
iseq.traceable_lines_norec(line_events = {})
|
149
|
+
lines = line_events.keys.sort
|
150
|
+
|
151
|
+
if !lines.empty? && lines.last >= line
|
152
|
+
nline = lines.bsearch{|l| line <= l}
|
153
|
+
events = line_events[nline]
|
154
|
+
|
155
|
+
next if events == [:RUBY_EVENT_B_CALL]
|
156
|
+
|
157
|
+
if @hook_call &&
|
158
|
+
events.include?(:RUBY_EVENT_CALL) &&
|
159
|
+
self.line == iseq.first_lineno
|
160
|
+
nline = iseq.first_lineno
|
161
|
+
end
|
162
|
+
|
163
|
+
if !nearest || ((line - nline).abs < (line - nearest.line).abs)
|
164
|
+
nearest = NearestISeq.new(iseq, nline, events)
|
165
|
+
else
|
166
|
+
if @hook_call && nearest.iseq.first_lineno <= iseq.first_lineno
|
167
|
+
if (nearest.line > line && !nearest.events.include?(:RUBY_EVENT_CALL)) ||
|
168
|
+
(events.include?(:RUBY_EVENT_CALL))
|
169
|
+
nearest = NearestISeq.new(iseq, nline, events)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
62
174
|
end
|
175
|
+
}
|
176
|
+
|
177
|
+
if nearest
|
178
|
+
activate_exact nearest.iseq, nearest.events, nearest.line
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def to_s
|
183
|
+
oneshot = @oneshot ? " (oneshot)" : ""
|
184
|
+
|
185
|
+
if @iseq
|
186
|
+
"line bp #{@path}:#{@line} (#{@type})#{oneshot}" + super
|
187
|
+
else
|
188
|
+
"line bp (pending) #{@path}:#{@line}#{oneshot}" + super
|
189
|
+
end
|
63
190
|
end
|
64
191
|
|
65
192
|
def inspect
|
@@ -67,31 +194,178 @@ module DEBUGGER__
|
|
67
194
|
end
|
68
195
|
end
|
69
196
|
|
70
|
-
class CatchBreakpoint
|
71
|
-
attr_reader :key
|
72
|
-
|
197
|
+
class CatchBreakpoint < Breakpoint
|
73
198
|
def initialize pat
|
74
|
-
@pat = pat
|
199
|
+
@pat = pat.freeze
|
200
|
+
@key = [:catch, @pat].freeze
|
201
|
+
|
202
|
+
super()
|
203
|
+
end
|
204
|
+
|
205
|
+
def setup
|
75
206
|
@tp = TracePoint.new(:raise){|tp|
|
76
207
|
exc = tp.raised_exception
|
77
208
|
exc.class.ancestors.each{|cls|
|
78
|
-
if pat === cls.name
|
79
|
-
puts "catch #{exc.class.inspect} by #{@pat.inspect}"
|
80
|
-
ThreadClient.current.on_suspend :catch
|
81
|
-
end
|
209
|
+
suspend if pat === cls.name
|
82
210
|
}
|
83
211
|
}
|
84
|
-
|
212
|
+
end
|
85
213
|
|
86
|
-
|
214
|
+
def to_s
|
215
|
+
"catch bp #{@pat.inspect}"
|
87
216
|
end
|
217
|
+
end
|
88
218
|
|
89
|
-
|
90
|
-
|
219
|
+
class CheckBreakpoint < Breakpoint
|
220
|
+
def initialize expr
|
221
|
+
@expr = expr.freeze
|
222
|
+
@key = [:check, @expr].freeze
|
223
|
+
|
224
|
+
super()
|
225
|
+
end
|
226
|
+
|
227
|
+
def setup
|
228
|
+
@tp = TracePoint.new(:line){|tp|
|
229
|
+
next if tp.path.start_with? __dir__
|
230
|
+
next if tp.path.start_with? '<internal:'
|
231
|
+
|
232
|
+
if safe_eval tp.binding, @expr
|
233
|
+
suspend
|
234
|
+
end
|
235
|
+
}
|
91
236
|
end
|
92
237
|
|
93
238
|
def to_s
|
94
|
-
"
|
239
|
+
"check bp: #{@expr}"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
class WatchExprBreakpoint < Breakpoint
|
244
|
+
def initialize expr, current
|
245
|
+
@expr = expr.freeze
|
246
|
+
@key = [:watch, @expr].freeze
|
247
|
+
|
248
|
+
@current = current
|
249
|
+
super()
|
250
|
+
end
|
251
|
+
|
252
|
+
def watch_eval b
|
253
|
+
result = b.eval(@expr)
|
254
|
+
if result != @current
|
255
|
+
begin
|
256
|
+
@prev = @current
|
257
|
+
@current = result
|
258
|
+
suspend
|
259
|
+
ensure
|
260
|
+
remove_instance_variable(:@prev)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
rescue Exception => e
|
264
|
+
false
|
265
|
+
end
|
266
|
+
|
267
|
+
def setup
|
268
|
+
@tp = TracePoint.new(:line, :return, :b_return){|tp|
|
269
|
+
next if tp.path.start_with? __dir__
|
270
|
+
next if tp.path.start_with? '<internal:'
|
271
|
+
|
272
|
+
watch_eval(tp.binding)
|
273
|
+
}
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_s
|
277
|
+
if defined? @prev
|
278
|
+
"watch bp: #{@expr} = #{@prev} -> #{@current}"
|
279
|
+
else
|
280
|
+
"watch bp: #{@expr} = #{@current}"
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
class MethodBreakpoint < Breakpoint
|
286
|
+
attr_reader :sig_method_name, :method
|
287
|
+
|
288
|
+
def initialize b, klass_name, op, method_name, cond
|
289
|
+
@sig_klass_name = klass_name
|
290
|
+
@sig_op = op
|
291
|
+
@sig_method_name = method_name
|
292
|
+
@klass_eval_binding = b
|
293
|
+
|
294
|
+
@klass = nil
|
295
|
+
@method = nil
|
296
|
+
@cond = cond
|
297
|
+
@key = "#{klass_name}#{op}#{method_name}".freeze
|
298
|
+
|
299
|
+
super(false)
|
300
|
+
end
|
301
|
+
|
302
|
+
def setup
|
303
|
+
if @cond
|
304
|
+
@tp = TracePoint.new(:call){|tp|
|
305
|
+
next unless safe_eval tp.binding, @cond
|
306
|
+
suspend
|
307
|
+
}
|
308
|
+
else
|
309
|
+
@tp = TracePoint.new(:call){|tp|
|
310
|
+
suspend
|
311
|
+
}
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def eval_class_name
|
316
|
+
return @klass if @klass
|
317
|
+
@klass = @klass_eval_binding.eval(@sig_klass_name)
|
318
|
+
@klass_eval_binding = nil
|
319
|
+
@klass
|
320
|
+
end
|
321
|
+
|
322
|
+
def search_method
|
323
|
+
case @sig_op
|
324
|
+
when '.'
|
325
|
+
@method = @klass.method(@sig_method_name)
|
326
|
+
when '#'
|
327
|
+
@method = @klass.instance_method(@sig_method_name)
|
328
|
+
else
|
329
|
+
raise "Unknown op: #{@sig_op}"
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def enable quiet: false
|
334
|
+
eval_class_name
|
335
|
+
search_method
|
336
|
+
|
337
|
+
begin
|
338
|
+
retried = false
|
339
|
+
@tp.enable(target: @method)
|
340
|
+
|
341
|
+
rescue ArgumentError => e
|
342
|
+
raise if retried
|
343
|
+
retried = true
|
344
|
+
|
345
|
+
# maybe C method
|
346
|
+
@klass.module_eval do
|
347
|
+
orig_name = @sig_method_name + '__orig__'
|
348
|
+
alias_method orig_name, @sig_method_name
|
349
|
+
define_method(@sig_method_name) do |*args|
|
350
|
+
send(orig_name, *args)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
retry
|
354
|
+
end
|
355
|
+
rescue Exception
|
356
|
+
raise unless quiet
|
357
|
+
end
|
358
|
+
|
359
|
+
def sig
|
360
|
+
@key
|
361
|
+
end
|
362
|
+
|
363
|
+
def to_s
|
364
|
+
if @method
|
365
|
+
"method bp: #{sig}"
|
366
|
+
else
|
367
|
+
"method bp (pending): #{sig}"
|
368
|
+
end + super
|
95
369
|
end
|
96
370
|
end
|
97
371
|
end
|