debug 1.6.1 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +22 -10
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +84 -55
- data/Rakefile +8 -3
- data/TODO.md +8 -8
- data/debug.gemspec +3 -3
- data/exe/rdbg +19 -4
- data/ext/debug/debug.c +33 -5
- data/ext/debug/extconf.rb +1 -0
- data/ext/debug/iseq_collector.c +2 -0
- data/lib/debug/abbrev_command.rb +77 -0
- data/lib/debug/breakpoint.rb +15 -11
- data/lib/debug/client.rb +26 -8
- data/lib/debug/color.rb +0 -0
- data/lib/debug/config.rb +69 -23
- data/lib/debug/console.rb +8 -29
- data/lib/debug/dap_custom/traceInspector.rb +336 -0
- data/lib/debug/frame_info.rb +9 -0
- data/lib/debug/irb_integration.rb +27 -0
- data/lib/debug/local.rb +16 -10
- data/lib/debug/open.rb +0 -0
- data/lib/debug/open_nonstop.rb +0 -0
- data/lib/debug/prelude.rb +2 -1
- data/lib/debug/server.rb +32 -27
- data/lib/debug/server_cdp.rb +360 -155
- data/lib/debug/server_dap.rb +330 -197
- data/lib/debug/session.rb +494 -258
- data/lib/debug/source_repository.rb +41 -21
- data/lib/debug/start.rb +1 -1
- data/lib/debug/thread_client.rb +241 -82
- data/lib/debug/tracer.rb +4 -5
- data/lib/debug/version.rb +1 -1
- data/lib/debug.rb +7 -2
- data/misc/README.md.erb +50 -44
- metadata +13 -10
data/lib/debug/server_cdp.rb
CHANGED
@@ -7,13 +7,18 @@ require 'securerandom'
|
|
7
7
|
require 'stringio'
|
8
8
|
require 'open3'
|
9
9
|
require 'tmpdir'
|
10
|
+
require 'tempfile'
|
11
|
+
require 'timeout'
|
10
12
|
|
11
13
|
module DEBUGGER__
|
12
14
|
module UI_CDP
|
13
15
|
SHOW_PROTOCOL = ENV['RUBY_DEBUG_CDP_SHOW_PROTOCOL'] == '1'
|
14
16
|
|
17
|
+
class UnsupportedError < StandardError; end
|
18
|
+
class NotFoundChromeEndpointError < StandardError; end
|
19
|
+
|
15
20
|
class << self
|
16
|
-
def setup_chrome addr
|
21
|
+
def setup_chrome addr, uuid
|
17
22
|
return if CONFIG[:chrome_path] == ''
|
18
23
|
|
19
24
|
port, path, pid = run_new_chrome
|
@@ -29,79 +34,217 @@ module DEBUGGER__
|
|
29
34
|
|
30
35
|
loop do
|
31
36
|
res = ws_client.extract_data
|
32
|
-
case
|
33
|
-
when
|
37
|
+
case res['id']
|
38
|
+
when 1
|
39
|
+
target_info = res.dig('result', 'targetInfos')
|
34
40
|
page = target_info.find{|t| t['type'] == 'page'}
|
35
41
|
ws_client.send id: 2, method: 'Target.attachToTarget',
|
36
42
|
params: {
|
37
43
|
targetId: page['targetId'],
|
38
44
|
flatten: true
|
39
45
|
}
|
40
|
-
when
|
46
|
+
when 2
|
41
47
|
s_id = res.dig('result', 'sessionId')
|
48
|
+
# TODO: change id
|
49
|
+
ws_client.send sessionId: s_id, id: 100, method: 'Network.enable'
|
42
50
|
ws_client.send sessionId: s_id, id: 3,
|
43
51
|
method: 'Page.enable'
|
44
|
-
when
|
52
|
+
when 3
|
45
53
|
s_id = res['sessionId']
|
46
54
|
ws_client.send sessionId: s_id, id: 4,
|
47
55
|
method: 'Page.getFrameTree'
|
48
|
-
when
|
56
|
+
when 4
|
49
57
|
s_id = res['sessionId']
|
50
58
|
f_id = res.dig('result', 'frameTree', 'frame', 'id')
|
51
59
|
ws_client.send sessionId: s_id, id: 5,
|
52
60
|
method: 'Page.navigate',
|
53
61
|
params: {
|
54
|
-
url: "devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{addr}/#{
|
62
|
+
url: "devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&noJavaScriptCompletion=true&ws=#{addr}/#{uuid}",
|
55
63
|
frameId: f_id
|
56
64
|
}
|
57
|
-
when
|
65
|
+
when 101
|
58
66
|
break
|
67
|
+
else
|
68
|
+
if res['method'] == 'Network.webSocketWillSendHandshakeRequest'
|
69
|
+
s_id = res['sessionId']
|
70
|
+
# Display the console by entering ESC key
|
71
|
+
ws_client.send sessionId: s_id, id: 101, # TODO: change id
|
72
|
+
method:"Input.dispatchKeyEvent",
|
73
|
+
params: {
|
74
|
+
type:"keyDown",
|
75
|
+
windowsVirtualKeyCode:27 # ESC key
|
76
|
+
}
|
77
|
+
end
|
59
78
|
end
|
60
79
|
end
|
61
80
|
pid
|
62
|
-
rescue Errno::ENOENT
|
81
|
+
rescue Errno::ENOENT, UnsupportedError, NotFoundChromeEndpointError
|
63
82
|
nil
|
64
83
|
end
|
65
84
|
|
66
|
-
|
67
|
-
|
85
|
+
TIMEOUT_SEC = 5
|
86
|
+
|
87
|
+
def run_new_chrome
|
88
|
+
path = CONFIG[:chrome_path]
|
89
|
+
|
90
|
+
data = nil
|
91
|
+
port = nil
|
92
|
+
wait_thr = nil
|
68
93
|
|
69
94
|
# The process to check OS is based on `selenium` project.
|
70
95
|
case RbConfig::CONFIG['host_os']
|
71
96
|
when /mswin|msys|mingw|cygwin|emc/
|
72
|
-
|
97
|
+
if path.nil?
|
98
|
+
candidates = ['C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe']
|
99
|
+
path = get_chrome_path candidates
|
100
|
+
end
|
101
|
+
# The path is based on https://github.com/sindresorhus/open/blob/v8.4.0/index.js#L128.
|
102
|
+
stdin, stdout, stderr, wait_thr = *Open3.popen3("#{ENV['SystemRoot']}\\System32\\WindowsPowerShell\\v1.0\\powershell")
|
103
|
+
tf = Tempfile.create(['debug-', '.txt'])
|
104
|
+
|
105
|
+
stdin.puts("Start-process '#{path}' -Argumentlist '--remote-debugging-port=0', '--no-first-run', '--no-default-browser-check', '--user-data-dir=C:\\temp' -Wait -RedirectStandardError #{tf.path}")
|
106
|
+
stdin.close
|
107
|
+
stdout.close
|
108
|
+
stderr.close
|
109
|
+
port, path = get_devtools_endpoint(tf.path)
|
110
|
+
|
111
|
+
at_exit{
|
112
|
+
DEBUGGER__.skip_all
|
113
|
+
|
114
|
+
stdin, stdout, stderr, wait_thr = *Open3.popen3("#{ENV['SystemRoot']}\\System32\\WindowsPowerShell\\v1.0\\powershell")
|
115
|
+
stdin.puts("Stop-process -Name chrome")
|
116
|
+
stdin.close
|
117
|
+
stdout.close
|
118
|
+
stderr.close
|
119
|
+
tf.close
|
120
|
+
begin
|
121
|
+
File.unlink(tf)
|
122
|
+
rescue Errno::EACCES
|
123
|
+
end
|
124
|
+
}
|
73
125
|
when /darwin|mac os/
|
74
|
-
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome'
|
126
|
+
path = path || '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome'
|
127
|
+
dir = Dir.mktmpdir
|
128
|
+
# The command line flags are based on: https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Chrome_Desktop#connecting.
|
129
|
+
stdin, stdout, stderr, wait_thr = *Open3.popen3("#{path} --remote-debugging-port=0 --no-first-run --no-default-browser-check --user-data-dir=#{dir}")
|
130
|
+
stdin.close
|
131
|
+
stdout.close
|
132
|
+
data = stderr.readpartial 4096
|
133
|
+
stderr.close
|
134
|
+
if data.match /DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/
|
135
|
+
port = $1
|
136
|
+
path = $2
|
137
|
+
end
|
138
|
+
|
139
|
+
at_exit{
|
140
|
+
DEBUGGER__.skip_all
|
141
|
+
FileUtils.rm_rf dir
|
142
|
+
}
|
75
143
|
when /linux/
|
76
|
-
'google-chrome'
|
144
|
+
path = path || 'google-chrome'
|
145
|
+
dir = Dir.mktmpdir
|
146
|
+
# The command line flags are based on: https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Chrome_Desktop#connecting.
|
147
|
+
stdin, stdout, stderr, wait_thr = *Open3.popen3("#{path} --remote-debugging-port=0 --no-first-run --no-default-browser-check --user-data-dir=#{dir}")
|
148
|
+
stdin.close
|
149
|
+
stdout.close
|
150
|
+
data = ''
|
151
|
+
begin
|
152
|
+
Timeout.timeout(TIMEOUT_SEC) do
|
153
|
+
until data.match?(/DevTools listening on ws:\/\/127.0.0.1:\d+.*/)
|
154
|
+
data = stderr.readpartial 4096
|
155
|
+
end
|
156
|
+
end
|
157
|
+
rescue Exception
|
158
|
+
raise NotFoundChromeEndpointError
|
159
|
+
end
|
160
|
+
stderr.close
|
161
|
+
if data.match /DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/
|
162
|
+
port = $1
|
163
|
+
path = $2
|
164
|
+
end
|
165
|
+
|
166
|
+
at_exit{
|
167
|
+
DEBUGGER__.skip_all
|
168
|
+
FileUtils.rm_rf dir
|
169
|
+
}
|
77
170
|
else
|
78
|
-
raise
|
171
|
+
raise UnsupportedError
|
79
172
|
end
|
173
|
+
|
174
|
+
[port, path, wait_thr.pid]
|
80
175
|
end
|
81
176
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
177
|
+
def get_chrome_path candidates
|
178
|
+
candidates.each{|c|
|
179
|
+
if File.exist? c
|
180
|
+
return c
|
181
|
+
end
|
182
|
+
}
|
183
|
+
raise UnsupportedError
|
184
|
+
end
|
185
|
+
|
186
|
+
ITERATIONS = 50
|
187
|
+
|
188
|
+
def get_devtools_endpoint tf
|
189
|
+
i = 1
|
190
|
+
while i < ITERATIONS
|
191
|
+
i += 1
|
192
|
+
if File.exist?(tf) && data = File.read(tf)
|
193
|
+
if data.match /DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/
|
194
|
+
port = $1
|
195
|
+
path = $2
|
196
|
+
return [port, path]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
sleep 0.1
|
93
200
|
end
|
94
|
-
|
201
|
+
raise NotFoundChromeEndpointError
|
202
|
+
end
|
203
|
+
end
|
95
204
|
|
96
|
-
|
97
|
-
|
98
|
-
|
205
|
+
def send_chrome_response req
|
206
|
+
@repl = false
|
207
|
+
case req
|
208
|
+
when /^GET\s\/json\/version\sHTTP\/1.1/
|
209
|
+
body = {
|
210
|
+
Browser: "ruby/v#{RUBY_VERSION}",
|
211
|
+
'Protocol-Version': "1.1"
|
99
212
|
}
|
100
|
-
|
101
|
-
|
213
|
+
send_http_res body
|
214
|
+
raise UI_ServerBase::RetryConnection
|
215
|
+
|
216
|
+
when /^GET\s\/json\sHTTP\/1.1/
|
217
|
+
@uuid = @uuid || SecureRandom.uuid
|
218
|
+
addr = @local_addr.inspect_sockaddr
|
219
|
+
body = [{
|
220
|
+
description: "ruby instance",
|
221
|
+
devtoolsFrontendUrl: "devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=#{addr}/#{@uuid}",
|
222
|
+
id: @uuid,
|
223
|
+
title: $0,
|
224
|
+
type: "node",
|
225
|
+
url: "file://#{File.absolute_path($0)}",
|
226
|
+
webSocketDebuggerUrl: "ws://#{addr}/#{@uuid}"
|
227
|
+
}]
|
228
|
+
send_http_res body
|
229
|
+
raise UI_ServerBase::RetryConnection
|
230
|
+
|
231
|
+
when /^GET\s\/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})\sHTTP\/1.1/
|
232
|
+
raise 'Incorrect uuid' unless $1 == @uuid
|
233
|
+
|
234
|
+
@need_pause_at_first = false
|
235
|
+
CONFIG.set_config no_color: true
|
236
|
+
|
237
|
+
@ws_server = WebSocketServer.new(@sock)
|
238
|
+
@ws_server.handshake
|
102
239
|
end
|
103
240
|
end
|
104
241
|
|
242
|
+
def send_http_res body
|
243
|
+
json = JSON.generate body
|
244
|
+
header = "HTTP/1.0 200 OK\r\nContent-Type: application/json; charset=UTF-8\r\nCache-Control: no-cache\r\nContent-Length: #{json.bytesize}\r\n\r\n"
|
245
|
+
@sock.puts "#{header}#{json}"
|
246
|
+
end
|
247
|
+
|
105
248
|
module WebSocketUtils
|
106
249
|
class Frame
|
107
250
|
attr_reader :b
|
@@ -306,11 +449,7 @@ module DEBUGGER__
|
|
306
449
|
end
|
307
450
|
|
308
451
|
def send_response req, **res
|
309
|
-
|
310
|
-
@ws_server.send id: req['id'], result: {}
|
311
|
-
else
|
312
|
-
@ws_server.send id: req['id'], result: res
|
313
|
-
end
|
452
|
+
@ws_server.send id: req['id'], result: res
|
314
453
|
end
|
315
454
|
|
316
455
|
def send_fail_response req, **res
|
@@ -318,11 +457,7 @@ module DEBUGGER__
|
|
318
457
|
end
|
319
458
|
|
320
459
|
def send_event method, **params
|
321
|
-
|
322
|
-
@ws_server.send method: method, params: {}
|
323
|
-
else
|
324
|
-
@ws_server.send method: method, params: params
|
325
|
-
end
|
460
|
+
@ws_server.send method: method, params: params
|
326
461
|
end
|
327
462
|
|
328
463
|
INVALID_REQUEST = -32600
|
@@ -339,7 +474,7 @@ module DEBUGGER__
|
|
339
474
|
when 'Debugger.getScriptSource'
|
340
475
|
@q_msg << req
|
341
476
|
when 'Debugger.enable'
|
342
|
-
send_response req
|
477
|
+
send_response req, debuggerId: rand.to_s
|
343
478
|
@q_msg << req
|
344
479
|
when 'Runtime.enable'
|
345
480
|
send_response req
|
@@ -413,6 +548,9 @@ module DEBUGGER__
|
|
413
548
|
activate_bp bps
|
414
549
|
end
|
415
550
|
send_response req
|
551
|
+
when 'Debugger.pause'
|
552
|
+
send_response req
|
553
|
+
Process.kill(UI_ServerBase::TRAP_SIGNAL, Process.pid)
|
416
554
|
|
417
555
|
# breakpoint
|
418
556
|
when 'Debugger.getPossibleBreakpoints'
|
@@ -420,35 +558,31 @@ module DEBUGGER__
|
|
420
558
|
when 'Debugger.setBreakpointByUrl'
|
421
559
|
line = req.dig('params', 'lineNumber')
|
422
560
|
if regexp = req.dig('params', 'urlRegex')
|
423
|
-
path = regexp.match(/(.*)\|/)[1].gsub("\\", "")
|
424
|
-
cond = req.dig('params', 'condition')
|
425
|
-
src = get_source_code path
|
426
|
-
end_line = src.lines.count
|
427
|
-
line = end_line if line > end_line
|
428
561
|
b_id = "1:#{line}:#{regexp}"
|
429
|
-
if cond != ''
|
430
|
-
SESSION.add_line_breakpoint(path, line + 1, cond: cond)
|
431
|
-
else
|
432
|
-
SESSION.add_line_breakpoint(path, line + 1)
|
433
|
-
end
|
434
562
|
bps[b_id] = bps.size
|
435
|
-
|
436
|
-
req
|
437
|
-
req['params']['lineNumber'] = line
|
438
|
-
req['params']['breakpointId'] = b_id
|
439
|
-
@q_msg << req
|
563
|
+
path = regexp.match(/(.*)\|/)[1].gsub("\\", "")
|
564
|
+
add_line_breakpoint(req, b_id, path)
|
440
565
|
elsif url = req.dig('params', 'url')
|
441
566
|
b_id = "#{line}:#{url}"
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
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
|
450
577
|
else
|
451
|
-
|
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
|
452
586
|
end
|
453
587
|
when 'Debugger.removeBreakpoint'
|
454
588
|
b_id = req.dig('params', 'breakpointId')
|
@@ -487,6 +621,24 @@ module DEBUGGER__
|
|
487
621
|
@q_msg << 'continue'
|
488
622
|
end
|
489
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
|
+
|
490
642
|
def del_bp bps, k
|
491
643
|
return bps unless idx = bps[k]
|
492
644
|
|
@@ -524,43 +676,50 @@ module DEBUGGER__
|
|
524
676
|
def cleanup_reader
|
525
677
|
super
|
526
678
|
Process.kill :KILL, @chrome_pid if @chrome_pid
|
679
|
+
rescue Errno::ESRCH # continue if @chrome_pid process is not found
|
527
680
|
end
|
528
681
|
|
529
682
|
## Called by the SESSION thread
|
530
683
|
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
@q_msg.pop || 'kill!'
|
535
|
-
end
|
536
|
-
|
537
|
-
def respond req, **result
|
538
|
-
send_response req, **result
|
539
|
-
end
|
540
|
-
|
541
|
-
def respond_fail req, **result
|
542
|
-
send_fail_response req, **result
|
543
|
-
end
|
544
|
-
|
545
|
-
def fire_event event, **result
|
546
|
-
if result.empty?
|
547
|
-
send_event event
|
548
|
-
else
|
549
|
-
send_event event, **result
|
550
|
-
end
|
551
|
-
end
|
684
|
+
alias respond send_response
|
685
|
+
alias respond_fail send_fail_response
|
686
|
+
alias fire_event send_event
|
552
687
|
|
553
688
|
def sock skip: false
|
554
689
|
yield $stderr
|
555
690
|
end
|
556
691
|
|
557
|
-
def puts result
|
692
|
+
def puts result=''
|
558
693
|
# STDERR.puts "puts: #{result}"
|
559
694
|
# send_event 'output', category: 'stderr', output: "PUTS!!: " + result.to_s
|
560
695
|
end
|
561
696
|
end
|
562
697
|
|
563
698
|
class Session
|
699
|
+
include GlobalVariablesHelper
|
700
|
+
|
701
|
+
# FIXME: unify this method with ThreadClient#propertyDescriptor.
|
702
|
+
def get_type obj
|
703
|
+
case obj
|
704
|
+
when Array
|
705
|
+
['object', 'array']
|
706
|
+
when Hash
|
707
|
+
['object', 'map']
|
708
|
+
when String
|
709
|
+
['string']
|
710
|
+
when TrueClass, FalseClass
|
711
|
+
['boolean']
|
712
|
+
when Symbol
|
713
|
+
['symbol']
|
714
|
+
when Integer, Float
|
715
|
+
['number']
|
716
|
+
when Exception
|
717
|
+
['object', 'error']
|
718
|
+
else
|
719
|
+
['object']
|
720
|
+
end
|
721
|
+
end
|
722
|
+
|
564
723
|
def fail_response req, **result
|
565
724
|
@ui.respond_fail req, **result
|
566
725
|
return :retry
|
@@ -584,17 +743,42 @@ module DEBUGGER__
|
|
584
743
|
code: INVALID_PARAMS,
|
585
744
|
message: "'callFrameId' is an invalid"
|
586
745
|
end
|
587
|
-
when 'Runtime.getProperties'
|
588
|
-
oid = req.dig('params', 'objectId')
|
746
|
+
when 'Runtime.getProperties', 'Runtime.getExceptionDetails'
|
747
|
+
oid = req.dig('params', 'objectId') || req.dig('params', 'errorObjectId')
|
589
748
|
if ref = @obj_map[oid]
|
590
749
|
case ref[0]
|
591
750
|
when 'local'
|
592
751
|
frame_id = ref[1]
|
593
752
|
fid = @frame_map[frame_id]
|
594
753
|
request_tc [:cdp, :scope, req, fid]
|
754
|
+
when 'global'
|
755
|
+
vars = safe_global_variables.sort.map do |name|
|
756
|
+
begin
|
757
|
+
gv = eval(name.to_s)
|
758
|
+
rescue Errno::ENOENT
|
759
|
+
gv = nil
|
760
|
+
end
|
761
|
+
prop = {
|
762
|
+
name: name,
|
763
|
+
value: {
|
764
|
+
description: gv.inspect
|
765
|
+
},
|
766
|
+
configurable: true,
|
767
|
+
enumerable: true
|
768
|
+
}
|
769
|
+
type, subtype = get_type(gv)
|
770
|
+
prop[:value][:type] = type
|
771
|
+
prop[:value][:subtype] = subtype if subtype
|
772
|
+
prop
|
773
|
+
end
|
774
|
+
|
775
|
+
@ui.respond req, result: vars
|
776
|
+
return :retry
|
595
777
|
when 'properties'
|
596
778
|
request_tc [:cdp, :properties, req, oid]
|
597
|
-
when '
|
779
|
+
when 'exception'
|
780
|
+
request_tc [:cdp, :exception, req, oid]
|
781
|
+
when 'script'
|
598
782
|
# TODO: Support script and global types
|
599
783
|
@ui.respond req, result: []
|
600
784
|
return :retry
|
@@ -653,7 +837,7 @@ module DEBUGGER__
|
|
653
837
|
end
|
654
838
|
end
|
655
839
|
|
656
|
-
def
|
840
|
+
def process_protocol_result args
|
657
841
|
type, req, result = args
|
658
842
|
|
659
843
|
case type
|
@@ -665,27 +849,25 @@ module DEBUGGER__
|
|
665
849
|
unless s_id = @scr_id_map[path]
|
666
850
|
s_id = (@scr_id_map.size + 1).to_s
|
667
851
|
@scr_id_map[path] = s_id
|
852
|
+
lineno = 0
|
853
|
+
src = ''
|
668
854
|
if path && File.exist?(path)
|
669
855
|
src = File.read(path)
|
856
|
+
@src_map[s_id] = src
|
857
|
+
lineno = src.lines.count
|
670
858
|
end
|
671
|
-
@
|
672
|
-
end
|
673
|
-
if src = @src_map[s_id]
|
674
|
-
lineno = src.lines.count
|
675
|
-
else
|
676
|
-
lineno = 0
|
677
|
-
end
|
678
|
-
frame[:location][:scriptId] = s_id
|
679
|
-
frame[:functionLocation][:scriptId] = s_id
|
680
|
-
@ui.fire_event 'Debugger.scriptParsed',
|
859
|
+
@ui.fire_event 'Debugger.scriptParsed',
|
681
860
|
scriptId: s_id,
|
682
|
-
url:
|
861
|
+
url: path,
|
683
862
|
startLine: 0,
|
684
863
|
startColumn: 0,
|
685
864
|
endLine: lineno,
|
686
865
|
endColumn: 0,
|
687
866
|
executionContextId: 1,
|
688
867
|
hash: src.hash.inspect
|
868
|
+
end
|
869
|
+
frame[:location][:scriptId] = s_id
|
870
|
+
frame[:functionLocation][:scriptId] = s_id
|
689
871
|
|
690
872
|
frame[:scopeChain].each {|s|
|
691
873
|
oid = s.dig(:object, :objectId)
|
@@ -730,6 +912,9 @@ module DEBUGGER__
|
|
730
912
|
frame[:scriptId] = s_id
|
731
913
|
end
|
732
914
|
}
|
915
|
+
if oid = exc[:exception][:objectId]
|
916
|
+
@obj_map[oid] = ['exception']
|
917
|
+
end
|
733
918
|
end
|
734
919
|
rs = result.dig(:response, :result)
|
735
920
|
[rs].each{|obj|
|
@@ -767,6 +952,8 @@ module DEBUGGER__
|
|
767
952
|
}
|
768
953
|
}
|
769
954
|
@ui.respond req, **result
|
955
|
+
when :exception
|
956
|
+
@ui.respond req, **result
|
770
957
|
end
|
771
958
|
end
|
772
959
|
end
|
@@ -838,7 +1025,7 @@ module DEBUGGER__
|
|
838
1025
|
result[:data] = evaluate_result exception
|
839
1026
|
result[:reason] = 'exception'
|
840
1027
|
end
|
841
|
-
event! :
|
1028
|
+
event! :protocol_result, :backtrace, req, result
|
842
1029
|
when :evaluate
|
843
1030
|
res = {}
|
844
1031
|
fid, expr, group = args
|
@@ -857,7 +1044,7 @@ module DEBUGGER__
|
|
857
1044
|
case expr
|
858
1045
|
# Chrome doesn't read instance variables
|
859
1046
|
when /\A\$\S/
|
860
|
-
|
1047
|
+
safe_global_variables.each{|gvar|
|
861
1048
|
if gvar.to_s == expr
|
862
1049
|
result = eval(gvar.to_s)
|
863
1050
|
break false
|
@@ -872,8 +1059,8 @@ module DEBUGGER__
|
|
872
1059
|
result = b.local_variable_get(expr)
|
873
1060
|
rescue NameError
|
874
1061
|
# try to check method
|
875
|
-
if b.receiver
|
876
|
-
result = b.receiver
|
1062
|
+
if M_RESPOND_TO_P.bind_call(b.receiver, expr, include_all: true)
|
1063
|
+
result = M_METHOD.bind_call(b.receiver, expr)
|
877
1064
|
else
|
878
1065
|
message = "Error: Can not evaluate: #{expr.inspect}"
|
879
1066
|
end
|
@@ -883,38 +1070,10 @@ module DEBUGGER__
|
|
883
1070
|
begin
|
884
1071
|
orig_stdout = $stdout
|
885
1072
|
$stdout = StringIO.new
|
886
|
-
result =
|
1073
|
+
result = b.eval(expr.to_s, '(DEBUG CONSOLE)')
|
887
1074
|
rescue Exception => e
|
888
1075
|
result = e
|
889
|
-
|
890
|
-
frames = [
|
891
|
-
{
|
892
|
-
columnNumber: 0,
|
893
|
-
functionName: 'eval',
|
894
|
-
lineNumber: 0,
|
895
|
-
url: ''
|
896
|
-
}
|
897
|
-
]
|
898
|
-
e.backtrace_locations&.each do |loc|
|
899
|
-
break if loc.path == __FILE__
|
900
|
-
path = loc.absolute_path || loc.path
|
901
|
-
frames << {
|
902
|
-
columnNumber: 0,
|
903
|
-
functionName: loc.base_label,
|
904
|
-
lineNumber: loc.lineno - 1,
|
905
|
-
url: path
|
906
|
-
}
|
907
|
-
end
|
908
|
-
res[:exceptionDetails] = {
|
909
|
-
exceptionId: 1,
|
910
|
-
text: 'Uncaught',
|
911
|
-
lineNumber: 0,
|
912
|
-
columnNumber: 0,
|
913
|
-
exception: evaluate_result(result),
|
914
|
-
stackTrace: {
|
915
|
-
callFrames: frames
|
916
|
-
}
|
917
|
-
}
|
1076
|
+
res[:exceptionDetails] = exceptionDetails(e, 'Uncaught')
|
918
1077
|
ensure
|
919
1078
|
output = $stdout.string
|
920
1079
|
$stdout = orig_stdout
|
@@ -927,7 +1086,7 @@ module DEBUGGER__
|
|
927
1086
|
end
|
928
1087
|
|
929
1088
|
res[:result] = evaluate_result(result)
|
930
|
-
event! :
|
1089
|
+
event! :protocol_result, :evaluate, req, message: message, response: res, output: output
|
931
1090
|
when :scope
|
932
1091
|
fid = args.shift
|
933
1092
|
frame = @target_frames[fid]
|
@@ -950,7 +1109,7 @@ module DEBUGGER__
|
|
950
1109
|
vars.unshift variable(name, val)
|
951
1110
|
end
|
952
1111
|
end
|
953
|
-
event! :
|
1112
|
+
event! :protocol_result, :scope, req, vars
|
954
1113
|
when :properties
|
955
1114
|
oid = args.shift
|
956
1115
|
result = []
|
@@ -987,21 +1146,63 @@ module DEBUGGER__
|
|
987
1146
|
]
|
988
1147
|
end
|
989
1148
|
|
990
|
-
result += obj.
|
991
|
-
variable(iv,
|
1149
|
+
result += M_INSTANCE_VARIABLES.bind_call(obj).map{|iv|
|
1150
|
+
variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
|
992
1151
|
}
|
993
|
-
prop += [internalProperty('#class', obj
|
1152
|
+
prop += [internalProperty('#class', M_CLASS.bind_call(obj))]
|
994
1153
|
end
|
995
|
-
event! :
|
1154
|
+
event! :protocol_result, :properties, req, result: result, internalProperties: prop
|
1155
|
+
when :exception
|
1156
|
+
oid = args.shift
|
1157
|
+
exc = nil
|
1158
|
+
if obj = @obj_map[oid]
|
1159
|
+
exc = exceptionDetails obj, obj.to_s
|
1160
|
+
end
|
1161
|
+
event! :protocol_result, :exception, req, exceptionDetails: exc
|
1162
|
+
end
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
def exceptionDetails exc, text
|
1166
|
+
frames = [
|
1167
|
+
{
|
1168
|
+
columnNumber: 0,
|
1169
|
+
functionName: 'eval',
|
1170
|
+
lineNumber: 0,
|
1171
|
+
url: ''
|
1172
|
+
}
|
1173
|
+
]
|
1174
|
+
exc.backtrace_locations&.each do |loc|
|
1175
|
+
break if loc.path == __FILE__
|
1176
|
+
path = loc.absolute_path || loc.path
|
1177
|
+
frames << {
|
1178
|
+
columnNumber: 0,
|
1179
|
+
functionName: loc.base_label,
|
1180
|
+
lineNumber: loc.lineno - 1,
|
1181
|
+
url: path
|
1182
|
+
}
|
996
1183
|
end
|
1184
|
+
{
|
1185
|
+
exceptionId: 1,
|
1186
|
+
text: text,
|
1187
|
+
lineNumber: 0,
|
1188
|
+
columnNumber: 0,
|
1189
|
+
exception: evaluate_result(exc),
|
1190
|
+
stackTrace: {
|
1191
|
+
callFrames: frames
|
1192
|
+
}
|
1193
|
+
}
|
997
1194
|
end
|
998
1195
|
|
999
1196
|
def search_const b, expr
|
1000
1197
|
cs = expr.delete_prefix('::').split('::')
|
1001
|
-
[Object, *b.eval('Module.nesting')].reverse_each{|mod|
|
1198
|
+
[Object, *b.eval('::Module.nesting')].reverse_each{|mod|
|
1002
1199
|
if cs.all?{|c|
|
1003
1200
|
if mod.const_defined?(c)
|
1004
|
-
|
1201
|
+
begin
|
1202
|
+
mod = mod.const_get(c)
|
1203
|
+
rescue Exception
|
1204
|
+
false
|
1205
|
+
end
|
1005
1206
|
else
|
1006
1207
|
false
|
1007
1208
|
end
|
@@ -1045,25 +1246,29 @@ module DEBUGGER__
|
|
1045
1246
|
v = prop[:value]
|
1046
1247
|
v.delete :value
|
1047
1248
|
v[:subtype] = subtype if subtype
|
1048
|
-
v[:className] = obj.
|
1249
|
+
v[:className] = (klass = M_CLASS.bind_call(obj)).name || klass.to_s
|
1049
1250
|
end
|
1050
1251
|
prop
|
1051
1252
|
end
|
1052
1253
|
|
1053
1254
|
def preview_ value, hash, overflow
|
1255
|
+
# The reason for not using "map" method is to prevent the object overriding it from causing bugs.
|
1256
|
+
# https://github.com/ruby/debug/issues/781
|
1257
|
+
props = []
|
1258
|
+
hash.each{|k, v|
|
1259
|
+
pd = propertyDescriptor k, v
|
1260
|
+
props << {
|
1261
|
+
name: pd[:name],
|
1262
|
+
type: pd[:value][:type],
|
1263
|
+
value: pd[:value][:description]
|
1264
|
+
}
|
1265
|
+
}
|
1054
1266
|
{
|
1055
1267
|
type: value[:type],
|
1056
1268
|
subtype: value[:subtype],
|
1057
1269
|
description: value[:description],
|
1058
1270
|
overflow: overflow,
|
1059
|
-
properties:
|
1060
|
-
pd = propertyDescriptor k, v
|
1061
|
-
{
|
1062
|
-
name: pd[:name],
|
1063
|
-
type: pd[:value][:type],
|
1064
|
-
value: pd[:value][:description]
|
1065
|
-
}
|
1066
|
-
}
|
1271
|
+
properties: props
|
1067
1272
|
}
|
1068
1273
|
end
|
1069
1274
|
|