debug 1.7.1 → 1.7.2

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 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