debug 1.7.1 → 1.7.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a84e87e9582c98cf24ca8aa9f1f30032c10736bf4dbc1c44da016db564a7a574
4
- data.tar.gz: da093ef6ebfd8fc5e4a10fcc39c59826e45829ec09da0d9dc164b9cd71085286
3
+ metadata.gz: 1bac91980fb350e0e37a0639612064bba25a8e441661a6d797e0d9f996ce0205
4
+ data.tar.gz: 3294757150ec0731ccadd0c26a57c8e920c0479e4a967b4900e999dae4afcf9c
5
5
  SHA512:
6
- metadata.gz: d663b6db0dba8921aaaa535f834e768367e07fef03613e6a277a27724fb75ecf830c7557465521998d42cbb41744e6335495958f626285468d6e38ae6ec52515
7
- data.tar.gz: cb24e62c5875c069812ff1b056dda3b912a20069d09123b72dbd0da69e61aed66968bc4dc5f80e9b599df743fb7755a2399f1461f344fc43ed9ea6bbf20f251c
6
+ metadata.gz: 7a0259c1f8fa904d9a425be5035e2642de89e69cd81cc61bd81bae2e9e7b17dbd18a78addce9e5ed8e3c7723417c0de1113a26922131760e490e77e91bb8493d
7
+ data.tar.gz: fd55333ccf690b4338729a2b84d55a21464ba2de71bdf5edcb0b82f309594477333224eb3be0a89c6b7c477042c4f904f8b743f74bba3931f6db4de7a9ff7dd9
data/CONTRIBUTING.md CHANGED
@@ -149,10 +149,10 @@ If the file already exists, **only method** will be added to it.
149
149
  ```ruby
150
150
  # frozen_string_literal: true
151
151
 
152
- require_relative '../support/test_case'
152
+ require_relative '../support/console_test_case'
153
153
 
154
154
  module DEBUGGER__
155
- class FooTest < TestCase
155
+ class FooTest < ConsoleTestCase
156
156
  def program
157
157
  <<~RUBY
158
158
  1| module Foo
data/README.md CHANGED
@@ -26,7 +26,7 @@ New debug.rb has several advantages:
26
26
  * Support threads (almost done) and ractors (TODO).
27
27
  * Support suspending and entering to the console debugging with `Ctrl-C` at most of timing.
28
28
  * Show parameters on backtrace command.
29
- * Support recording & reply debugging.
29
+ * Support recording & replay debugging.
30
30
 
31
31
  # Installation
32
32
 
@@ -563,9 +563,9 @@ The `<...>` notation means the argument.
563
563
  * `u[ntil]`
564
564
  * Similar to `next` command, but only stop later lines or the end of the current frame.
565
565
  * Similar to gdb's `advance` command.
566
- * `u[ntil] <[file:]line>
566
+ * `u[ntil] <[file:]line>`
567
567
  * Run til the program reaches given location or the end of the current frame.
568
- * `u[ntil] <name>
568
+ * `u[ntil] <name>`
569
569
  * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
570
570
  * `c` or `cont` or `continue`
571
571
  * Resume the program.
data/Rakefile CHANGED
@@ -35,9 +35,14 @@ task :check_readme do
35
35
  end
36
36
  end
37
37
 
38
+ desc "Run debug.gem test-framework tests"
39
+ Rake::TestTask.new(:test_test) do |t|
40
+ t.test_files = FileList["test/support/*_test.rb"]
41
+ end
42
+
38
43
  desc "Run all debugger console related tests"
39
44
  Rake::TestTask.new(:test_console) do |t|
40
- t.test_files = FileList["test/console/*_test.rb", "test/support/*_test.rb"]
45
+ t.test_files = FileList["test/console/*_test.rb"]
41
46
  end
42
47
 
43
48
  desc "Run all debugger protocols (CAP & DAP) related tests"
@@ -46,7 +51,7 @@ Rake::TestTask.new(:test_protocol) do |t|
46
51
  end
47
52
 
48
53
  task test: 'test_console' do
49
- warn '`rake test` doesn\'t run protocol tests. Use `rake test-all` to test all.'
54
+ warn '`rake test` doesn\'t run protocol tests. Use `rake test_all` to test all.'
50
55
  end
51
56
 
52
- task test_all: [:test_console, :test_protocol]
57
+ task test_all: [:test_test, :test_console, :test_protocol]
@@ -101,10 +101,6 @@ module DEBUGGER__
101
101
  def generate_label(name)
102
102
  colorize(" BP - #{name} ", [:YELLOW, :BOLD, :REVERSE])
103
103
  end
104
-
105
- def pending_until_load?
106
- false
107
- end
108
104
  end
109
105
 
110
106
  if RUBY_VERSION.to_f <= 2.7
@@ -163,10 +159,6 @@ module DEBUGGER__
163
159
  @pending = !@iseq
164
160
  end
165
161
 
166
- def pending_until_load?
167
- @pending
168
- end
169
-
170
162
  def setup
171
163
  return unless @type
172
164
 
@@ -207,6 +199,8 @@ module DEBUGGER__
207
199
  if @pending && !@oneshot
208
200
  DEBUGGER__.info "#{self} is activated."
209
201
  end
202
+
203
+ @pending = false
210
204
  end
211
205
 
212
206
  def activate_exact iseq, events, line
@@ -299,6 +293,10 @@ module DEBUGGER__
299
293
  def inspect
300
294
  "<#{self.class.name} #{self.to_s}>"
301
295
  end
296
+
297
+ def path_is? path
298
+ DEBUGGER__.compare_path(@path, path)
299
+ end
302
300
  end
303
301
 
304
302
  class CatchBreakpoint < Breakpoint
data/lib/debug/config.rb CHANGED
@@ -265,6 +265,8 @@ module DEBUGGER__
265
265
  require 'optparse'
266
266
  require_relative 'version'
267
267
 
268
+ have_shown_version = false
269
+
268
270
  opt = OptionParser.new do |o|
269
271
  o.banner = "#{$0} [options] -- [debuggee options]"
270
272
  o.separator ''
@@ -372,6 +374,16 @@ module DEBUGGER__
372
374
  o.separator ''
373
375
  o.separator 'Other options:'
374
376
 
377
+ o.on('-v', 'Show version number') do
378
+ puts o.ver
379
+ have_shown_version = true
380
+ end
381
+
382
+ o.on('--version', 'Show version number and exit') do
383
+ puts o.ver
384
+ exit
385
+ end
386
+
375
387
  o.on("-h", "--help", "Print help") do
376
388
  puts o
377
389
  exit
@@ -395,6 +407,14 @@ module DEBUGGER__
395
407
 
396
408
  opt.parse!(argv)
397
409
 
410
+ if argv.empty?
411
+ case
412
+ when have_shown_version && config[:mode] == :start
413
+ pp config
414
+ exit
415
+ end
416
+ end
417
+
398
418
  config
399
419
  end
400
420
 
@@ -425,8 +445,9 @@ module DEBUGGER__
425
445
  unless (dir_uid = fs.uid) == (uid = Process.uid)
426
446
  raise "#{path} uid is #{dir_uid}, but Process.uid is #{uid}"
427
447
  end
428
- unless (dir_mode = fs.mode) == 040700 # 4: dir, 7:rwx
429
- raise "#{path}'s mode is #{dir_mode.to_s(8)} (should be 040700)"
448
+
449
+ if fs.world_writable? && !fs.sticky?
450
+ raise "#{path} is world writable but not sticky"
430
451
  end
431
452
 
432
453
  path
data/lib/debug/server.rb CHANGED
@@ -406,14 +406,13 @@ module DEBUGGER__
406
406
  require_relative 'server_cdp'
407
407
 
408
408
  @uuid = SecureRandom.uuid
409
- unless @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr, @uuid)
410
- DEBUGGER__.warn <<~EOS
411
- With Chrome browser, type the following URL in the address-bar:
409
+ @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr, @uuid)
410
+ DEBUGGER__.warn <<~EOS
411
+ With Chrome browser, type the following URL in the address-bar:
412
412
 
413
- devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@local_addr.inspect_sockaddr}/#{@uuid}
413
+ devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@local_addr.inspect_sockaddr}/#{@uuid}
414
414
 
415
- EOS
416
- end
415
+ EOS
417
416
  end
418
417
 
419
418
  def accept
@@ -98,7 +98,6 @@ module DEBUGGER__
98
98
  candidates = ['C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe']
99
99
  path = get_chrome_path candidates
100
100
  end
101
- uuid = SecureRandom.uuid
102
101
  # The path is based on https://github.com/sindresorhus/open/blob/v8.4.0/index.js#L128.
103
102
  stdin, stdout, stderr, wait_thr = *Open3.popen3("#{ENV['SystemRoot']}\\System32\\WindowsPowerShell\\v1.0\\powershell")
104
103
  tf = Tempfile.create(['debug-', '.txt'])
@@ -450,11 +449,7 @@ module DEBUGGER__
450
449
  end
451
450
 
452
451
  def send_response req, **res
453
- if res.empty?
454
- @ws_server.send id: req['id'], result: {}
455
- else
456
- @ws_server.send id: req['id'], result: res
457
- end
452
+ @ws_server.send id: req['id'], result: res
458
453
  end
459
454
 
460
455
  def send_fail_response req, **res
@@ -462,11 +457,7 @@ module DEBUGGER__
462
457
  end
463
458
 
464
459
  def send_event method, **params
465
- if params.empty?
466
- @ws_server.send method: method, params: {}
467
- else
468
- @ws_server.send method: method, params: params
469
- end
460
+ @ws_server.send method: method, params: params
470
461
  end
471
462
 
472
463
  INVALID_REQUEST = -32600
@@ -557,6 +548,9 @@ module DEBUGGER__
557
548
  activate_bp bps
558
549
  end
559
550
  send_response req
551
+ when 'Debugger.pause'
552
+ send_response req
553
+ Process.kill(UI_ServerBase::TRAP_SIGNAL, Process.pid)
560
554
 
561
555
  # breakpoint
562
556
  when 'Debugger.getPossibleBreakpoints'
@@ -564,35 +558,31 @@ module DEBUGGER__
564
558
  when 'Debugger.setBreakpointByUrl'
565
559
  line = req.dig('params', 'lineNumber')
566
560
  if regexp = req.dig('params', 'urlRegex')
567
- path = regexp.match(/(.*)\|/)[1].gsub("\\", "")
568
- cond = req.dig('params', 'condition')
569
- src = get_source_code path
570
- end_line = src.lines.count
571
- line = end_line if line > end_line
572
561
  b_id = "1:#{line}:#{regexp}"
573
- if cond != ''
574
- SESSION.add_line_breakpoint(path, line + 1, cond: cond)
575
- else
576
- SESSION.add_line_breakpoint(path, line + 1)
577
- end
578
562
  bps[b_id] = bps.size
579
- # Because we need to return scriptId, responses are returned in SESSION thread.
580
- req['params']['scriptId'] = path
581
- req['params']['lineNumber'] = line
582
- req['params']['breakpointId'] = b_id
583
- @q_msg << req
563
+ path = regexp.match(/(.*)\|/)[1].gsub("\\", "")
564
+ add_line_breakpoint(req, b_id, path)
584
565
  elsif url = req.dig('params', 'url')
585
566
  b_id = "#{line}:#{url}"
586
- send_response req,
587
- breakpointId: b_id,
588
- locations: []
589
- elsif hash = req.dig('params', 'scriptHash')
590
- b_id = "#{line}:#{hash}"
591
- send_response req,
592
- breakpointId: b_id,
593
- locations: []
567
+ # When breakpoints are set in Script snippet, non-existent path such as "snippet:///Script%20snippet%20%231" sent.
568
+ # That's why we need to check it here.
569
+ if File.exist? url
570
+ bps[b_id] = bps.size
571
+ add_line_breakpoint(req, b_id, url)
572
+ else
573
+ send_response req,
574
+ breakpointId: b_id,
575
+ locations: []
576
+ end
594
577
  else
595
- raise 'Unsupported'
578
+ if hash = req.dig('params', 'scriptHash')
579
+ b_id = "#{line}:#{hash}"
580
+ send_response req,
581
+ breakpointId: b_id,
582
+ locations: []
583
+ else
584
+ raise 'Unsupported'
585
+ end
596
586
  end
597
587
  when 'Debugger.removeBreakpoint'
598
588
  b_id = req.dig('params', 'breakpointId')
@@ -631,6 +621,24 @@ module DEBUGGER__
631
621
  @q_msg << 'continue'
632
622
  end
633
623
 
624
+ def add_line_breakpoint req, b_id, path
625
+ cond = req.dig('params', 'condition')
626
+ line = req.dig('params', 'lineNumber')
627
+ src = get_source_code path
628
+ end_line = src.lines.count
629
+ line = end_line if line > end_line
630
+ if cond != ''
631
+ SESSION.add_line_breakpoint(path, line + 1, cond: cond)
632
+ else
633
+ SESSION.add_line_breakpoint(path, line + 1)
634
+ end
635
+ # Because we need to return scriptId, responses are returned in SESSION thread.
636
+ req['params']['scriptId'] = path
637
+ req['params']['lineNumber'] = line
638
+ req['params']['breakpointId'] = b_id
639
+ @q_msg << req
640
+ end
641
+
634
642
  def del_bp bps, k
635
643
  return bps unless idx = bps[k]
636
644
 
@@ -668,31 +676,20 @@ module DEBUGGER__
668
676
  def cleanup_reader
669
677
  super
670
678
  Process.kill :KILL, @chrome_pid if @chrome_pid
679
+ rescue Errno::ESRCH # continue if @chrome_pid process is not found
671
680
  end
672
681
 
673
682
  ## Called by the SESSION thread
674
683
 
675
- def respond req, **result
676
- send_response req, **result
677
- end
678
-
679
- def respond_fail req, **result
680
- send_fail_response req, **result
681
- end
682
-
683
- def fire_event event, **result
684
- if result.empty?
685
- send_event event
686
- else
687
- send_event event, **result
688
- end
689
- end
684
+ alias respond send_response
685
+ alias respond_fail send_fail_response
686
+ alias fire_event send_event
690
687
 
691
688
  def sock skip: false
692
689
  yield $stderr
693
690
  end
694
691
 
695
- def puts result
692
+ def puts result=''
696
693
  # STDERR.puts "puts: #{result}"
697
694
  # send_event 'output', category: 'stderr', output: "PUTS!!: " + result.to_s
698
695
  end
@@ -756,7 +753,11 @@ module DEBUGGER__
756
753
  request_tc [:cdp, :scope, req, fid]
757
754
  when 'global'
758
755
  vars = safe_global_variables.sort.map do |name|
759
- gv = eval(name.to_s)
756
+ begin
757
+ gv = eval(name.to_s)
758
+ rescue Errno::ENOENT
759
+ gv = nil
760
+ end
760
761
  prop = {
761
762
  name: name,
762
763
  value: {
@@ -836,7 +837,7 @@ module DEBUGGER__
836
837
  end
837
838
  end
838
839
 
839
- def cdp_event args
840
+ def process_protocol_result args
840
841
  type, req, result = args
841
842
 
842
843
  case type
@@ -1026,7 +1027,7 @@ module DEBUGGER__
1026
1027
  result[:data] = evaluate_result exception
1027
1028
  result[:reason] = 'exception'
1028
1029
  end
1029
- event! :cdp_result, :backtrace, req, result
1030
+ event! :protocol_result, :backtrace, req, result
1030
1031
  when :evaluate
1031
1032
  res = {}
1032
1033
  fid, expr, group = args
@@ -1071,7 +1072,7 @@ module DEBUGGER__
1071
1072
  begin
1072
1073
  orig_stdout = $stdout
1073
1074
  $stdout = StringIO.new
1074
- result = current_frame.binding.eval(expr.to_s, '(DEBUG CONSOLE)')
1075
+ result = b.eval(expr.to_s, '(DEBUG CONSOLE)')
1075
1076
  rescue Exception => e
1076
1077
  result = e
1077
1078
  res[:exceptionDetails] = exceptionDetails(e, 'Uncaught')
@@ -1087,7 +1088,7 @@ module DEBUGGER__
1087
1088
  end
1088
1089
 
1089
1090
  res[:result] = evaluate_result(result)
1090
- event! :cdp_result, :evaluate, req, message: message, response: res, output: output
1091
+ event! :protocol_result, :evaluate, req, message: message, response: res, output: output
1091
1092
  when :scope
1092
1093
  fid = args.shift
1093
1094
  frame = @target_frames[fid]
@@ -1110,7 +1111,7 @@ module DEBUGGER__
1110
1111
  vars.unshift variable(name, val)
1111
1112
  end
1112
1113
  end
1113
- event! :cdp_result, :scope, req, vars
1114
+ event! :protocol_result, :scope, req, vars
1114
1115
  when :properties
1115
1116
  oid = args.shift
1116
1117
  result = []
@@ -1152,14 +1153,14 @@ module DEBUGGER__
1152
1153
  }
1153
1154
  prop += [internalProperty('#class', M_CLASS.bind_call(obj))]
1154
1155
  end
1155
- event! :cdp_result, :properties, req, result: result, internalProperties: prop
1156
+ event! :protocol_result, :properties, req, result: result, internalProperties: prop
1156
1157
  when :exception
1157
1158
  oid = args.shift
1158
1159
  exc = nil
1159
1160
  if obj = @obj_map[oid]
1160
1161
  exc = exceptionDetails obj, obj.to_s
1161
1162
  end
1162
- event! :cdp_result, :exception, req, exceptionDetails: exc
1163
+ event! :protocol_result, :exception, req, exceptionDetails: exc
1163
1164
  end
1164
1165
  end
1165
1166
 
@@ -125,6 +125,7 @@ module DEBUGGER__
125
125
  def dap_setup bytes
126
126
  CONFIG.set_config no_color: true
127
127
  @seq = 0
128
+ @send_lock = Mutex.new
128
129
 
129
130
  case self
130
131
  when UI_UnixDomainServer
@@ -212,9 +213,13 @@ module DEBUGGER__
212
213
  if sock = @sock
213
214
  kw[:seq] = @seq += 1
214
215
  str = JSON.dump(kw)
215
- sock.write "Content-Length: #{str.bytesize}\r\n\r\n#{str}"
216
+ @send_lock.synchronize do
217
+ sock.write "Content-Length: #{str.bytesize}\r\n\r\n#{str}"
218
+ end
216
219
  show_protocol '<', str
217
220
  end
221
+ rescue Errno::EPIPE => e
222
+ $stderr.puts "#{e.inspect} rescued during sending message"
218
223
  end
219
224
 
220
225
  def send_response req, success: true, message: nil, **kw
@@ -246,17 +251,17 @@ module DEBUGGER__
246
251
  end
247
252
 
248
253
  def recv_request
249
- r = IO.select([@sock])
254
+ IO.select([@sock])
250
255
 
251
256
  @session.process_group.sync do
252
257
  raise RetryBecauseCantRead unless IO.select([@sock], nil, nil, 0)
253
258
 
254
- case header = @sock.gets
259
+ case @sock.gets
255
260
  when /Content-Length: (\d+)/
256
261
  b = @sock.read(2)
257
262
  raise b.inspect unless b == "\r\n"
258
263
 
259
- l = @sock.read(s = $1.to_i)
264
+ l = @sock.read($1.to_i)
260
265
  show_protocol :>, l
261
266
  JSON.load(l)
262
267
  when nil
@@ -309,7 +314,6 @@ module DEBUGGER__
309
314
  when 'setBreakpoints'
310
315
  req_path = args.dig('source', 'path')
311
316
  path = UI_DAP.local_to_remote_path(req_path)
312
-
313
317
  if path
314
318
  SESSION.clear_line_breakpoints path
315
319
 
@@ -436,11 +440,12 @@ module DEBUGGER__
436
440
  when 'evaluate'
437
441
  expr = req.dig('arguments', 'expression')
438
442
  if /\A\s*,(.+)\z/ =~ expr
439
- dbg_expr = $1
443
+ dbg_expr = $1.strip
444
+ dbg_expr.split(';;') { |cmd| @q_msg << cmd }
445
+
440
446
  send_response req,
441
- result: "",
447
+ result: "(rdbg:command) #{dbg_expr}",
442
448
  variablesReference: 0
443
- debugger do: dbg_expr
444
449
  else
445
450
  @q_msg << req
446
451
  end
@@ -452,8 +457,8 @@ module DEBUGGER__
452
457
  @q_msg << req
453
458
 
454
459
  else
455
- if respond_to? mid = "request_#{req['command']}"
456
- send mid, req
460
+ if respond_to? mid = "custom_dap_request_#{req['command']}"
461
+ __send__ mid, req
457
462
  else
458
463
  raise "Unknown request: #{req.inspect}"
459
464
  end
@@ -546,7 +551,7 @@ module DEBUGGER__
546
551
  when 'stackTrace'
547
552
  tid = req.dig('arguments', 'threadId')
548
553
 
549
- if tc = find_waiting_tc(tid)
554
+ if find_waiting_tc(tid)
550
555
  request_tc [:dap, :backtrace, req]
551
556
  else
552
557
  fail_response req
@@ -555,7 +560,7 @@ module DEBUGGER__
555
560
  frame_id = req.dig('arguments', 'frameId')
556
561
  if @frame_map[frame_id]
557
562
  tid, fid = @frame_map[frame_id]
558
- if tc = find_waiting_tc(tid)
563
+ if find_waiting_tc(tid)
559
564
  request_tc [:dap, :scopes, req, fid]
560
565
  else
561
566
  fail_response req
@@ -591,7 +596,7 @@ module DEBUGGER__
591
596
  frame_id = ref[1]
592
597
  tid, fid = @frame_map[frame_id]
593
598
 
594
- if tc = find_waiting_tc(tid)
599
+ if find_waiting_tc(tid)
595
600
  request_tc [:dap, :scope, req, fid]
596
601
  else
597
602
  fail_response req
@@ -600,7 +605,7 @@ module DEBUGGER__
600
605
  when :variable
601
606
  tid, vid = ref[1], ref[2]
602
607
 
603
- if tc = find_waiting_tc(tid)
608
+ if find_waiting_tc(tid)
604
609
  request_tc [:dap, :variable, req, vid]
605
610
  else
606
611
  fail_response req
@@ -619,7 +624,7 @@ module DEBUGGER__
619
624
  tid, fid = @frame_map[frame_id]
620
625
  expr = req.dig('arguments', 'expression')
621
626
 
622
- if tc = find_waiting_tc(tid)
627
+ if find_waiting_tc(tid)
623
628
  request_tc [:dap, :evaluate, req, fid, expr, context]
624
629
  else
625
630
  fail_response req
@@ -640,7 +645,7 @@ module DEBUGGER__
640
645
  frame_id = req.dig('arguments', 'frameId')
641
646
  tid, fid = @frame_map[frame_id]
642
647
 
643
- if tc = find_waiting_tc(tid)
648
+ if find_waiting_tc(tid)
644
649
  text = req.dig('arguments', 'text')
645
650
  line = req.dig('arguments', 'line')
646
651
  if col = req.dig('arguments', 'column')
@@ -651,11 +656,15 @@ module DEBUGGER__
651
656
  fail_response req
652
657
  end
653
658
  else
654
- raise "Unknown DAP request: #{req.inspect}"
659
+ if respond_to? mid = "custom_dap_request_#{req['command']}"
660
+ __send__ mid, req
661
+ else
662
+ raise "Unknown request: #{req.inspect}"
663
+ end
655
664
  end
656
665
  end
657
666
 
658
- def dap_event args
667
+ def process_protocol_result args
659
668
  # puts({dap_event: args}.inspect)
660
669
  type, req, result = args
661
670
 
@@ -703,7 +712,11 @@ module DEBUGGER__
703
712
  when :completions
704
713
  @ui.respond req, result
705
714
  else
706
- raise "unsupported: #{args.inspect}"
715
+ if respond_to? mid = "custom_dap_request_event_#{type}"
716
+ __send__ mid, req, result
717
+ else
718
+ raise "unsupported: #{args.inspect}"
719
+ end
707
720
  end
708
721
  end
709
722
 
@@ -789,7 +802,7 @@ module DEBUGGER__
789
802
  }
790
803
  end
791
804
 
792
- event! :dap_result, :backtrace, req, {
805
+ event! :protocol_result, :backtrace, req, {
793
806
  stackFrames: frames,
794
807
  totalFrames: @target_frames.size,
795
808
  }
@@ -806,7 +819,7 @@ module DEBUGGER__
806
819
  0
807
820
  end
808
821
 
809
- event! :dap_result, :scopes, req, scopes: [{
822
+ event! :protocol_result, :scopes, req, scopes: [{
810
823
  name: 'Local variables',
811
824
  presentationHint: 'locals',
812
825
  # variablesReference: N, # filled by SESSION
@@ -828,7 +841,7 @@ module DEBUGGER__
828
841
  variable(var, val)
829
842
  end
830
843
 
831
- event! :dap_result, :scope, req, variables: vars, tid: self.id
844
+ event! :protocol_result, :scope, req, variables: vars, tid: self.id
832
845
  when :variable
833
846
  vid = args.shift
834
847
  obj = @var_map[vid]
@@ -876,7 +889,7 @@ module DEBUGGER__
876
889
  end
877
890
  end
878
891
  end
879
- event! :dap_result, :variable, req, variables: (vars || []), tid: self.id
892
+ event! :protocol_result, :variable, req, variables: (vars || []), tid: self.id
880
893
 
881
894
  when :evaluate
882
895
  fid, expr, context = args
@@ -931,7 +944,7 @@ module DEBUGGER__
931
944
  result = 'Error: Can not evaluate on this frame'
932
945
  end
933
946
 
934
- event! :dap_result, :evaluate, req, message: message, tid: self.id, **evaluate_result(result)
947
+ event! :protocol_result, :evaluate, req, message: message, tid: self.id, **evaluate_result(result)
935
948
 
936
949
  when :completions
937
950
  fid, text = args
@@ -941,7 +954,7 @@ module DEBUGGER__
941
954
  words = IRB::InputCompletor::retrieve_completion_data(word, bind: b).compact
942
955
  end
943
956
 
944
- event! :dap_result, :completions, req, targets: (words || []).map{|phrase|
957
+ event! :protocol_result, :completions, req, targets: (words || []).map{|phrase|
945
958
  detail = nil
946
959
 
947
960
  if /\b([_a-zA-Z]\w*[!\?]?)\z/ =~ phrase
@@ -964,7 +977,11 @@ module DEBUGGER__
964
977
  }
965
978
 
966
979
  else
967
- raise "Unknown req: #{args.inspect}"
980
+ if respond_to? mid = "custom_dap_request_#{type}"
981
+ __send__ mid, req
982
+ else
983
+ raise "Unknown request: #{args.inspect}"
984
+ end
968
985
  end
969
986
  end
970
987
 
data/lib/debug/session.rb CHANGED
@@ -128,16 +128,16 @@ module DEBUGGER__
128
128
  @obj_map = {} # { object_id => ... } for CDP
129
129
 
130
130
  @tp_thread_begin = nil
131
+ @tp_thread_end = nil
132
+
131
133
  @commands = {}
132
134
  @unsafe_context = false
133
135
 
134
- has_keep_script_lines = RubyVM.respond_to? :keep_script_lines
136
+ @has_keep_script_lines = RubyVM.respond_to? :keep_script_lines
135
137
 
136
138
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
137
- if !has_keep_script_lines || bps_pending_until_load?
138
- eval_script = tp.eval_script unless has_keep_script_lines
139
- ThreadClient.current.on_load tp.instruction_sequence, eval_script
140
- end
139
+ eval_script = tp.eval_script unless @has_keep_script_lines
140
+ ThreadClient.current.on_load tp.instruction_sequence, eval_script
141
141
  }
142
142
  @tp_load_script.enable
143
143
 
@@ -169,12 +169,17 @@ module DEBUGGER__
169
169
  @ui = ui if ui
170
170
 
171
171
  @tp_thread_begin&.disable
172
+ @tp_thread_end&.disable
172
173
  @tp_thread_begin = nil
173
-
174
+ @tp_thread_end = nil
174
175
  @ui.activate self, on_fork: on_fork
175
176
 
176
177
  q = Queue.new
178
+ first_q = Queue.new
177
179
  @session_server = Thread.new do
180
+ # make sure `@session_server` is assigned
181
+ first_q.pop; first_q = nil
182
+
178
183
  Thread.current.name = 'DEBUGGER__::SESSION@server'
179
184
  Thread.current.abort_on_exception = true
180
185
 
@@ -192,10 +197,16 @@ module DEBUGGER__
192
197
  end
193
198
  @tp_thread_begin.enable
194
199
 
200
+ @tp_thread_end = TracePoint.new(:thread_end) do |tp|
201
+ @th_clients.delete(Thread.current)
202
+ end
203
+ @tp_thread_end.enable
204
+
195
205
  # session start
196
206
  q << true
197
207
  session_server_main
198
208
  end
209
+ first_q << :ok
199
210
 
200
211
  q.pop
201
212
  end
@@ -205,6 +216,7 @@ module DEBUGGER__
205
216
  @thread_stopper.disable
206
217
  @tp_load_script.disable
207
218
  @tp_thread_begin.disable
219
+ @tp_thread_end.disable
208
220
  @bps.each_value{|bp| bp.disable}
209
221
  @th_clients.each_value{|thc| thc.close}
210
222
  @tracers.values.each{|t| t.disable}
@@ -219,11 +231,13 @@ module DEBUGGER__
219
231
 
220
232
  # activate new ui
221
233
  @tp_thread_begin.disable
234
+ @tp_thread_end.disable
222
235
  @ui.activate self
223
236
  if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
224
237
  thc.mark_as_management
225
238
  end
226
239
  @tp_thread_begin.enable
240
+ @tp_thread_end.enable
227
241
  end
228
242
 
229
243
  def pop_event
@@ -329,16 +343,13 @@ module DEBUGGER__
329
343
  opt = ev_args[3]
330
344
  add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
331
345
  else
332
- # ignore
346
+ stop_all_threads
333
347
  end
334
348
 
335
349
  wait_command_loop
336
350
 
337
- when :dap_result
338
- dap_event ev_args # server.rb
339
- wait_command_loop
340
- when :cdp_result
341
- cdp_event ev_args
351
+ when :protocol_result
352
+ process_protocol_result ev_args
342
353
  wait_command_loop
343
354
  end
344
355
  end
@@ -480,9 +491,9 @@ module DEBUGGER__
480
491
  # * `u[ntil]`
481
492
  # * Similar to `next` command, but only stop later lines or the end of the current frame.
482
493
  # * Similar to gdb's `advance` command.
483
- # * `u[ntil] <[file:]line>
494
+ # * `u[ntil] <[file:]line>`
484
495
  # * Run til the program reaches given location or the end of the current frame.
485
- # * `u[ntil] <name>
496
+ # * `u[ntil] <name>`
486
497
  # * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
487
498
  register_command 'u', 'until',
488
499
  repeat: true,
@@ -889,13 +900,13 @@ module DEBUGGER__
889
900
  # * `p <expr>`
890
901
  # * Evaluate like `p <expr>` on the current frame.
891
902
  register_command 'p' do |arg|
892
- request_tc [:eval, :p, arg.to_s]
903
+ request_eval :p, arg.to_s
893
904
  end
894
905
 
895
906
  # * `pp <expr>`
896
907
  # * Evaluate like `pp <expr>` on the current frame.
897
908
  register_command 'pp' do |arg|
898
- request_tc [:eval, :pp, arg.to_s]
909
+ request_eval :pp, arg.to_s
899
910
  end
900
911
 
901
912
  # * `eval <expr>`
@@ -906,7 +917,7 @@ module DEBUGGER__
906
917
  @ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
907
918
  :retry
908
919
  else
909
- request_tc [:eval, :call, arg]
920
+ request_eval :call, arg
910
921
  end
911
922
  end
912
923
 
@@ -917,7 +928,7 @@ module DEBUGGER__
917
928
  @ui.puts "not supported on the remote console."
918
929
  :retry
919
930
  end
920
- request_tc [:eval, :irb]
931
+ request_eval :irb, nil
921
932
  end
922
933
 
923
934
  ### Trace
@@ -1137,7 +1148,7 @@ module DEBUGGER__
1137
1148
  @repl_prev_line = nil
1138
1149
  check_unsafe
1139
1150
 
1140
- request_tc [:eval, :pp, line]
1151
+ request_eval :pp, line
1141
1152
  end
1142
1153
 
1143
1154
  rescue Interrupt
@@ -1153,6 +1164,11 @@ module DEBUGGER__
1153
1164
  return :retry
1154
1165
  end
1155
1166
 
1167
+ def request_eval type, src
1168
+ restart_all_threads
1169
+ request_tc [:eval, type, src]
1170
+ end
1171
+
1156
1172
  def step_command type, arg
1157
1173
  if type == :until
1158
1174
  leave_subsession [:step, type, arg]
@@ -1321,10 +1337,6 @@ module DEBUGGER__
1321
1337
 
1322
1338
  # breakpoint management
1323
1339
 
1324
- def bps_pending_until_load?
1325
- @bps.any?{|key, bp| bp.pending_until_load?}
1326
- end
1327
-
1328
1340
  def iterate_bps
1329
1341
  deleted_bps = []
1330
1342
  i = 0
@@ -1500,7 +1512,7 @@ module DEBUGGER__
1500
1512
  def clear_line_breakpoints path
1501
1513
  path = resolve_path(path)
1502
1514
  clear_breakpoints do |k, bp|
1503
- bp.is_a?(LineBreakpoint) && DEBUGGER__.compare_path(k.first, path)
1515
+ bp.is_a?(LineBreakpoint) && bp.path_is?(path)
1504
1516
  end
1505
1517
  rescue Errno::ENOENT
1506
1518
  # just ignore
@@ -1524,7 +1536,7 @@ module DEBUGGER__
1524
1536
  # tracers
1525
1537
 
1526
1538
  def add_tracer tracer
1527
- if @tracers.has_key? tracer.key
1539
+ if @tracers[tracer.key]&.enabled?
1528
1540
  tracer.disable
1529
1541
  @ui.puts "Duplicated tracer: #{tracer}"
1530
1542
  else
@@ -1724,25 +1736,26 @@ module DEBUGGER__
1724
1736
  file_path, reloaded = @sr.add(iseq, src)
1725
1737
  @ui.event :load, file_path, reloaded
1726
1738
 
1727
- pending_line_breakpoints = @bps.find_all do |key, bp|
1728
- LineBreakpoint === bp && !bp.iseq
1729
- end
1730
-
1731
- pending_line_breakpoints.each do |_key, bp|
1732
- if DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
1733
- bp.try_activate iseq
1734
- end
1735
- end
1736
-
1737
- if reloaded
1738
- @bps.find_all do |key, bp|
1739
- LineBreakpoint === bp && DEBUGGER__.compare_path(bp.path, file_path)
1739
+ # check breakpoints
1740
+ if file_path
1741
+ @bps.find_all do |_key, bp|
1742
+ LineBreakpoint === bp && bp.path_is?(file_path)
1740
1743
  end.each do |_key, bp|
1741
- @bps.delete bp.key # to allow duplicate
1742
- if nbp = LineBreakpoint.copy(bp, iseq)
1743
- add_bp nbp
1744
+ if !bp.iseq
1745
+ bp.try_activate iseq
1746
+ elsif reloaded
1747
+ @bps.delete bp.key # to allow duplicate
1748
+ if nbp = LineBreakpoint.copy(bp, iseq)
1749
+ add_bp nbp
1750
+ end
1744
1751
  end
1745
1752
  end
1753
+ else # !file_path => file_path is not existing
1754
+ @bps.find_all do |_key, bp|
1755
+ LineBreakpoint === bp && !bp.iseq && DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
1756
+ end.each do |_key, bp|
1757
+ bp.try_activate iseq
1758
+ end
1746
1759
  end
1747
1760
  end
1748
1761
 
@@ -2438,7 +2451,7 @@ module DEBUGGER__
2438
2451
  end
2439
2452
 
2440
2453
  module DaemonInterceptor
2441
- def daemon
2454
+ def daemon(*args)
2442
2455
  return super unless defined?(SESSION) && SESSION.active?
2443
2456
 
2444
2457
  _, child_hook = __fork_setup_for_debugger(:child)
@@ -34,7 +34,7 @@ module DEBUGGER__
34
34
  end
35
35
 
36
36
  def add iseq, src
37
- # do nothing
37
+ # only manage loaded file names
38
38
  if (path = (iseq.absolute_path || iseq.path)) && File.exist?(path)
39
39
  if @loaded_file_map.has_key? path
40
40
  return path, true # reloaded
@@ -49,7 +49,7 @@ module DEBUGGER__
49
49
  lines = iseq.script_lines&.map(&:chomp)
50
50
  line = iseq.first_line
51
51
  if line > 1
52
- lines = [*([''] * (line - 1)), *lines]
52
+ [*([''] * (line - 1)), *lines]
53
53
  else
54
54
  lines
55
55
  end
@@ -1230,7 +1230,15 @@ module DEBUGGER__
1230
1230
  rescue SuspendReplay, SystemExit, Interrupt
1231
1231
  raise
1232
1232
  rescue Exception => e
1233
- pp ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
1233
+ STDERR.puts e.cause.inspect
1234
+ STDERR.puts e.inspect
1235
+ Thread.list.each{|th|
1236
+ STDERR.puts "@@@ #{th}"
1237
+ th.backtrace.each{|b|
1238
+ STDERR.puts " > #{b}"
1239
+ }
1240
+ }
1241
+ p ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
1234
1242
  raise
1235
1243
  ensure
1236
1244
  @returning = false
data/lib/debug/tracer.rb CHANGED
@@ -54,6 +54,10 @@ module DEBUGGER__
54
54
  @tracer.disable
55
55
  end
56
56
 
57
+ def enabled?
58
+ @tracer.enabled?
59
+ end
60
+
57
61
  def description
58
62
  nil
59
63
  end
@@ -85,11 +89,6 @@ module DEBUGGER__
85
89
  end
86
90
  end
87
91
 
88
- def puts msg
89
- @output.puts msg
90
- @output.flush
91
- end
92
-
93
92
  def minfo tp
94
93
  return "block{}" if tp.event == :b_call
95
94
 
data/lib/debug/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DEBUGGER__
4
- VERSION = "1.7.1"
4
+ VERSION = "1.7.2"
5
5
  end
data/misc/README.md.erb CHANGED
@@ -26,7 +26,7 @@ New debug.rb has several advantages:
26
26
  * Support threads (almost done) and ractors (TODO).
27
27
  * Support suspending and entering to the console debugging with `Ctrl-C` at most of timing.
28
28
  * Show parameters on backtrace command.
29
- * Support recording & reply debugging.
29
+ * Support recording & replay debugging.
30
30
 
31
31
  # Installation
32
32
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: debug
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.1
4
+ version: 1.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichi Sasada
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-22 00:00:00.000000000 Z
11
+ date: 2023-03-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: irb