debug 1.0.0.beta8 → 1.0.0.rc1

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.
data/lib/debug/console.rb CHANGED
@@ -1,63 +1,87 @@
1
1
  # frozen_string_literal: true
2
+ module DEBUGGER__
3
+ class Console
4
+ begin
5
+ raise LoadError if CONFIG[:no_reline]
6
+ require 'reline'
2
7
 
3
- require 'io/console/size'
8
+ # reline 0.2.7 or later is required.
9
+ raise LoadError if Reline::VERSION < '0.2.6'
4
10
 
5
- module DEBUGGER__
6
- class UI_Console < UI_Base
7
- def initialize
8
- unless CONFIG[:no_sigint_hook]
9
- @prev_handler = trap(:SIGINT){
10
- ThreadClient.current.on_trap :SIGINT
11
- }
12
- end
13
- end
11
+ require_relative 'color'
12
+ include Color
14
13
 
15
- def close
16
- if @prev_handler
17
- trap(:SIGINT, @prev_handler)
14
+ # 0.2.7 has SIGWINCH issue on non-main thread
15
+ class ::Reline::LineEditor
16
+ m = Module.new do
17
+ def reset(prompt = '', encoding:)
18
+ super
19
+ Signal.trap(:SIGWINCH, nil)
20
+ end
21
+ end
22
+ prepend m
18
23
  end
19
- end
20
24
 
21
- def remote?
22
- false
23
- end
25
+ def readline_setup prompt
26
+ commands = DEBUGGER__.commands
24
27
 
25
- def width
26
- if (w = IO.console_size[1]) == 0 # for tests PTY
27
- 80
28
- else
29
- w
30
- end
31
- end
28
+ Reline.completion_proc = -> given do
29
+ buff = Reline.line_buffer
30
+ Reline.completion_append_character= ' '
32
31
 
33
- def quit n
34
- exit n
35
- end
32
+ if /\s/ =~ buff # second parameters
33
+ given = File.expand_path(given + 'a').sub(/a\z/, '')
34
+ files = Dir.glob(given + '*')
35
+ if files.size == 1 && File.directory?(files.first)
36
+ Reline.completion_append_character= '/'
37
+ end
38
+ files
39
+ else
40
+ commands.keys.grep(/\A#{given}/)
41
+ end
42
+ end
36
43
 
37
- def ask prompt
38
- setup_interrupt do
39
- print prompt
40
- ($stdin.gets || '').strip
44
+ Reline.output_modifier_proc = -> buff, **kw do
45
+ c, rest = get_command buff
46
+
47
+ case
48
+ when commands.keys.include?(c = c.strip)
49
+ # [:DIM, :CYAN, :BLUE, :CLEAR, :UNDERLINE, :REVERSE, :RED, :GREEN, :MAGENTA, :BOLD, :YELLOW]
50
+ cmd = colorize(c.strip, [:CYAN, :UNDERLINE])
51
+
52
+ if commands[c] == c
53
+ rprompt = colorize(" # command", [:DIM])
54
+ else
55
+ rprompt = colorize(" # #{commands[c]} command", [:DIM])
56
+ end
57
+
58
+ rest = (rest ? colorize_code(rest) : '') + rprompt
59
+ cmd + rest
60
+ when !rest && /\A\s*[a-z]*\z/ =~ c
61
+ buff
62
+ else
63
+ colorize_code(buff.chomp) + colorize(" # ruby", [:DIM])
64
+ end
65
+ end
41
66
  end
42
- end
43
67
 
44
- def puts str = nil
45
- case str
46
- when Array
47
- str.each{|line|
48
- $stdout.puts line.chomp
49
- }
50
- when String
51
- str.each_line{|line|
52
- $stdout.puts line.chomp
53
- }
54
- when nil
55
- $stdout.puts
68
+ private def get_command line
69
+ case line.chomp
70
+ when /\A(\s*[a-z]+)(\s.*)?\z$/
71
+ return $1, $2
72
+ else
73
+ line.chomp
74
+ end
56
75
  end
57
- end
58
76
 
77
+ def readline prompt
78
+ readline_setup prompt
79
+ Reline.readmultiline(prompt, true){ true }
80
+ end
81
+
82
+ rescue LoadError
59
83
  begin
60
- require 'readline'
84
+ require 'readline.so'
61
85
 
62
86
  def readline_setup
63
87
  Readline.completion_proc = proc{|given|
@@ -72,38 +96,22 @@ module DEBUGGER__
72
96
  end
73
97
  files
74
98
  else
75
- DEBUGGER__.commands.grep(/\A#{given}/)
99
+ DEBUGGER__.commands.keys.grep(/\A#{given}/)
76
100
  end
77
101
  }
78
102
  end
79
103
 
80
- def readline_body
104
+ def readline prompt
81
105
  readline_setup
82
- Readline.readline("\n(rdbg) ", true)
106
+ Readline.readline(prompt, true)
83
107
  end
108
+
84
109
  rescue LoadError
85
- def readline_body
86
- print "\n(rdbg) "
110
+ def readline prompt
111
+ print prompt
87
112
  gets
88
113
  end
89
114
  end
90
-
91
- def readline
92
- setup_interrupt do
93
- (readline_body || 'quit').strip
94
- end
95
- end
96
-
97
- def setup_interrupt
98
- current_thread = Thread.current # should be session_server thread
99
-
100
- prev_handler = trap(:INT){
101
- current_thread.raise Interrupt
102
- }
103
-
104
- yield
105
- ensure
106
- trap(:INT, prev_handler)
107
115
  end
108
116
  end
109
117
  end
@@ -4,7 +4,9 @@ module DEBUGGER__
4
4
  FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
5
5
  :has_return_value, :return_value,
6
6
  :has_raised_exception, :raised_exception,
7
- :show_line)
7
+ :show_line,
8
+ :_local_variables, :_callee # for recorder
9
+ )
8
10
 
9
11
  # extend FrameInfo with debug.so
10
12
  if File.exist? File.join(__dir__, 'debug.so')
@@ -25,10 +27,10 @@ module DEBUGGER__
25
27
  end
26
28
 
27
29
  def pretty_path
28
- use_short_path = ::DEBUGGER__::CONFIG[:use_short_path]
30
+ use_short_path = CONFIG[:use_short_path]
29
31
 
30
32
  case
31
- when use_short_path && path.start_with?(dir = ::DEBUGGER__::CONFIG["rubylibdir"] + '/')
33
+ when use_short_path && path.start_with?(dir = CONFIG["rubylibdir"] + '/')
32
34
  path.sub(dir, '$(rubylibdir)/')
33
35
  when use_short_path && Gem.path.any? do |gp|
34
36
  path.start_with?(dir = gp + '/gems/')
@@ -62,7 +64,7 @@ module DEBUGGER__
62
64
  end
63
65
 
64
66
  def frame_type
65
- if binding && iseq
67
+ if self.local_variables && iseq
66
68
  if iseq.type == :block
67
69
  :block
68
70
  elsif callee
@@ -102,11 +104,11 @@ module DEBUGGER__
102
104
  end
103
105
 
104
106
  def callee
105
- @callee ||= binding&.eval('__callee__', __FILE__, __LINE__)
107
+ self._callee ||= self.binding&.eval('__callee__')
106
108
  end
107
109
 
108
110
  def return_str
109
- if binding && iseq && has_return_value
111
+ if self.binding && iseq && has_return_value
110
112
  DEBUGGER__.short_inspect(return_value)
111
113
  end
112
114
  end
@@ -115,6 +117,33 @@ module DEBUGGER__
115
117
  "#{pretty_path}:#{location.lineno}"
116
118
  end
117
119
 
120
+ private def make_binding
121
+ __newb__ = self.self.instance_eval('binding')
122
+ self.local_variables.each{|var, val|
123
+ __newb__.local_variable_set(var, val)
124
+ }
125
+ __newb__
126
+ end
127
+
128
+ def eval_binding
129
+ if b = self.binding
130
+ b
131
+ elsif self.local_variables
132
+ make_binding
133
+ end
134
+ end
135
+
136
+ def local_variables
137
+ if lvars = self._local_variables
138
+ lvars
139
+ elsif b = self.binding
140
+ lvars = b.local_variables.map{|var|
141
+ [var, b.local_variable_get(var)]
142
+ }.to_h
143
+ self._local_variables = lvars
144
+ end
145
+ end
146
+
118
147
  private
119
148
 
120
149
  def get_singleton_class obj
@@ -123,11 +152,15 @@ module DEBUGGER__
123
152
  nil
124
153
  end
125
154
 
155
+ private def local_variable_get var
156
+ local_variables[var]
157
+ end
158
+
126
159
  def parameters_info(argc)
127
160
  vars = iseq.locals[0...argc]
128
161
  vars.map{|var|
129
162
  begin
130
- { name: var, value: DEBUGGER__.short_inspect(binding.local_variable_get(var)) }
163
+ { name: var, value: DEBUGGER__.short_inspect(local_variable_get(var)) }
131
164
  rescue NameError, TypeError
132
165
  nil
133
166
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console/size'
4
+ require_relative 'console'
5
+
6
+ module DEBUGGER__
7
+ class UI_LocalConsole < UI_Base
8
+ def initialize
9
+ @console = Console.new
10
+
11
+ unless CONFIG[:no_sigint_hook]
12
+ @prev_handler = trap(:SIGINT){
13
+ if SESSION.active?
14
+ ThreadClient.current.on_trap :SIGINT
15
+ end
16
+ }
17
+ end
18
+ end
19
+
20
+ def close
21
+ if @prev_handler
22
+ trap(:SIGINT, @prev_handler)
23
+ end
24
+ end
25
+
26
+ def remote?
27
+ false
28
+ end
29
+
30
+ def activate on_fork: false
31
+ # Do nothing
32
+ end
33
+
34
+ def deactivate
35
+ # Do nothing
36
+ end
37
+
38
+ def width
39
+ if (w = IO.console_size[1]) == 0 # for tests PTY
40
+ 80
41
+ else
42
+ w
43
+ end
44
+ end
45
+
46
+ def quit n
47
+ exit n
48
+ end
49
+
50
+ def ask prompt
51
+ setup_interrupt do
52
+ print prompt
53
+ ($stdin.gets || '').strip
54
+ end
55
+ end
56
+
57
+ def puts str = nil
58
+ case str
59
+ when Array
60
+ str.each{|line|
61
+ $stdout.puts line.chomp
62
+ }
63
+ when String
64
+ str.each_line{|line|
65
+ $stdout.puts line.chomp
66
+ }
67
+ when nil
68
+ $stdout.puts
69
+ end
70
+ end
71
+
72
+ def readline prompt = '(rdbg)'
73
+ setup_interrupt do
74
+ (@console.readline(prompt) || 'quit').strip
75
+ end
76
+ end
77
+
78
+ def setup_interrupt
79
+ current_thread = Thread.current # should be session_server thread
80
+
81
+ prev_handler = trap(:INT){
82
+ current_thread.raise Interrupt
83
+ }
84
+
85
+ yield
86
+ ensure
87
+ trap(:INT, prev_handler)
88
+ end
89
+ end
90
+ end
91
+
data/lib/debug/server.rb CHANGED
@@ -7,7 +7,7 @@ require_relative 'version'
7
7
  module DEBUGGER__
8
8
  class UI_ServerBase < UI_Base
9
9
  def initialize
10
- @sock = nil
10
+ @sock = @sock_for_fork = nil
11
11
  @accept_m = Mutex.new
12
12
  @accept_cv = ConditionVariable.new
13
13
  @client_addr = nil
@@ -16,11 +16,33 @@ module DEBUGGER__
16
16
  @unsent_messages = []
17
17
  @width = 80
18
18
 
19
+ activate
20
+ end
21
+
22
+ class Terminate < StandardError
23
+ end
24
+
25
+ def deactivate
26
+ @reader_thread.raise Terminate
27
+ end
28
+
29
+ def accept
30
+ if @sock_for_fork
31
+ begin
32
+ yield @sock_for_fork, already_connected: true
33
+ ensure
34
+ @sock_for_fork.close
35
+ @sock_for_fork = nil
36
+ end
37
+ end
38
+ end
39
+
40
+ def activate on_fork: false
19
41
  @reader_thread = Thread.new do
20
42
  # An error on this thread should break the system.
21
43
  Thread.current.abort_on_exception = true
22
44
 
23
- accept do |server|
45
+ accept do |server, already_connected: false|
24
46
  DEBUGGER__.warn "Connected."
25
47
 
26
48
  @accept_m.synchronize{
@@ -37,14 +59,17 @@ module DEBUGGER__
37
59
 
38
60
  @q_msg = Queue.new
39
61
  @q_ans = Queue.new
40
- }
62
+ } unless already_connected
41
63
 
42
64
  setup_interrupt do
65
+ pause unless already_connected
43
66
  process
44
67
  end
45
68
 
69
+ rescue Terminate
70
+ raise # should catch at outer scope
46
71
  rescue => e
47
- DEBUGGER__.warn "ReaderThreadError: #{e}", :error
72
+ DEBUGGER__.warn "ReaderThreadError: #{e}"
48
73
  ensure
49
74
  DEBUGGER__.warn "Disconnected."
50
75
  @sock = nil
@@ -52,7 +77,10 @@ module DEBUGGER__
52
77
  @q_msg = nil
53
78
  @q_ans.close
54
79
  @q_ans = nil
55
- end
80
+ end # accept
81
+
82
+ rescue Terminate
83
+ # ignore
56
84
  end
57
85
  end
58
86
 
@@ -84,8 +112,6 @@ module DEBUGGER__
84
112
  end
85
113
 
86
114
  def process
87
- pause
88
-
89
115
  while line = @sock.gets
90
116
  case line
91
117
  when /\Apause/
@@ -129,10 +155,6 @@ module DEBUGGER__
129
155
  trap(:SIGINT, prev_handler)
130
156
  end
131
157
 
132
- def accept
133
- raise "NOT IMPLEMENTED ERROR"
134
- end
135
-
136
158
  attr_reader :reader_thread
137
159
 
138
160
  class NoRemoteError < Exception; end
@@ -154,7 +176,7 @@ module DEBUGGER__
154
176
  until s = @sock
155
177
  @accept_m.synchronize{
156
178
  unless @sock
157
- DEBUGGER__.warn "wait for debuger connection..."
179
+ DEBUGGER__.warn "wait for debugger connection..."
158
180
  @accept_cv.wait(@accept_m)
159
181
  end
160
182
  }
@@ -195,12 +217,15 @@ module DEBUGGER__
195
217
  end
196
218
  end
197
219
 
198
- def readline
199
- (sock do |s|
220
+ def readline prompt
221
+ input = (sock do |s|
200
222
  s.puts "input"
201
223
  sleep 0.01 until @q_msg
224
+
202
225
  @q_msg.pop
203
226
  end || 'continue').strip
227
+
228
+ input
204
229
  end
205
230
 
206
231
  def pause
@@ -218,9 +243,9 @@ module DEBUGGER__
218
243
 
219
244
  class UI_TcpServer < UI_ServerBase
220
245
  def initialize host: nil, port: nil
221
- @host = host || ::DEBUGGER__::CONFIG[:host] || '127.0.0.1'
246
+ @host = host || CONFIG[:host] || '127.0.0.1'
222
247
  @port = port || begin
223
- port_str = ::DEBUGGER__::CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
248
+ port_str = CONFIG[:port] || raise("Specify listening port by RUBY_DEBUG_PORT environment variable.")
224
249
  if /\A\d+\z/ !~ port_str
225
250
  raise "Specify digits for port number"
226
251
  else
@@ -232,17 +257,34 @@ module DEBUGGER__
232
257
  end
233
258
 
234
259
  def accept
235
- Socket.tcp_server_sockets @host, @port do |socks|
236
- ::DEBUGGER__.warn "Debugger can attach via TCP/IP (#{socks.map{|e| e.local_address.inspect}})"
237
- Socket.accept_loop(socks) do |sock, client|
238
- @client_addr = client
239
- yield sock
260
+ retry_cnt = 0
261
+ super # for fork
262
+
263
+ begin
264
+ Socket.tcp_server_sockets @host, @port do |socks|
265
+ ::DEBUGGER__.warn "Debugger can attach via TCP/IP (#{socks.map{|e| e.local_address.inspect}})"
266
+ Socket.accept_loop(socks) do |sock, client|
267
+ @client_addr = client
268
+ yield @sock_for_fork = sock
269
+ end
270
+ end
271
+ rescue Errno::EADDRINUSE
272
+ if retry_cnt < 10
273
+ retry_cnt += 1
274
+ sleep 0.1
275
+ retry
276
+ else
277
+ raise
240
278
  end
279
+ rescue Terminate
280
+ # OK
281
+ rescue => e
282
+ $stderr.puts e.inspect, e.message
283
+ pp e.backtrace
284
+ exit
241
285
  end
242
- rescue => e
243
- $stderr.puts e.message
244
- pp e.backtrace
245
- exit
286
+ ensure
287
+ @sock_for_fork = nil
246
288
  end
247
289
  end
248
290
 
@@ -250,14 +292,17 @@ module DEBUGGER__
250
292
  def initialize sock_dir: nil, sock_path: nil
251
293
  @sock_path = sock_path
252
294
  @sock_dir = sock_dir || DEBUGGER__.unix_domain_socket_dir
295
+ @sock_for_fork = nil
253
296
 
254
297
  super()
255
298
  end
256
299
 
257
300
  def accept
301
+ super # for fork
302
+
258
303
  case
259
304
  when @sock_path
260
- when sp = ::DEBUGGER__::CONFIG[:sock_path]
305
+ when sp = CONFIG[:sock_path]
261
306
  @sock_path = sp
262
307
  else
263
308
  @sock_path = DEBUGGER__.create_unix_domain_socket_name(@sock_dir)
@@ -265,10 +310,13 @@ module DEBUGGER__
265
310
 
266
311
  ::DEBUGGER__.warn "Debugger can attach via UNIX domain socket (#{@sock_path})"
267
312
  Socket.unix_server_loop @sock_path do |sock, client|
313
+ @sock_for_fork = sock
268
314
  @client_addr = client
315
+
269
316
  yield sock
270
317
  ensure
271
318
  sock.close
319
+ @sock_for_fork = nil
272
320
  end
273
321
  end
274
322
  end