debug 1.4.0 → 1.5.0
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 +124 -0
- data/Gemfile +1 -0
- data/README.md +16 -16
- data/Rakefile +5 -5
- data/debug.gemspec +6 -4
- data/ext/debug/debug.c +84 -0
- data/ext/debug/extconf.rb +11 -0
- data/lib/debug/breakpoint.rb +24 -35
- data/lib/debug/client.rb +9 -3
- data/lib/debug/console.rb +34 -13
- data/lib/debug/frame_info.rb +4 -5
- data/lib/debug/local.rb +1 -1
- data/lib/debug/server.rb +65 -27
- data/lib/debug/server_cdp.rb +369 -148
- data/lib/debug/server_dap.rb +72 -75
- data/lib/debug/session.rb +225 -114
- data/lib/debug/source_repository.rb +91 -51
- data/lib/debug/thread_client.rb +94 -42
- data/lib/debug/tracer.rb +3 -9
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +6 -6
- metadata +4 -13
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
- data/.github/ISSUE_TEMPLATE/custom.md +0 -10
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
- data/.github/pull_request_template.md +0 -9
- data/.github/workflows/ruby.yml +0 -34
- data/.gitignore +0 -12
- data/bin/console +0 -14
- data/bin/gentest +0 -30
- data/bin/setup +0 -8
data/lib/debug/server_cdp.rb
CHANGED
@@ -27,7 +27,7 @@ module DEBUGGER__
|
|
27
27
|
ws_client.handshake port, path
|
28
28
|
ws_client.send id: 1, method: 'Target.getTargets'
|
29
29
|
|
30
|
-
|
30
|
+
loop do
|
31
31
|
res = ws_client.extract_data
|
32
32
|
case
|
33
33
|
when res['id'] == 1 && target_info = res.dig('result', 'targetInfos')
|
@@ -39,12 +39,23 @@ module DEBUGGER__
|
|
39
39
|
}
|
40
40
|
when res['id'] == 2
|
41
41
|
s_id = res.dig('result', 'sessionId')
|
42
|
-
|
43
|
-
|
42
|
+
ws_client.send sessionId: s_id, id: 3,
|
43
|
+
method: 'Page.enable'
|
44
|
+
when res['id'] == 3
|
45
|
+
s_id = res['sessionId']
|
46
|
+
ws_client.send sessionId: s_id, id: 4,
|
47
|
+
method: 'Page.getFrameTree'
|
48
|
+
when res['id'] == 4
|
49
|
+
s_id = res['sessionId']
|
50
|
+
f_id = res.dig('result', 'frameTree', 'frame', 'id')
|
51
|
+
ws_client.send sessionId: s_id, id: 5,
|
44
52
|
method: 'Page.navigate',
|
45
53
|
params: {
|
46
|
-
url: "devtools://devtools/bundled/inspector.html?ws=#{addr}"
|
54
|
+
url: "devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{addr}/#{SecureRandom.uuid}",
|
55
|
+
frameId: f_id
|
47
56
|
}
|
57
|
+
when res['method'] == 'Page.loadEventFired'
|
58
|
+
break
|
48
59
|
end
|
49
60
|
end
|
50
61
|
pid
|
@@ -91,16 +102,57 @@ module DEBUGGER__
|
|
91
102
|
end
|
92
103
|
end
|
93
104
|
|
105
|
+
module WebSocketUtils
|
106
|
+
class Frame
|
107
|
+
attr_reader :b
|
108
|
+
|
109
|
+
def initialize
|
110
|
+
@b = ''.b
|
111
|
+
end
|
112
|
+
|
113
|
+
def << obj
|
114
|
+
case obj
|
115
|
+
when String
|
116
|
+
@b << obj.b
|
117
|
+
when Enumerable
|
118
|
+
obj.each{|e| self << e}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def char bytes
|
123
|
+
@b << bytes
|
124
|
+
end
|
125
|
+
|
126
|
+
def ulonglong bytes
|
127
|
+
@b << [bytes].pack('Q>')
|
128
|
+
end
|
129
|
+
|
130
|
+
def uint16 bytes
|
131
|
+
@b << [bytes].pack('n*')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def show_protocol dir, msg
|
136
|
+
if DEBUGGER__::UI_CDP::SHOW_PROTOCOL
|
137
|
+
$stderr.puts "\#[#{dir}] #{msg}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
94
142
|
class WebSocketClient
|
143
|
+
include WebSocketUtils
|
144
|
+
|
95
145
|
def initialize s
|
96
146
|
@sock = s
|
97
147
|
end
|
98
148
|
|
99
149
|
def handshake port, path
|
100
150
|
key = SecureRandom.hex(11)
|
101
|
-
|
151
|
+
req = "GET #{path} HTTP/1.1\r\nHost: 127.0.0.1:#{port}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: #{key}==\r\n\r\n"
|
152
|
+
show_protocol :>, req
|
153
|
+
@sock.print req
|
102
154
|
res = @sock.readpartial 4092
|
103
|
-
|
155
|
+
show_protocol :<, res
|
104
156
|
|
105
157
|
if res.match /^Sec-WebSocket-Accept: (.*)\r\n/
|
106
158
|
correct_key = Base64.strict_encode64 Digest::SHA1.digest "#{key}==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
@@ -112,34 +164,39 @@ module DEBUGGER__
|
|
112
164
|
|
113
165
|
def send **msg
|
114
166
|
msg = JSON.generate(msg)
|
115
|
-
|
167
|
+
show_protocol :>, msg
|
168
|
+
frame = Frame.new
|
116
169
|
fin = 0b10000000
|
117
170
|
opcode = 0b00000001
|
118
|
-
frame
|
171
|
+
frame.char fin + opcode
|
119
172
|
|
120
173
|
mask = 0b10000000 # A client must mask all frames in a WebSocket Protocol.
|
121
174
|
bytesize = msg.bytesize
|
122
175
|
if bytesize < 126
|
123
176
|
payload_len = bytesize
|
177
|
+
frame.char mask + payload_len
|
124
178
|
elsif bytesize < 2 ** 16
|
125
179
|
payload_len = 0b01111110
|
126
|
-
|
127
|
-
|
180
|
+
frame.char mask + payload_len
|
181
|
+
frame.uint16 bytesize
|
182
|
+
elsif bytesize < 2 ** 64
|
128
183
|
payload_len = 0b01111111
|
129
|
-
|
184
|
+
frame.char mask + payload_len
|
185
|
+
frame.ulonglong bytesize
|
186
|
+
else
|
187
|
+
raise 'Bytesize is too big.'
|
130
188
|
end
|
131
189
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
190
|
+
masking_key = 4.times.map{
|
191
|
+
key = rand(1..255)
|
192
|
+
frame.char key
|
193
|
+
key
|
194
|
+
}
|
137
195
|
msg.bytes.each_with_index do |b, i|
|
138
|
-
|
196
|
+
frame.char(b ^ masking_key[i % 4])
|
139
197
|
end
|
140
198
|
|
141
|
-
frame.
|
142
|
-
@sock.print frame.pack 'c*'
|
199
|
+
@sock.print frame.b
|
143
200
|
end
|
144
201
|
|
145
202
|
def extract_data
|
@@ -158,9 +215,9 @@ module DEBUGGER__
|
|
158
215
|
payload_len = @sock.read(2).unpack('n*')[0]
|
159
216
|
end
|
160
217
|
|
161
|
-
|
162
|
-
|
163
|
-
|
218
|
+
msg = @sock.read payload_len
|
219
|
+
show_protocol :<, msg
|
220
|
+
JSON.parse msg
|
164
221
|
end
|
165
222
|
end
|
166
223
|
|
@@ -168,17 +225,21 @@ module DEBUGGER__
|
|
168
225
|
end
|
169
226
|
|
170
227
|
class WebSocketServer
|
228
|
+
include WebSocketUtils
|
229
|
+
|
171
230
|
def initialize s
|
172
231
|
@sock = s
|
173
232
|
end
|
174
233
|
|
175
234
|
def handshake
|
176
235
|
req = @sock.readpartial 4096
|
177
|
-
|
236
|
+
show_protocol '>', req
|
178
237
|
|
179
238
|
if req.match /^Sec-WebSocket-Key: (.*)\r\n/
|
180
239
|
accept = Base64.strict_encode64 Digest::SHA1.digest "#{$1}258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
181
|
-
|
240
|
+
res = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: #{accept}\r\n\r\n"
|
241
|
+
@sock.print res
|
242
|
+
show_protocol :<, res
|
182
243
|
else
|
183
244
|
"Unknown request: #{req}"
|
184
245
|
end
|
@@ -186,27 +247,31 @@ module DEBUGGER__
|
|
186
247
|
|
187
248
|
def send **msg
|
188
249
|
msg = JSON.generate(msg)
|
189
|
-
|
250
|
+
show_protocol :<, msg
|
251
|
+
frame = Frame.new
|
190
252
|
fin = 0b10000000
|
191
253
|
opcode = 0b00000001
|
192
|
-
frame
|
254
|
+
frame.char fin + opcode
|
193
255
|
|
194
256
|
mask = 0b00000000 # A server must not mask any frames in a WebSocket Protocol.
|
195
257
|
bytesize = msg.bytesize
|
196
258
|
if bytesize < 126
|
197
259
|
payload_len = bytesize
|
260
|
+
frame.char mask + payload_len
|
198
261
|
elsif bytesize < 2 ** 16
|
199
262
|
payload_len = 0b01111110
|
200
|
-
|
201
|
-
|
263
|
+
frame.char mask + payload_len
|
264
|
+
frame.uint16 bytesize
|
265
|
+
elsif bytesize < 2 ** 64
|
202
266
|
payload_len = 0b01111111
|
203
|
-
|
267
|
+
frame.char mask + payload_len
|
268
|
+
frame.ulonglong bytesize
|
269
|
+
else
|
270
|
+
raise 'Bytesize is too big.'
|
204
271
|
end
|
205
272
|
|
206
|
-
frame <<
|
207
|
-
frame.
|
208
|
-
frame.push *msg.bytes
|
209
|
-
@sock.print frame.pack 'c*'
|
273
|
+
frame << msg
|
274
|
+
@sock.print frame.b
|
210
275
|
end
|
211
276
|
|
212
277
|
def extract_data
|
@@ -234,7 +299,9 @@ module DEBUGGER__
|
|
234
299
|
masked = @sock.getbyte
|
235
300
|
unmasked << (masked ^ masking_key[n % 4])
|
236
301
|
end
|
237
|
-
|
302
|
+
msg = unmasked.pack 'c*'
|
303
|
+
show_protocol :>, msg
|
304
|
+
JSON.parse msg
|
238
305
|
end
|
239
306
|
end
|
240
307
|
|
@@ -265,63 +332,46 @@ module DEBUGGER__
|
|
265
332
|
@src_map = {}
|
266
333
|
loop do
|
267
334
|
req = @ws_server.extract_data
|
268
|
-
$stderr.puts '[>]' + req.inspect if SHOW_PROTOCOL
|
269
335
|
|
270
336
|
case req['method']
|
271
337
|
|
272
338
|
## boot/configuration
|
273
|
-
when '
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
id: SecureRandom.hex(16),
|
281
|
-
loaderId: SecureRandom.hex(16),
|
282
|
-
url: 'http://debuggee/',
|
283
|
-
securityOrigin: 'http://debuggee',
|
284
|
-
mimeType: 'text/plain' },
|
285
|
-
resources: [
|
286
|
-
]
|
287
|
-
}
|
288
|
-
send_event 'Debugger.scriptParsed',
|
289
|
-
scriptId: path,
|
290
|
-
url: "http://debuggee#{path}",
|
291
|
-
startLine: 0,
|
292
|
-
startColumn: 0,
|
293
|
-
endLine: src.count("\n"),
|
294
|
-
endColumn: 0,
|
295
|
-
executionContextId: 1,
|
296
|
-
hash: src.hash
|
339
|
+
when 'Debugger.getScriptSource'
|
340
|
+
@q_msg << req
|
341
|
+
when 'Debugger.enable'
|
342
|
+
send_response req
|
343
|
+
@q_msg << req
|
344
|
+
when 'Runtime.enable'
|
345
|
+
send_response req
|
297
346
|
send_event 'Runtime.executionContextCreated',
|
298
347
|
context: {
|
299
348
|
id: SecureRandom.hex(16),
|
300
349
|
origin: "http://#{@addr}",
|
301
350
|
name: ''
|
302
351
|
}
|
303
|
-
when '
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
352
|
+
when 'Runtime.getIsolateId'
|
353
|
+
send_response req,
|
354
|
+
id: SecureRandom.hex
|
355
|
+
when 'Runtime.terminateExecution'
|
356
|
+
send_response req
|
357
|
+
exit
|
308
358
|
when 'Page.startScreencast', 'Emulation.setTouchEmulationEnabled', 'Emulation.setEmitTouchEventsForMouse',
|
309
359
|
'Runtime.compileScript', 'Page.getResourceContent', 'Overlay.setPausedInDebuggerMessage',
|
310
|
-
'Runtime.releaseObjectGroup', 'Runtime.discardConsoleEntries', 'Log.clear'
|
360
|
+
'Runtime.releaseObjectGroup', 'Runtime.discardConsoleEntries', 'Log.clear', 'Runtime.runIfWaitingForDebugger'
|
311
361
|
send_response req
|
312
362
|
|
313
363
|
## control
|
314
364
|
when 'Debugger.resume'
|
315
|
-
@q_msg << 'c'
|
316
|
-
@q_msg << req
|
317
365
|
send_response req
|
318
366
|
send_event 'Debugger.resumed'
|
367
|
+
@q_msg << 'c'
|
368
|
+
@q_msg << req
|
319
369
|
when 'Debugger.stepOver'
|
320
370
|
begin
|
321
371
|
@session.check_postmortem
|
322
|
-
@q_msg << 'n'
|
323
372
|
send_response req
|
324
373
|
send_event 'Debugger.resumed'
|
374
|
+
@q_msg << 'n'
|
325
375
|
rescue PostmortemError
|
326
376
|
send_fail_response req,
|
327
377
|
code: INVALID_REQUEST,
|
@@ -332,9 +382,9 @@ module DEBUGGER__
|
|
332
382
|
when 'Debugger.stepInto'
|
333
383
|
begin
|
334
384
|
@session.check_postmortem
|
335
|
-
@q_msg << 's'
|
336
385
|
send_response req
|
337
386
|
send_event 'Debugger.resumed'
|
387
|
+
@q_msg << 's'
|
338
388
|
rescue PostmortemError
|
339
389
|
send_fail_response req,
|
340
390
|
code: INVALID_REQUEST,
|
@@ -345,9 +395,9 @@ module DEBUGGER__
|
|
345
395
|
when 'Debugger.stepOut'
|
346
396
|
begin
|
347
397
|
@session.check_postmortem
|
348
|
-
@q_msg << 'fin'
|
349
398
|
send_response req
|
350
399
|
send_event 'Debugger.resumed'
|
400
|
+
@q_msg << 'fin'
|
351
401
|
rescue PostmortemError
|
352
402
|
send_fail_response req,
|
353
403
|
code: INVALID_REQUEST,
|
@@ -366,41 +416,40 @@ module DEBUGGER__
|
|
366
416
|
|
367
417
|
# breakpoint
|
368
418
|
when 'Debugger.getPossibleBreakpoints'
|
369
|
-
|
370
|
-
line = req.dig('params', 'start', 'lineNumber')
|
371
|
-
src = get_source_code s_id
|
372
|
-
end_line = src.count("\n")
|
373
|
-
line = end_line if line > end_line
|
374
|
-
send_response req,
|
375
|
-
locations: [
|
376
|
-
{ scriptId: s_id,
|
377
|
-
lineNumber: line,
|
378
|
-
}
|
379
|
-
]
|
419
|
+
@q_msg << req
|
380
420
|
when 'Debugger.setBreakpointByUrl'
|
381
421
|
line = req.dig('params', 'lineNumber')
|
382
|
-
|
383
|
-
|
384
|
-
if url.match /http:\/\/debuggee(.*)/
|
385
|
-
path = $1
|
422
|
+
if regexp = req.dig('params', 'urlRegex')
|
423
|
+
path = regexp.match(/(.*)\|/)[1].gsub("\\", "")
|
386
424
|
cond = req.dig('params', 'condition')
|
387
425
|
src = get_source_code path
|
388
|
-
end_line = src.count
|
426
|
+
end_line = src.lines.count
|
389
427
|
line = end_line if line > end_line
|
390
|
-
b_id = "1:#{line}:#{
|
428
|
+
b_id = "1:#{line}:#{regexp}"
|
391
429
|
if cond != ''
|
392
430
|
SESSION.add_line_breakpoint(path, line + 1, cond: cond)
|
393
431
|
else
|
394
432
|
SESSION.add_line_breakpoint(path, line + 1)
|
395
433
|
end
|
396
434
|
bps[b_id] = bps.size
|
397
|
-
|
435
|
+
# Because we need to return scriptId, responses are returned in SESSION thread.
|
436
|
+
req['params']['scriptId'] = path
|
437
|
+
req['params']['lineNumber'] = line
|
438
|
+
req['params']['breakpointId'] = b_id
|
439
|
+
@q_msg << req
|
440
|
+
elsif url = req.dig('params', 'url')
|
441
|
+
b_id = "#{line}:#{url}"
|
442
|
+
send_response req,
|
443
|
+
breakpointId: b_id,
|
444
|
+
locations: []
|
445
|
+
elsif hash = req.dig('params', 'scriptHash')
|
446
|
+
b_id = "#{line}:#{hash}"
|
447
|
+
send_response req,
|
448
|
+
breakpointId: b_id,
|
449
|
+
locations: []
|
398
450
|
else
|
399
|
-
|
451
|
+
raise 'Unsupported'
|
400
452
|
end
|
401
|
-
send_response req,
|
402
|
-
breakpointId: b_id,
|
403
|
-
locations: locations
|
404
453
|
when 'Debugger.removeBreakpoint'
|
405
454
|
b_id = req.dig('params', 'breakpointId')
|
406
455
|
bps = del_bp bps, b_id
|
@@ -473,6 +522,7 @@ module DEBUGGER__
|
|
473
522
|
end
|
474
523
|
|
475
524
|
def cleanup_reader
|
525
|
+
super
|
476
526
|
Process.kill :KILL, @chrome_pid if @chrome_pid
|
477
527
|
end
|
478
528
|
|
@@ -512,21 +562,23 @@ module DEBUGGER__
|
|
512
562
|
|
513
563
|
class Session
|
514
564
|
def fail_response req, **result
|
515
|
-
@ui.respond_fail req, result
|
565
|
+
@ui.respond_fail req, **result
|
516
566
|
return :retry
|
517
567
|
end
|
518
568
|
|
519
569
|
INVALID_PARAMS = -32602
|
570
|
+
INTERNAL_ERROR = -32603
|
520
571
|
|
521
572
|
def process_protocol_request req
|
522
573
|
case req['method']
|
523
|
-
when 'Debugger.stepOver', 'Debugger.stepInto', 'Debugger.stepOut', 'Debugger.resume', 'Debugger.
|
574
|
+
when 'Debugger.stepOver', 'Debugger.stepInto', 'Debugger.stepOut', 'Debugger.resume', 'Debugger.enable'
|
524
575
|
@tc << [:cdp, :backtrace, req]
|
525
576
|
when 'Debugger.evaluateOnCallFrame'
|
526
577
|
frame_id = req.dig('params', 'callFrameId')
|
578
|
+
group = req.dig('params', 'objectGroup')
|
527
579
|
if fid = @frame_map[frame_id]
|
528
580
|
expr = req.dig('params', 'expression')
|
529
|
-
@tc << [:cdp, :evaluate, req, fid, expr]
|
581
|
+
@tc << [:cdp, :evaluate, req, fid, expr, group]
|
530
582
|
else
|
531
583
|
fail_response req,
|
532
584
|
code: INVALID_PARAMS,
|
@@ -544,7 +596,7 @@ module DEBUGGER__
|
|
544
596
|
@tc << [:cdp, :properties, req, oid]
|
545
597
|
when 'script', 'global'
|
546
598
|
# TODO: Support script and global types
|
547
|
-
@ui.respond req
|
599
|
+
@ui.respond req, result: []
|
548
600
|
return :retry
|
549
601
|
else
|
550
602
|
raise "Unknown type: #{ref.inspect}"
|
@@ -554,6 +606,50 @@ module DEBUGGER__
|
|
554
606
|
code: INVALID_PARAMS,
|
555
607
|
message: "'objectId' is an invalid"
|
556
608
|
end
|
609
|
+
when 'Debugger.getScriptSource'
|
610
|
+
s_id = req.dig('params', 'scriptId')
|
611
|
+
if src = @src_map[s_id]
|
612
|
+
@ui.respond req, scriptSource: src
|
613
|
+
else
|
614
|
+
fail_response req,
|
615
|
+
code: INVALID_PARAMS,
|
616
|
+
message: "'scriptId' is an invalid"
|
617
|
+
end
|
618
|
+
return :retry
|
619
|
+
when 'Debugger.getPossibleBreakpoints'
|
620
|
+
s_id = req.dig('params', 'start', 'scriptId')
|
621
|
+
if src = @src_map[s_id]
|
622
|
+
lineno = req.dig('params', 'start', 'lineNumber')
|
623
|
+
end_line = src.lines.count
|
624
|
+
lineno = end_line if lineno > end_line
|
625
|
+
@ui.respond req,
|
626
|
+
locations: [{
|
627
|
+
scriptId: s_id,
|
628
|
+
lineNumber: lineno
|
629
|
+
}]
|
630
|
+
else
|
631
|
+
fail_response req,
|
632
|
+
code: INVALID_PARAMS,
|
633
|
+
message: "'scriptId' is an invalid"
|
634
|
+
end
|
635
|
+
return :retry
|
636
|
+
when 'Debugger.setBreakpointByUrl'
|
637
|
+
path = req.dig('params', 'scriptId')
|
638
|
+
if s_id = @scr_id_map[path]
|
639
|
+
lineno = req.dig('params', 'lineNumber')
|
640
|
+
b_id = req.dig('params', 'breakpointId')
|
641
|
+
@ui.respond req,
|
642
|
+
breakpointId: b_id,
|
643
|
+
locations: [{
|
644
|
+
scriptId: s_id,
|
645
|
+
lineNumber: lineno
|
646
|
+
}]
|
647
|
+
else
|
648
|
+
fail_response req,
|
649
|
+
code: INTERNAL_ERROR,
|
650
|
+
message: 'The target script is not found...'
|
651
|
+
end
|
652
|
+
return :retry
|
557
653
|
end
|
558
654
|
end
|
559
655
|
|
@@ -565,20 +661,31 @@ module DEBUGGER__
|
|
565
661
|
result[:callFrames].each.with_index do |frame, i|
|
566
662
|
frame_id = frame[:callFrameId]
|
567
663
|
@frame_map[frame_id] = i
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
@
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
endLine: src.count("\n"),
|
577
|
-
endColumn: 0,
|
578
|
-
executionContextId: @script_paths.size + 1,
|
579
|
-
hash: src.hash
|
580
|
-
@script_paths << s_id
|
664
|
+
path = frame[:url]
|
665
|
+
unless s_id = @scr_id_map[path]
|
666
|
+
s_id = (@scr_id_map.size + 1).to_s
|
667
|
+
@scr_id_map[path] = s_id
|
668
|
+
if path && File.exist?(path)
|
669
|
+
src = File.read(path)
|
670
|
+
end
|
671
|
+
@src_map[s_id] = src
|
581
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',
|
681
|
+
scriptId: s_id,
|
682
|
+
url: frame[:url],
|
683
|
+
startLine: 0,
|
684
|
+
startColumn: 0,
|
685
|
+
endLine: lineno,
|
686
|
+
endColumn: 0,
|
687
|
+
executionContextId: 1,
|
688
|
+
hash: src.hash.inspect
|
582
689
|
|
583
690
|
frame[:scopeChain].each {|s|
|
584
691
|
oid = s.dig(:object, :objectId)
|
@@ -597,6 +704,33 @@ module DEBUGGER__
|
|
597
704
|
code: INVALID_PARAMS,
|
598
705
|
message: message
|
599
706
|
else
|
707
|
+
src = req.dig('params', 'expression')
|
708
|
+
s_id = (@src_map.size + 1).to_s
|
709
|
+
@src_map[s_id] = src
|
710
|
+
lineno = src.lines.count
|
711
|
+
@ui.fire_event 'Debugger.scriptParsed',
|
712
|
+
scriptId: s_id,
|
713
|
+
url: '',
|
714
|
+
startLine: 0,
|
715
|
+
startColumn: 0,
|
716
|
+
endLine: lineno,
|
717
|
+
endColumn: 0,
|
718
|
+
executionContextId: 1,
|
719
|
+
hash: src.hash.inspect
|
720
|
+
if exc = result.dig(:response, :exceptionDetails)
|
721
|
+
exc[:stackTrace][:callFrames].each{|frame|
|
722
|
+
if frame[:url].empty?
|
723
|
+
frame[:scriptId] = s_id
|
724
|
+
else
|
725
|
+
path = frame[:url]
|
726
|
+
unless s_id = @scr_id_map[path]
|
727
|
+
s_id = (@scr_id_map.size + 1).to_s
|
728
|
+
@scr_id_map[path] = s_id
|
729
|
+
end
|
730
|
+
frame[:scriptId] = s_id
|
731
|
+
end
|
732
|
+
}
|
733
|
+
end
|
600
734
|
rs = result.dig(:response, :result)
|
601
735
|
[rs].each{|obj|
|
602
736
|
if oid = obj[:objectId]
|
@@ -651,22 +785,25 @@ module DEBUGGER__
|
|
651
785
|
exception = frame.raised_exception if frame == current_frame && frame.has_raised_exception
|
652
786
|
|
653
787
|
path = frame.realpath || frame.path
|
654
|
-
|
655
|
-
|
788
|
+
|
789
|
+
if frame.iseq.nil?
|
790
|
+
lineno = 0
|
791
|
+
else
|
792
|
+
lineno = frame.iseq.first_line - 1
|
656
793
|
end
|
657
794
|
|
658
795
|
{
|
659
796
|
callFrameId: SecureRandom.hex(16),
|
660
797
|
functionName: frame.name,
|
661
798
|
functionLocation: {
|
662
|
-
scriptId:
|
663
|
-
lineNumber:
|
799
|
+
# scriptId: N, # filled by SESSION
|
800
|
+
lineNumber: lineno
|
664
801
|
},
|
665
802
|
location: {
|
666
|
-
scriptId:
|
803
|
+
# scriptId: N, # filled by SESSION
|
667
804
|
lineNumber: frame.location.lineno - 1 # The line number is 0-based.
|
668
805
|
},
|
669
|
-
url:
|
806
|
+
url: path,
|
670
807
|
scopeChain: [
|
671
808
|
{
|
672
809
|
type: 'local',
|
@@ -704,19 +841,18 @@ module DEBUGGER__
|
|
704
841
|
event! :cdp_result, :backtrace, req, result
|
705
842
|
when :evaluate
|
706
843
|
res = {}
|
707
|
-
fid, expr = args
|
844
|
+
fid, expr, group = args
|
708
845
|
frame = @target_frames[fid]
|
709
846
|
message = nil
|
710
847
|
|
711
|
-
if frame && (b = frame.
|
712
|
-
|
713
|
-
special_local_variables current_frame do |name, var|
|
848
|
+
if frame && (b = frame.eval_binding)
|
849
|
+
special_local_variables frame do |name, var|
|
714
850
|
b.local_variable_set(name, var) if /\%/ !~name
|
715
851
|
end
|
716
852
|
|
717
853
|
result = nil
|
718
854
|
|
719
|
-
case
|
855
|
+
case group
|
720
856
|
when 'popover'
|
721
857
|
case expr
|
722
858
|
# Chrome doesn't read instance variables
|
@@ -727,14 +863,12 @@ module DEBUGGER__
|
|
727
863
|
break false
|
728
864
|
end
|
729
865
|
} and (message = "Error: Not defined global variable: #{expr.inspect}")
|
730
|
-
when /(\A[A-Z][
|
866
|
+
when /(\A((::[A-Z]|[A-Z])\w*)+)/
|
731
867
|
unless result = search_const(b, $1)
|
732
868
|
message = "Error: Not defined constant: #{expr.inspect}"
|
733
869
|
end
|
734
870
|
else
|
735
871
|
begin
|
736
|
-
# try to check local variables
|
737
|
-
b.local_variable_defined?(expr) or raise NameError
|
738
872
|
result = b.local_variable_get(expr)
|
739
873
|
rescue NameError
|
740
874
|
# try to check method
|
@@ -745,7 +879,7 @@ module DEBUGGER__
|
|
745
879
|
end
|
746
880
|
end
|
747
881
|
end
|
748
|
-
|
882
|
+
when 'console', 'watch-group'
|
749
883
|
begin
|
750
884
|
orig_stdout = $stdout
|
751
885
|
$stdout = StringIO.new
|
@@ -753,18 +887,40 @@ module DEBUGGER__
|
|
753
887
|
rescue Exception => e
|
754
888
|
result = e
|
755
889
|
b = result.backtrace.map{|e| " #{e}\n"}
|
756
|
-
|
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
|
757
908
|
res[:exceptionDetails] = {
|
758
909
|
exceptionId: 1,
|
759
910
|
text: 'Uncaught',
|
760
|
-
lineNumber:
|
911
|
+
lineNumber: 0,
|
761
912
|
columnNumber: 0,
|
762
913
|
exception: evaluate_result(result),
|
914
|
+
stackTrace: {
|
915
|
+
callFrames: frames
|
916
|
+
}
|
763
917
|
}
|
764
918
|
ensure
|
765
919
|
output = $stdout.string
|
766
920
|
$stdout = orig_stdout
|
767
921
|
end
|
922
|
+
else
|
923
|
+
message = "Error: unknown objectGroup: #{group}"
|
768
924
|
end
|
769
925
|
else
|
770
926
|
result = Exception.new("Error: Can not evaluate on this frame")
|
@@ -816,32 +972,32 @@ module DEBUGGER__
|
|
816
972
|
}
|
817
973
|
when String
|
818
974
|
prop = [
|
819
|
-
|
820
|
-
|
975
|
+
internalProperty('#length', obj.length),
|
976
|
+
internalProperty('#encoding', obj.encoding)
|
821
977
|
]
|
822
978
|
when Class, Module
|
823
979
|
result = obj.instance_variables.map{|iv|
|
824
980
|
variable(iv, obj.instance_variable_get(iv))
|
825
981
|
}
|
826
|
-
prop = [
|
982
|
+
prop = [internalProperty('%ancestors', obj.ancestors[1..])]
|
827
983
|
when Range
|
828
984
|
prop = [
|
829
|
-
|
830
|
-
|
985
|
+
internalProperty('#begin', obj.begin),
|
986
|
+
internalProperty('#end', obj.end),
|
831
987
|
]
|
832
988
|
end
|
833
989
|
|
834
990
|
result += obj.instance_variables.map{|iv|
|
835
991
|
variable(iv, obj.instance_variable_get(iv))
|
836
992
|
}
|
837
|
-
prop += [
|
993
|
+
prop += [internalProperty('#class', obj.class)]
|
838
994
|
end
|
839
995
|
event! :cdp_result, :properties, req, result: result, internalProperties: prop
|
840
996
|
end
|
841
997
|
end
|
842
998
|
|
843
999
|
def search_const b, expr
|
844
|
-
cs = expr.split('::')
|
1000
|
+
cs = expr.delete_prefix('::').split('::')
|
845
1001
|
[Object, *b.eval('Module.nesting')].reverse_each{|mod|
|
846
1002
|
if cs.all?{|c|
|
847
1003
|
if mod.const_defined?(c)
|
@@ -862,14 +1018,15 @@ module DEBUGGER__
|
|
862
1018
|
v[:value]
|
863
1019
|
end
|
864
1020
|
|
865
|
-
def
|
1021
|
+
def internalProperty name, obj
|
866
1022
|
v = variable name, obj
|
867
1023
|
v.delete :configurable
|
868
1024
|
v.delete :enumerable
|
869
1025
|
v
|
870
1026
|
end
|
871
1027
|
|
872
|
-
def
|
1028
|
+
def propertyDescriptor_ name, obj, type, description: nil, subtype: nil
|
1029
|
+
description = DEBUGGER__.safe_inspect(obj, short: true) if description.nil?
|
873
1030
|
oid = rand.to_s
|
874
1031
|
@obj_map[oid] = obj
|
875
1032
|
prop = {
|
@@ -893,30 +1050,94 @@ module DEBUGGER__
|
|
893
1050
|
prop
|
894
1051
|
end
|
895
1052
|
|
1053
|
+
def preview_ value, hash, overflow
|
1054
|
+
{
|
1055
|
+
type: value[:type],
|
1056
|
+
subtype: value[:subtype],
|
1057
|
+
description: value[:description],
|
1058
|
+
overflow: overflow,
|
1059
|
+
properties: hash.map{|k, v|
|
1060
|
+
pd = propertyDescriptor k, v
|
1061
|
+
{
|
1062
|
+
name: pd[:name],
|
1063
|
+
type: pd[:value][:type],
|
1064
|
+
value: pd[:value][:description]
|
1065
|
+
}
|
1066
|
+
}
|
1067
|
+
}
|
1068
|
+
end
|
1069
|
+
|
896
1070
|
def variable name, obj
|
1071
|
+
pd = propertyDescriptor name, obj
|
1072
|
+
case obj
|
1073
|
+
when Array
|
1074
|
+
pd[:value][:preview] = preview name, obj
|
1075
|
+
obj.each_with_index{|item, idx|
|
1076
|
+
if valuePreview = preview(idx.to_s, item)
|
1077
|
+
pd[:value][:preview][:properties][idx][:valuePreview] = valuePreview
|
1078
|
+
end
|
1079
|
+
}
|
1080
|
+
when Hash
|
1081
|
+
pd[:value][:preview] = preview name, obj
|
1082
|
+
obj.each_with_index{|item, idx|
|
1083
|
+
key, val = item
|
1084
|
+
if valuePreview = preview(key, val)
|
1085
|
+
pd[:value][:preview][:properties][idx][:valuePreview] = valuePreview
|
1086
|
+
end
|
1087
|
+
}
|
1088
|
+
end
|
1089
|
+
pd
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
def preview name, obj
|
1093
|
+
case obj
|
1094
|
+
when Array
|
1095
|
+
pd = propertyDescriptor name, obj
|
1096
|
+
overflow = false
|
1097
|
+
if obj.size > 100
|
1098
|
+
obj = obj[0..99]
|
1099
|
+
overflow = true
|
1100
|
+
end
|
1101
|
+
hash = obj.each_with_index.to_h{|o, i| [i.to_s, o]}
|
1102
|
+
preview_ pd[:value], hash, overflow
|
1103
|
+
when Hash
|
1104
|
+
pd = propertyDescriptor name, obj
|
1105
|
+
overflow = false
|
1106
|
+
if obj.size > 100
|
1107
|
+
obj = obj.to_a[0..99].to_h
|
1108
|
+
overflow = true
|
1109
|
+
end
|
1110
|
+
preview_ pd[:value], obj, overflow
|
1111
|
+
else
|
1112
|
+
nil
|
1113
|
+
end
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
def propertyDescriptor name, obj
|
897
1117
|
case obj
|
898
1118
|
when Array
|
899
|
-
|
1119
|
+
propertyDescriptor_ name, obj, 'object', subtype: 'array'
|
900
1120
|
when Hash
|
901
|
-
|
1121
|
+
propertyDescriptor_ name, obj, 'object', subtype: 'map'
|
902
1122
|
when String
|
903
|
-
|
904
|
-
when Class, Module, Struct, Range, Time, Method
|
905
|
-
variable_ name, obj, 'object'
|
1123
|
+
propertyDescriptor_ name, obj, 'string', description: obj
|
906
1124
|
when TrueClass, FalseClass
|
907
|
-
|
1125
|
+
propertyDescriptor_ name, obj, 'boolean'
|
908
1126
|
when Symbol
|
909
|
-
|
1127
|
+
propertyDescriptor_ name, obj, 'symbol'
|
910
1128
|
when Integer, Float
|
911
|
-
|
1129
|
+
propertyDescriptor_ name, obj, 'number'
|
912
1130
|
when Exception
|
913
|
-
bt =
|
914
|
-
if log = obj.
|
915
|
-
|
1131
|
+
bt = ''
|
1132
|
+
if log = obj.backtrace_locations
|
1133
|
+
log.each do |loc|
|
1134
|
+
break if loc.path == __FILE__
|
1135
|
+
bt += " #{loc}\n"
|
1136
|
+
end
|
916
1137
|
end
|
917
|
-
|
1138
|
+
propertyDescriptor_ name, obj, 'object', description: "#{obj.inspect}\n#{bt}", subtype: 'error'
|
918
1139
|
else
|
919
|
-
|
1140
|
+
propertyDescriptor_ name, obj, 'object'
|
920
1141
|
end
|
921
1142
|
end
|
922
1143
|
end
|