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.
data/lib/debug/session.rb CHANGED
@@ -1,11 +1,15 @@
1
- require 'json'
2
- require 'pp'
3
- require 'debug_inspector'
4
- require 'iseq_collector'
1
+ module DEBUGGER__
2
+ # used in thread_client.c
3
+ FrameInfo = Struct.new(:location, :self, :binding, :iseq, :class, :frame_depth,
4
+ :has_return_value, :return_value, :show_line)
5
+ end
6
+
7
+ require_relative 'debug.so'
5
8
 
6
9
  require_relative 'source_repository'
7
10
  require_relative 'breakpoint'
8
11
  require_relative 'thread_client'
12
+ require_relative 'config'
9
13
 
10
14
  class RubyVM::InstructionSequence
11
15
  def traceable_lines_norec lines
@@ -39,6 +43,10 @@ class RubyVM::InstructionSequence
39
43
  def locals
40
44
  self.to_a[10]
41
45
  end
46
+
47
+ def last_line
48
+ self.to_a[4][:code_location][2]
49
+ end
42
50
  end
43
51
 
44
52
  module DEBUGGER__
@@ -46,12 +54,18 @@ module DEBUGGER__
46
54
  def initialize ui
47
55
  @ui = ui
48
56
  @sr = SourceRepository.new
49
- @reserved_bps = []
50
- @bps = {} # [file, line] => LineBreakpoint || "Error" => CatchBreakpoint
57
+ @bps = {} # bp.key => bp
58
+ # [file, line] => LineBreakpoint
59
+ # "Error" => CatchBreakpoint
60
+ # Method => MethodBreakpoint
61
+ # [:watch, expr] => WatchExprBreakpoint
62
+ # [:check, expr] => CheckBreakpoint
51
63
  @th_clients = {} # {Thread => ThreadClient}
52
64
  @q_evt = Queue.new
53
65
  @displays = []
54
66
  @tc = nil
67
+ @tc_id = 0
68
+ @initial_commands = []
55
69
 
56
70
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
57
71
  ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
@@ -69,13 +83,53 @@ module DEBUGGER__
69
83
  iseq, src = ev_args
70
84
  on_load iseq, src
71
85
  tc << :continue
86
+ when :thread_begin
87
+ th = ev_args.shift
88
+ on_thread_begin th
89
+ tc << :continue
72
90
  when :suspend
91
+ case ev_args.first
92
+ when :breakpoint
93
+ bp, i = bp_index ev_args[1]
94
+ if bp
95
+ @ui.puts "\nStop by \##{i} #{bp}"
96
+ end
97
+ when :trap
98
+ @ui.puts ''
99
+ @ui.puts "\nStop by #{ev_args[1]}"
100
+ end
101
+
73
102
  if @displays.empty?
74
103
  wait_command_loop tc
75
104
  else
76
105
  tc << [:eval, :display, @displays]
77
106
  end
78
107
  when :result
108
+ case ev_args.first
109
+ when :watch
110
+ bp = ev_args[1]
111
+ @bps[bp.key] = bp
112
+ show_bps bp
113
+ when :try_display
114
+ failed_results = ev_args[1]
115
+ if failed_results.size > 0
116
+ i, msg = failed_results.last
117
+ if i+1 == @displays.size
118
+ @ui.puts "canceled: #{@displays.pop}"
119
+ end
120
+ end
121
+ when :method_breakpoint
122
+ bp = ev_args[1]
123
+ if bp
124
+ @bps[bp.key] = bp
125
+ show_bps bp
126
+ else
127
+ # can't make a bp
128
+ end
129
+ else
130
+ # ignore
131
+ end
132
+
79
133
  wait_command_loop tc
80
134
  end
81
135
  end
@@ -85,6 +139,17 @@ module DEBUGGER__
85
139
  @management_threads << @ui.reader_thread if @ui.respond_to? :reader_thread
86
140
 
87
141
  setup_threads
142
+
143
+ @tp_thread_begin = TracePoint.new(:thread_begin){|tp|
144
+ ThreadClient.current.on_thread_begin Thread.current
145
+ }.enable
146
+ end
147
+
148
+ def add_initial_commands cmds
149
+ cmds.each{|c|
150
+ c.gsub('#.*', '').strip!
151
+ @initial_commands << c unless c.empty?
152
+ }
88
153
  end
89
154
 
90
155
  def source path
@@ -109,10 +174,17 @@ module DEBUGGER__
109
174
  retry
110
175
  end
111
176
  end
177
+ ensure
178
+ @tc = nil
112
179
  end
113
180
 
114
181
  def wait_command
115
- line = @ui.readline
182
+ if @initial_commands.empty?
183
+ line = @ui.readline
184
+ else
185
+ line = @initial_commands.shift.strip
186
+ @ui.puts "(rdbg:init) #{line}"
187
+ end
116
188
 
117
189
  if line.empty?
118
190
  if @repl_prev_line
@@ -130,40 +202,83 @@ module DEBUGGER__
130
202
  # p cmd: [cmd, *arg]
131
203
 
132
204
  case cmd
205
+ ### Control flow
133
206
 
134
- # control
207
+ # * `s[tep]`
208
+ # * Step in. Resume the program until next breakable point.
135
209
  when 's', 'step'
136
210
  @tc << [:step, :in]
211
+
212
+ # * `n[ext]`
213
+ # * Step over. Resume the program until next line.
137
214
  when 'n', 'next'
138
215
  @tc << [:step, :next]
216
+
217
+ # * `fin[ish]`
218
+ # * Finish this frame. Resume the program until the current frame is finished.
139
219
  when 'fin', 'finish'
140
220
  @tc << [:step, :finish]
221
+
222
+ # * `c[ontinue]`
223
+ # * Resume the program.
141
224
  when 'c', 'continue'
142
225
  @tc << :continue
143
- when 'q', 'quit'
226
+
227
+ # * `q[uit]` or exit or `Ctrl-D`
228
+ # * Finish debugger (with the debuggee process on non-remote debugging).
229
+ when 'q', 'quit', 'exit'
144
230
  if ask 'Really quit?'
145
231
  @ui.quit arg.to_i
146
232
  @tc << :continue
147
233
  else
148
234
  return :retry
149
235
  end
150
- when 'kill'
151
- if ask 'Really quit?'
236
+
237
+ # * `kill` or `q[uit]!`
238
+ # * Stop the debuggee process.
239
+ when 'kill', 'quit!', 'q!'
240
+ if ask 'Really kill?'
152
241
  exit! (arg || 1).to_i
153
242
  else
154
243
  return :retry
155
244
  end
156
245
 
157
- # breakpoints
246
+ ### Breakpoint
247
+
248
+ # * `b[reak]`
249
+ # * Show all breakpoints.
250
+ # * `b[reak] <line>`
251
+ # * Set breakpoint on `<line>` at the current frame's file.
252
+ # * `b[reak] <file>:<line>` or `<file> <line>`
253
+ # * Set breakpoint on `<file>:<line>`.
254
+ # * `b[reak] <class>#<name>`
255
+ # * Set breakpoint on the method `<class>#<name>`.
256
+ # * `b[reak] <expr>.<name>`
257
+ # * Set breakpoint on the method `<expr>.<name>`.
258
+ # * `b[reak] ... if <expr>`
259
+ # * break if `<expr>` is true at specified location.
260
+ # * `b[reak] if <expr>`
261
+ # * break if `<expr>` is true at any lines.
262
+ # * Note that this feature is super slow.
158
263
  when 'b', 'break'
159
264
  if arg == nil
160
265
  show_bps
266
+ return :retry
161
267
  else
162
- bp = repl_add_breakpoint arg
163
- show_bps bp if bp
268
+ case bp = repl_add_breakpoint(arg)
269
+ when :noretry
270
+ when nil
271
+ return :retry
272
+ else
273
+ show_bps bp
274
+ return :retry
275
+ end
164
276
  end
165
- return :retry
277
+
278
+ # skip
166
279
  when 'bv'
280
+ require 'json'
281
+
167
282
  h = Hash.new{|h, k| h[k] = []}
168
283
  @bps.each{|key, bp|
169
284
  if LineBreakpoint === bp
@@ -184,12 +299,33 @@ module DEBUGGER__
184
299
  end
185
300
 
186
301
  return :retry
302
+
303
+ # * `catch <Error>`
304
+ # * Set breakpoint on raising `<Error>`.
187
305
  when 'catch'
188
306
  if arg
189
307
  bp = add_catch_breakpoint arg
190
308
  show_bps bp if bp
309
+ else
310
+ show_bps
191
311
  end
192
312
  return :retry
313
+
314
+ # * `watch <expr>`
315
+ # * Stop the execution when the result of <expr> is changed.
316
+ # * Note that this feature is super slow.
317
+ when 'wat', 'watch'
318
+ if arg
319
+ @tc << [:eval, :watch, arg]
320
+ else
321
+ show_bps
322
+ return :retry
323
+ end
324
+
325
+ # * `del[ete]`
326
+ # * delete all breakpoints.
327
+ # * `del[ete] <bpnum>`
328
+ # * delete specified breakpoint.
193
329
  when 'del', 'delete'
194
330
  bp =
195
331
  case arg
@@ -206,42 +342,84 @@ module DEBUGGER__
206
342
  @ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp
207
343
  return :retry
208
344
 
209
- # evaluate
210
- when 'p'
211
- @tc << [:eval, :p, arg.to_s]
212
- when 'pp'
213
- @tc << [:eval, :pp, arg.to_s]
214
- when 'e', 'eval', 'call'
215
- @tc << [:eval, :call, arg]
216
- when 'irb'
217
- @tc << [:eval, :call, 'binding.irb']
345
+ ### Information
218
346
 
219
- # evaluate/frame selector
220
- when 'up'
221
- @tc << [:frame, :up]
222
- when 'down'
223
- @tc << [:frame, :down]
224
- when 'frame', 'f'
225
- @tc << [:frame, :set, arg]
226
-
227
- # information
347
+ # * `bt` or `backtrace`
348
+ # * Show backtrace (frame) information.
228
349
  when 'bt', 'backtrace'
229
350
  @tc << [:show, :backtrace]
230
- when 'list'
231
- @tc << [:show, :list]
232
- when 'info'
233
- case arg
234
- when 'l', 'local', 'locals'
235
- @tc << [:show, :locals]
236
- when 'i', 'instance', 'ivars'
237
- @tc << [:show, :ivars]
351
+
352
+ # * `l[ist]`
353
+ # * Show current frame's source code.
354
+ # * Next `list` command shows the successor lines.
355
+ # * `l[ist] -`
356
+ # * Show predecessor lines as opposed to the `list` command.
357
+ # * `l[ist] <start>` or `l[ist] <start>-<end>`
358
+ # * Show current frame's source code from the line <start> to <end> if given.
359
+ when 'l', 'list'
360
+ case arg ? arg.strip : nil
361
+ when /\A(\d+)\z/
362
+ @tc << [:show, :list, {start_line: arg.to_i - 1}]
363
+ when /\A-\z/
364
+ @tc << [:show, :list, {dir: -1}]
365
+ when /\A(\d+)-(\d+)\z/
366
+ @tc << [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
367
+ when nil
368
+ @tc << [:show, :list]
238
369
  else
239
- @ui.puts "unknown info argument: #{arg}"
370
+ @ui.puts "Can not handle list argument: #{arg}"
240
371
  return :retry
241
372
  end
373
+
374
+ # * `edit`
375
+ # * Open the current file on the editor (use `EDITOR` environment variable).
376
+ # * Note that editted file will not be reloaded.
377
+ # * `edit <file>`
378
+ # * Open <file> on the editor.
379
+ when 'edit'
380
+ if @ui.remote?
381
+ @ui.puts "not supported on the remote console."
382
+ return :retry
383
+ end
384
+
385
+ begin
386
+ arg = resolve_path(arg) if arg
387
+ rescue Errno::ENOENT
388
+ @ui.puts "not found: #{arg}"
389
+ return :retry
390
+ end
391
+
392
+ @tc << [:show, :edit, arg]
393
+
394
+ # * `i[nfo]`
395
+ # * Show information about the current frame (local variables)
396
+ # * It includes `self` as `%self` and a return value as `%return`.
397
+ # * `i[nfo] <expr>`
398
+ # * Show information about the result of <expr>.
399
+ when 'i', 'info'
400
+ case arg
401
+ when nil
402
+ @tc << [:show, :local]
403
+ else
404
+ @tc << [:show, :object_info, arg]
405
+ end
406
+
407
+ # * `display`
408
+ # * Show display setting.
409
+ # * `display <expr>`
410
+ # * Show the result of `<expr>` at every suspended timing.
242
411
  when 'display'
243
- @displays << arg if arg && !arg.empty?
244
- @tc << [:eval, :display, @displays]
412
+ if arg && !arg.empty?
413
+ @displays << arg
414
+ @tc << [:eval, :try_display, @displays]
415
+ else
416
+ @tc << [:eval, :display, @displays]
417
+ end
418
+
419
+ # * `undisplay`
420
+ # * Remove all display settings.
421
+ # * `undisplay <displaynum>`
422
+ # * Remove a specified display setting.
245
423
  when 'undisplay'
246
424
  case arg
247
425
  when /(\d+)/
@@ -258,12 +436,14 @@ module DEBUGGER__
258
436
  end
259
437
  return :retry
260
438
 
261
- # trace
439
+ # * `trace [on|off]`
440
+ # * enable or disable line tracer.
262
441
  when 'trace'
263
442
  case arg
264
443
  when 'on'
444
+ dir = __dir__
265
445
  @tracer ||= TracePoint.new(){|tp|
266
- next if tp.path == __FILE__
446
+ next if File.dirname(tp.path) == dir
267
447
  next if tp.path == '<internal:trace_point>'
268
448
  # next if tp.event != :line
269
449
  @ui.puts pretty_tp(tp)
@@ -271,13 +451,62 @@ module DEBUGGER__
271
451
  @tracer.enable
272
452
  when 'off'
273
453
  @tracer && @tracer.disable
274
- else
275
- enabled = (@tracer && @tracer.enabled?) ? true : false
276
- @ui.puts "Trace #{enabled ? 'on' : 'off'}"
277
454
  end
455
+ enabled = (@tracer && @tracer.enabled?) ? true : false
456
+ @ui.puts "Trace #{enabled ? 'on' : 'off'}"
278
457
  return :retry
279
458
 
280
- # threads
459
+ ### Frame control
460
+
461
+ # * `f[rame]`
462
+ # * Show current frame.
463
+ # * `f[rame] <framenum>`
464
+ # * Specify frame. Evaluation are run on this frame environement.
465
+ when 'frame', 'f'
466
+ @tc << [:frame, :set, arg]
467
+
468
+ # * `up`
469
+ # * Specify upper frame.
470
+ when 'up'
471
+ @tc << [:frame, :up]
472
+
473
+ # * `down`
474
+ # * Specify down frame.
475
+ when 'down'
476
+ @tc << [:frame, :down]
477
+
478
+ ### Evaluate
479
+
480
+ # * `p <expr>`
481
+ # * Evaluate like `p <expr>` on the current frame.
482
+ when 'p'
483
+ @tc << [:eval, :p, arg.to_s]
484
+
485
+ # * `pp <expr>`
486
+ # * Evaluate like `pp <expr>` on the current frame.
487
+ when 'pp'
488
+ @tc << [:eval, :pp, arg.to_s]
489
+
490
+ # * `e[val] <expr>`
491
+ # * Evaluate `<expr>` on the current frame.
492
+ when 'e', 'eval', 'call'
493
+ @tc << [:eval, :call, arg]
494
+
495
+ # * `irb`
496
+ # * Invoke `irb` on the current frame.
497
+ when 'irb'
498
+ if @ui.remote?
499
+ @ui.puts "not supported on the remote console."
500
+ return :retry
501
+ end
502
+ @tc << [:eval, :call, 'binding.irb']
503
+
504
+ ### Thread control
505
+
506
+ # * `th[read]`
507
+ # * Show all threads.
508
+ # * `th[read] <thnum>`
509
+ # * Switch thread specified by `<thnum>`.
281
510
  when 'th', 'thread'
282
511
  case arg
283
512
  when nil, 'list', 'l'
@@ -289,6 +518,21 @@ module DEBUGGER__
289
518
  end
290
519
  return :retry
291
520
 
521
+ ### Help
522
+
523
+ # * `h[elp]`
524
+ # * Show help for all commands.
525
+ # * `h[elp] <command>`
526
+ # * Show help for the given command.
527
+ when 'h', 'help'
528
+ if arg
529
+ show_help arg
530
+ else
531
+ @ui.puts DEBUGGER__.help
532
+ end
533
+ return :retry
534
+
535
+ ### END
292
536
  else
293
537
  @ui.puts "unknown command: #{line}"
294
538
  @repl_prev_line = nil
@@ -305,6 +549,18 @@ module DEBUGGER__
305
549
  return :retry
306
550
  end
307
551
 
552
+ def show_help arg
553
+ DEBUGGER__.helps.each{|cat, cs|
554
+ cs.each{|ws, desc|
555
+ if ws.include? arg
556
+ @ui.puts desc
557
+ return
558
+ end
559
+ }
560
+ }
561
+ @ui.puts "not found: #{arg}"
562
+ end
563
+
308
564
  def ask msg, default = 'Y'
309
565
  opts = '[y/n]'.tr(default.downcase, default)
310
566
  input = @ui.ask("#{msg} #{opts} ")
@@ -363,35 +619,84 @@ module DEBUGGER__
363
619
  exit!
364
620
  end
365
621
 
366
- def show_bps specified_bp = nil
367
- @bps.each_with_index{|(key, bp), i|
368
- if !specified_bp || bp == specified_bp
369
- @ui.puts "#%d %s" % [i, bp.to_s]
622
+ def iterate_bps
623
+ deleted_bps = []
624
+ i = 0
625
+ @bps.each{|key, bp|
626
+ if !bp.deleted?
627
+ yield key, bp, i
628
+ i += 1
629
+ else
630
+ deleted_bps << bp
370
631
  end
371
632
  }
633
+ ensure
634
+ deleted_bps.each{|bp| @bps.delete bp}
372
635
  end
373
636
 
374
- def thread_list
375
- thcs, unmanaged_ths = update_thread_list
376
- thcs.each_with_index{|thc, i|
377
- @ui.puts "#{@tc == thc ? "--> " : " "}\##{i} #{thc}"
378
- }
637
+ def show_bps specific_bp = nil
638
+ iterate_bps do |key, bp, i|
639
+ @ui.puts "#%d %s" % [i, bp.to_s] if !specific_bp || bp == specific_bp
640
+ end
641
+ end
379
642
 
380
- if !unmanaged_ths.empty?
381
- @ui.puts "The following threads are not managed yet by the debugger:"
382
- unmanaged_ths.each{|th|
383
- @ui.puts " " + th.to_s
384
- }
643
+ def bp_index specific_bp_key
644
+ iterate_bps do |key, bp, i|
645
+ if key == specific_bp_key
646
+ return [bp, i]
647
+ end
385
648
  end
649
+ nil
386
650
  end
387
651
 
388
- def thread_switch n
389
- if th = @th_clients.keys[n]
390
- @tc = @th_clients[th]
652
+ def delete_breakpoint arg = nil
653
+ case arg
654
+ when nil
655
+ @bps.each{|key, bp| bp.delete}
656
+ @bps.clear
657
+ else
658
+ del_bp = nil
659
+ iterate_bps{|key, bp, i| del_bp = bp if i == arg}
660
+ if del_bp
661
+ del_bp.delete
662
+ @bps.delete del_bp.key
663
+ return [arg, del_bp]
664
+ end
665
+ end
666
+ end
667
+
668
+ def repl_add_breakpoint arg
669
+ arg.strip!
670
+
671
+ case arg
672
+ when /\Aif\s+(.+)\z/
673
+ cond = $1
674
+ when /(.+?)\s+if\s+(.+)\z/
675
+ sig = $1
676
+ cond = $2
677
+ else
678
+ sig = arg
679
+ end
680
+
681
+ case sig
682
+ when /\A(\d+)\z/
683
+ add_line_breakpoint @tc.location.path, $1.to_i, cond: cond
684
+ when /\A(.+)[:\s+](\d+)\z/
685
+ add_line_breakpoint $1, $2.to_i, cond: cond
686
+ when /\A(.+)([\.\#])(.+)\z/
687
+ @tc << [:breakpoint, :method, $1, $2, $3, cond]
688
+ return :noretry
689
+ when nil
690
+ add_check_breakpoint cond
691
+ else
692
+ @ui.puts "Unknown breakpoint format: #{arg}"
693
+ @ui.puts
694
+ show_help 'b'
391
695
  end
392
- thread_list
393
696
  end
394
697
 
698
+ # threads
699
+
395
700
  def update_thread_list
396
701
  list = Thread.list
397
702
  thcs = []
@@ -407,60 +712,64 @@ module DEBUGGER__
407
712
  unmanaged << th
408
713
  end
409
714
  }
410
- return thcs, unmanaged
715
+ return thcs.sort_by{|thc| thc.id}, unmanaged
411
716
  end
412
717
 
413
- def delete_breakpoint arg = nil
414
- case arg
415
- when nil
416
- @bps.each{|key, bp| bp.disable}
417
- @bps.clear
418
- else
419
- if bp = @bps[key = @bps.keys[arg]]
420
- bp.disable
421
- @bps.delete key
422
- return [arg, bp]
423
- end
718
+ def thread_list
719
+ thcs, unmanaged_ths = update_thread_list
720
+ thcs.each_with_index{|thc, i|
721
+ @ui.puts "#{@tc == thc ? "--> " : " "}\##{i} #{thc}"
722
+ }
723
+
724
+ if !unmanaged_ths.empty?
725
+ @ui.puts "The following threads are not managed yet by the debugger:"
726
+ unmanaged_ths.each{|th|
727
+ @ui.puts " " + th.to_s
728
+ }
424
729
  end
425
730
  end
426
731
 
427
- def repl_add_breakpoint arg
428
- arg.strip!
429
-
430
- if /(.+?)\s+if\s+(.+)\z/ =~ arg
431
- sig = $1
432
- cond = $2
433
- else
434
- sig = arg
435
- end
732
+ def thread_switch n
733
+ thcs, unmanaged_ths = update_thread_list
436
734
 
437
- case sig
438
- when /\A(\d+)\z/
439
- add_line_breakpoint @tc.location.path, $1.to_i, cond
440
- when /\A(.+):(\d+)\z/
441
- add_line_breakpoint $1, $2.to_i, cond
442
- when /\A(.+)[\.\#](.+)\z/
443
- add_method_breakpoint arg, cond
444
- else
445
- raise "unknown breakpoint format: #{arg}"
735
+ if tc = thcs[n]
736
+ if tc.mode
737
+ @tc = tc
738
+ else
739
+ @ui.puts "#{tc.thread} is not controllable yet."
740
+ end
446
741
  end
742
+ thread_list
447
743
  end
448
744
 
449
- def break? file, line
450
- @bps.has_key? [file, line]
745
+ def thread_client_create th
746
+ @th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th)
451
747
  end
452
748
 
453
749
  def setup_threads
454
750
  stop_all_threads do
455
751
  Thread.list.each{|th|
456
- @th_clients[th] = ThreadClient.new(@q_evt, Queue.new, th)
752
+ thread_client_create(th)
457
753
  }
458
754
  end
459
755
  end
460
756
 
757
+ def on_thread_begin th
758
+ if @th_clients.has_key? th
759
+ # OK
760
+ else
761
+ # TODO: NG?
762
+ thread_client_create th
763
+ end
764
+ end
765
+
461
766
  def thread_client
462
767
  thr = Thread.current
463
- @th_clients[thr] ||= ThreadClient.new(@q_evt, Queue.new)
768
+ if @th_clients.has_key? thr
769
+ @th_clients[thr]
770
+ else
771
+ @th_clients[thr] = thread_client_create(thr)
772
+ end
464
773
  end
465
774
 
466
775
  def stop_all_threads
@@ -496,99 +805,120 @@ module DEBUGGER__
496
805
 
497
806
  def on_load iseq, src
498
807
  @sr.add iseq, src
499
- @reserved_bps.each{|(path, line, cond)|
500
- if path == iseq.absolute_path
501
- add_line_breakpoint(path, line, cond)
808
+
809
+ pending_line_breakpoints do |bp|
810
+ if bp.path == (iseq.absolute_path || iseq.path)
811
+ bp.try_activate
502
812
  end
813
+ end
814
+ end
815
+
816
+ # breakpoint management
817
+
818
+ def add_breakpoint bp
819
+ if @bps.has_key? bp.key
820
+ @ui.puts "duplicated breakpoint: #{bp}"
821
+ else
822
+ @bps[bp.key] = bp
823
+ end
824
+ end
825
+
826
+ def rehash_bps
827
+ bps = @bps.values
828
+ @bps.clear
829
+ bps.each{|bp|
830
+ add_breakpoint bp
503
831
  }
504
832
  end
505
833
 
506
- # configuration
834
+ def break? file, line
835
+ @bps.has_key? [file, line]
836
+ end
507
837
 
508
838
  def add_catch_breakpoint arg
509
839
  bp = CatchBreakpoint.new(arg)
510
- @bps[bp.key] = bp
511
- bp
840
+ add_braekpoint bp
512
841
  end
513
842
 
514
- def add_line_breakpoint_exact iseq, events, file, line, cond
515
- if @bps[[file, line]]
516
- return nil # duplicated
517
- end
518
-
519
- bp = case
520
- when events.include?(:RUBY_EVENT_CALL)
521
- # "def foo" line set bp on the beggining of method foo
522
- LineBreakpoint.new(:call, iseq, line, cond)
523
- when events.include?(:RUBY_EVENT_LINE)
524
- LineBreakpoint.new(:line, iseq, line, cond)
525
- when events.include?(:RUBY_EVENT_RETURN)
526
- LineBreakpoint.new(:return, iseq, line, cond)
527
- when events.include?(:RUBY_EVENT_B_RETURN)
528
- LineBreakpoint.new(:b_return, iseq, line, cond)
529
- when events.include?(:RUBY_EVENT_END)
530
- LineBreakpoint.new(:end, iseq, line, cond)
531
- else
532
- nil
533
- end
534
- @bps[bp.key] = bp if bp
843
+ def add_check_breakpoint expr
844
+ bp = CheckBreakpoint.new(expr)
845
+ add_breakpoint bp
535
846
  end
536
847
 
537
- NearestISeq = Struct.new(:iseq, :line, :events)
538
-
539
- def add_line_breakpoint_nearest file, line, cond
540
- nearest = nil # NearestISeq
848
+ def resolve_path file
849
+ File.realpath(File.expand_path(file))
850
+ rescue Errno::ENOENT
851
+ return file if file == '-e'
852
+ raise
853
+ end
541
854
 
542
- ObjectSpace.each_iseq{|iseq|
543
- if iseq.absolute_path == file && iseq.first_lineno <= line
544
- iseq.traceable_lines_norec(line_events = {})
545
- lines = line_events.keys.sort
855
+ def add_line_breakpoint file, line, **kw
856
+ file = resolve_path(file)
857
+ bp = LineBreakpoint.new(file, line, **kw)
858
+ add_breakpoint bp
859
+ rescue Errno::ENOENT => e
860
+ @ui.puts e.message
861
+ end
546
862
 
547
- if !lines.empty? && lines.last >= line
548
- nline = lines.bsearch{|l| line <= l}
549
- events = line_events[nline]
863
+ def pending_line_breakpoints
864
+ @bps.find_all do |key, bp|
865
+ LineBreakpoint === bp && !bp.iseq
866
+ end.each do |key, bp|
867
+ yield bp
868
+ end
869
+ end
550
870
 
551
- if !nearest
552
- nearest = NearestISeq.new(iseq, nline, events)
553
- else
554
- if nearest.iseq.first_lineno <= iseq.first_lineno
555
- if (nearest.line > line && !nearest.events.include?(:RUBY_EVENT_CALL)) ||
556
- events.include?(:RUBY_EVENT_CALL)
557
- nearest = NearestISeq.new(iseq, nline, events)
558
- end
871
+ def method_added tp
872
+ b = tp.binding
873
+ if var_name = b.local_variables.first
874
+ mid = b.local_variable_get(var_name)
875
+ found = false
876
+
877
+ @bps.each{|k, bp|
878
+ case bp
879
+ when MethodBreakpoint
880
+ if bp.method.nil?
881
+ found = true
882
+ if bp.sig_method_name == mid.to_s
883
+ bp.enable(quiet: true)
559
884
  end
560
885
  end
561
886
  end
887
+ }
888
+ unless found
889
+ METHOD_ADDED_TRACKER.disable
562
890
  end
563
- }
564
-
565
- if nearest
566
- add_line_breakpoint_exact nearest.iseq, nearest.events, file, nearest.line, cond
567
- else
568
- return nil
569
891
  end
570
892
  end
893
+ end
571
894
 
572
- def resolve_path file
573
- File.realpath(File.expand_path(file))
574
- rescue Errno::ENOENT
575
- file
576
- end
895
+ # String for requring location
896
+ # nil for -r
897
+ def self.require_location
898
+ locs = caller_locations
899
+ dir_prefix = /#{__dir__}/
577
900
 
578
- def add_line_breakpoint file, line, cond = nil
579
- file = resolve_path(file)
580
- bp = add_line_breakpoint_nearest file, line, cond
581
- @reserved_bps << [file, line, cond] unless bp
582
- bp
901
+ locs.each do |loc|
902
+ case loc.absolute_path
903
+ when dir_prefix
904
+ when %r{rubygems/core_ext/kernel_require\.rb}
905
+ else
906
+ return loc
907
+ end
583
908
  end
909
+ nil
910
+ end
584
911
 
585
- def add_method_breakpoint signature
586
- raise
587
- end
912
+ def self.console
913
+ initialize_session UI_Console.new
914
+
915
+ @prev_handler = trap(:SIGINT){
916
+ ThreadClient.current.on_trap :SIGINT
917
+ }
588
918
  end
589
919
 
590
- def self.add_line_breakpoint file, line, if: if_not_given = true
591
- ::DEBUGGER__::SESSION.add_line_breakpoint file, line, if_not_given ? nil : binding.local_variable_get(:if)
920
+ def self.add_line_breakpoint file, line, **kw
921
+ ::DEBUGGER__::SESSION.add_line_breakpoint file, line, **kw
592
922
  end
593
923
 
594
924
  def self.add_catch_breakpoint pat
@@ -600,12 +930,117 @@ module DEBUGGER__
600
930
  ::DEBUGGER__.const_set(:SESSION, Session.new(ui))
601
931
 
602
932
  # default breakpoints
603
- ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
933
+
934
+ # ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
604
935
 
605
936
  Binding.module_eval do
606
937
  ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1
607
938
  def bp; nil; end
608
939
  end
940
+
941
+ if ::DEBUGGER__::CONFIG[:nonstop] != '1'
942
+ if loc = ::DEBUGGER__.require_location
943
+ # require 'debug/console' or 'debug'
944
+ add_line_breakpoint loc.absolute_path, loc.lineno + 1, oneshot: true, hook_call: false
945
+ else
946
+ # -r
947
+ add_line_breakpoint $0, 1, oneshot: true, hook_call: false
948
+ end
949
+ end
950
+
951
+ load_rc
952
+ end
953
+ end
954
+
955
+ def self.load_rc
956
+ ['./rdbgrc.rb', File.expand_path('~/.rdbgrc.rb')].each{|path|
957
+ if File.file? path
958
+ load path
959
+ end
960
+ }
961
+
962
+ # debug commands file
963
+ [::DEBUGGER__::CONFIG[:init_script],
964
+ './.rdbgrc',
965
+ File.expand_path('~/.rdbgrc')].each{|path|
966
+ next unless path
967
+
968
+ if File.file? path
969
+ ::DEBUGGER__::SESSION.add_initial_commands File.readlines(path)
970
+ end
971
+ }
972
+
973
+ # given debug commands
974
+ if ::DEBUGGER__::CONFIG[:commands]
975
+ cmds = ::DEBUGGER__::CONFIG[:commands].split(';;')
976
+ ::DEBUGGER__::SESSION.add_initial_commands cmds
977
+ end
978
+ end
979
+
980
+ def self.parse_help
981
+ helps = Hash.new{|h, k| h[k] = []}
982
+ desc = cat = nil
983
+ cmds = []
984
+
985
+ File.read(__FILE__).each_line do |line|
986
+ case line
987
+ when /\A\s*### (.+)/
988
+ cat = $1
989
+ break if $1 == 'END'
990
+ when /\A when (.+)/
991
+ next unless cat
992
+ next unless desc
993
+ ws = $1.split(/,\s*/).map{|e| e.gsub('\'', '')}
994
+ helps[cat] << [ws, desc]
995
+ desc = nil
996
+ cmds.concat ws
997
+ when /\A\s+# (\s*\*.+)/
998
+ if desc
999
+ desc << "\n" + $1
1000
+ else
1001
+ desc = $1
1002
+ end
1003
+ end
609
1004
  end
1005
+ @commands = cmds
1006
+ @helps = helps
610
1007
  end
1008
+
1009
+ def self.helps
1010
+ (defined?(@helps) && @helps) || parse_help
1011
+ end
1012
+
1013
+ def self.commands
1014
+ (defined?(@commands) && @commands) || (parse_help; @commands)
1015
+ end
1016
+
1017
+ def self.help
1018
+ r = []
1019
+ self.helps.each{|cat, cmds|
1020
+ r << "### #{cat}"
1021
+ r << ''
1022
+ cmds.each{|ws, desc|
1023
+ r << desc
1024
+ }
1025
+ r << ''
1026
+ }
1027
+ r.join("\n")
1028
+ end
1029
+
1030
+ CONFIG = ::DEBUGGER__.parse_argv(ENV['RUBY_DEBUG_OPT'])
1031
+
1032
+ class ::Module
1033
+ def method_added mid; end
1034
+ def singleton_method_added mid; end
1035
+ end
1036
+
1037
+ def self.method_added tp
1038
+ begin
1039
+ SESSION.method_added tp
1040
+ rescue Exception => e
1041
+ p e
1042
+ end
1043
+ end
1044
+
1045
+ METHOD_ADDED_TRACKER = self.create_method_added_tracker
611
1046
  end