byebug-dap 0.1.2 → 0.1.3
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/CHANGELOG.md +14 -3
- data/README.md +5 -5
- data/bin/byebug-dap +25 -8
- data/lib/byebug/dap.rb +48 -18
- data/lib/byebug/dap/command.rb +250 -0
- data/lib/byebug/dap/command_processor.rb +50 -49
- data/lib/byebug/dap/commands/attach.rb +13 -0
- data/lib/byebug/dap/commands/breakpoint_locations.rb +28 -0
- data/lib/byebug/dap/commands/configuration_done.rb +12 -0
- data/lib/byebug/dap/commands/continue.rb +18 -0
- data/lib/byebug/dap/commands/disconnect.rb +16 -0
- data/lib/byebug/dap/commands/evaluate.rb +27 -0
- data/lib/byebug/dap/commands/exception_info.rb +40 -0
- data/lib/byebug/dap/commands/initialize.rb +27 -0
- data/lib/byebug/dap/commands/launch.rb +13 -0
- data/lib/byebug/dap/commands/next.rb +20 -0
- data/lib/byebug/dap/commands/pause.rb +20 -0
- data/lib/byebug/dap/commands/scopes.rb +56 -0
- data/lib/byebug/dap/commands/set_breakpoints.rb +48 -0
- data/lib/byebug/dap/commands/set_exception_breakpoints.rb +28 -0
- data/lib/byebug/dap/commands/set_function_breakpoints.rb +96 -0
- data/lib/byebug/dap/commands/source.rb +12 -0
- data/lib/byebug/dap/commands/stack_trace.rb +50 -0
- data/lib/byebug/dap/commands/step_in.rb +24 -0
- data/lib/byebug/dap/commands/step_out.rb +21 -0
- data/lib/byebug/dap/commands/threads.rb +20 -0
- data/lib/byebug/dap/commands/variables.rb +33 -0
- data/lib/byebug/dap/contextual_command.rb +30 -0
- data/lib/byebug/dap/helpers/captured_io.rb +65 -0
- data/lib/byebug/dap/helpers/captured_output.rb +21 -0
- data/lib/byebug/dap/{channel.rb → helpers/channel.rb} +0 -0
- data/lib/byebug/dap/{child_spawned_event_body.rb → helpers/child_spawned_event_body.rb} +0 -0
- data/lib/byebug/dap/{handles.rb → helpers/handles.rb} +0 -0
- data/lib/byebug/dap/{invalid_request_argument_error.rb → helpers/invalid_request_argument_error.rb} +0 -0
- data/lib/byebug/dap/helpers/safe_helpers.rb +17 -0
- data/lib/byebug/dap/helpers/scalar.rb +19 -0
- data/lib/byebug/dap/helpers/stdio.rb +21 -0
- data/lib/byebug/dap/helpers/value_helpers.rb +60 -0
- data/lib/byebug/dap/server.rb +52 -36
- data/lib/byebug/dap/session.rb +176 -0
- data/lib/byebug/gem.rb +11 -0
- metadata +38 -10
- data/lib/byebug/dap/controller.rb +0 -252
- data/lib/byebug/dap/interface.rb +0 -303
- data/lib/byebug/dap/safe_helpers.rb +0 -55
File without changes
|
File without changes
|
File without changes
|
data/lib/byebug/dap/{invalid_request_argument_error.rb → helpers/invalid_request_argument_error.rb}
RENAMED
File without changes
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Byebug
|
2
|
+
module DAP
|
3
|
+
module SafeHelpers
|
4
|
+
def safe(target, method, *args, &block)
|
5
|
+
if method.is_a?(Array) && args.empty?
|
6
|
+
method.each { |m| target = target.__send__(m) }
|
7
|
+
target
|
8
|
+
else
|
9
|
+
target.__send__(method, *args)
|
10
|
+
end
|
11
|
+
rescue StandardError => e
|
12
|
+
log "\n! #{e.message} (#{e.class})", *e.backtrace if Debug.evaluate
|
13
|
+
block.parameters.empty? ? yield : yield(e)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
module Scalar
|
3
|
+
def ===(value)
|
4
|
+
case value
|
5
|
+
when nil, true, false
|
6
|
+
return true
|
7
|
+
when ::String, ::Symbol, ::Numeric
|
8
|
+
return true
|
9
|
+
when ::Time, ::Range
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
return true if defined?(::Date) && ::Date === value
|
14
|
+
return true if defined?(::DateTime) && ::DateTime === value
|
15
|
+
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
class STDIO
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@in = STDIN.dup
|
7
|
+
@out = STDOUT.dup
|
8
|
+
@in.sync = true
|
9
|
+
@out.sync = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def close; @in.close; @out.close; end
|
13
|
+
def flush; @in.flush; @out.flush; end
|
14
|
+
def fsync; @in.fsync; @out.fsync; end
|
15
|
+
def closed?; @in.closed? || @out.closed?; end
|
16
|
+
|
17
|
+
def_delegators :@in, :close_read, :bytes, :chars, :codepoints, :each, :each_byte, :each_char, :each_codepoint, :each_line, :getbyte, :getc, :gets, :lines, :pread, :print, :printf, :read, :read_nonblock, :readbyte, :readchar, :readline, :readlines, :readpartial, :sysread, :ungetbyte, :ungetc
|
18
|
+
def_delegators :@out, :<<, :close_write, :putc, :puts, :pwrite, :syswrite, :write, :write_nonblock
|
19
|
+
public :<<, :bytes, :chars, :close_read, :close_write, :codepoints, :each, :each_byte, :each_char, :each_codepoint, :each_line, :getbyte, :getc, :gets, :lines, :pread, :print, :printf, :putc, :puts, :pwrite, :read, :read_nonblock, :readbyte, :readchar, :readline, :readlines, :readpartial, :sysread, :syswrite, :ungetbyte, :ungetc, :write, :write_nonblock
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Byebug::DAP
|
2
|
+
module ValueHelpers
|
3
|
+
def prepare_value(val)
|
4
|
+
str = safe(val, :inspect) { safe(val, :to_s) { return yield } }
|
5
|
+
cls = safe(val, :class) { nil }
|
6
|
+
typ = safe(cls, :name) { safe(cls, :to_s) { nil } }
|
7
|
+
|
8
|
+
scalar = safe(-> { Scalar === val }, :call) { true }
|
9
|
+
return str, typ, [], [] if scalar
|
10
|
+
|
11
|
+
named = safe(val, :instance_variables) { [] }
|
12
|
+
named += safe(val, :class_variables) { [] }
|
13
|
+
# named += safe(val, :constants) { [] }
|
14
|
+
|
15
|
+
indexed = safe(-> {
|
16
|
+
return (0...val.size).to_a if val.is_a?(Array)
|
17
|
+
return val.keys if val.respond_to?(:keys) && val.respond_to?(:[])
|
18
|
+
[]
|
19
|
+
}, :call) { [] }
|
20
|
+
|
21
|
+
return str, typ, named, indexed
|
22
|
+
end
|
23
|
+
|
24
|
+
def prepare_value_response(thnum, frnum, kind, name: nil, &block)
|
25
|
+
err = nil
|
26
|
+
raw = execute_on_thread(thnum, block) { |e| err = e; nil }
|
27
|
+
|
28
|
+
if err.nil?
|
29
|
+
value, type, named, indexed = prepare_value(raw) { |e| next exception_description(e), nil, [], [] }
|
30
|
+
else
|
31
|
+
type, named, indexed = nil, [], []
|
32
|
+
if err.is_a?(CommandProcessor::TimeoutError)
|
33
|
+
name = err.context.thread.name
|
34
|
+
value = "*Thread ##{err.context.thnum} #{name ? '(' + name + ')' : ''} unresponsive*"
|
35
|
+
else
|
36
|
+
value = exception_description(err)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
case kind
|
41
|
+
when :variable
|
42
|
+
klazz = ::DAP::Variable
|
43
|
+
args = { name: safe(name, :to_s) { safe(name, :inspect) { '???' } }, value: value, type: type }
|
44
|
+
when :evaluate
|
45
|
+
klazz = ::DAP::EvaluateResponseBody
|
46
|
+
args = { result: value, type: type }
|
47
|
+
end
|
48
|
+
|
49
|
+
if named.empty? && indexed.empty?
|
50
|
+
args[:variablesReference] = 0
|
51
|
+
else
|
52
|
+
args[:variablesReference] = @session.save_variables(thnum, frnum, kind, raw, named, indexed)
|
53
|
+
args[:namedVariables] = named.size
|
54
|
+
args[:indexedVariables] = indexed.size
|
55
|
+
end
|
56
|
+
|
57
|
+
klazz.new(args).validate!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/byebug/dap/server.rb
CHANGED
@@ -1,30 +1,13 @@
|
|
1
1
|
module Byebug
|
2
2
|
module DAP
|
3
3
|
class Server
|
4
|
-
|
5
|
-
extend Forwardable
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
@in = STDIN
|
9
|
-
@out = STDOUT
|
10
|
-
STDIN.sync = true
|
11
|
-
STDOUT.sync = true
|
12
|
-
end
|
13
|
-
|
14
|
-
def close; @in.close; @out.close; end
|
15
|
-
def flush; @in.flush; @out.flush; end
|
16
|
-
def fsync; @in.fsync; @out.fsync; end
|
17
|
-
|
18
|
-
def_delegators :@in, :close_read, :bytes, :chars, :codepoints, :each, :each_byte, :each_char, :each_codepoint, :each_line, :getbyte, :getc, :gets, :lines, :pread, :print, :printf, :read, :read_nonblock, :readbyte, :readchar, :readline, :readlines, :readpartial, :sysread, :ungetbyte, :ungetc
|
19
|
-
def_delegators :@out, :<<, :close_write, :putc, :puts, :pwrite, :syswrite, :write, :write_nonblock
|
20
|
-
public :<<, :bytes, :chars, :close_read, :close_write, :codepoints, :each, :each_byte, :each_char, :each_codepoint, :each_line, :getbyte, :getc, :gets, :lines, :pread, :print, :printf, :putc, :puts, :pwrite, :read, :read_nonblock, :readbyte, :readchar, :readline, :readlines, :readpartial, :sysread, :syswrite, :ungetbyte, :ungetc, :write, :write_nonblock
|
21
|
-
end
|
22
|
-
|
23
|
-
def initialize
|
4
|
+
def initialize(capture: true, forward: true)
|
24
5
|
@started = false
|
25
6
|
@mu = Mutex.new
|
26
7
|
@cond = ConditionVariable.new
|
27
8
|
@configured = false
|
9
|
+
@capture = capture
|
10
|
+
@forward = forward
|
28
11
|
end
|
29
12
|
|
30
13
|
def start(host, port = 0)
|
@@ -42,21 +25,26 @@ module Byebug
|
|
42
25
|
return if @started
|
43
26
|
@started = true
|
44
27
|
|
45
|
-
|
28
|
+
@ios = CapturedIO.new(@forward, @forward) if @capture
|
29
|
+
launch_accept TCPServer.new(host, port)
|
46
30
|
end
|
47
31
|
|
48
32
|
def start_unix(socket)
|
49
33
|
return if @started
|
50
34
|
@started = true
|
51
35
|
|
52
|
-
|
36
|
+
@ios = CapturedIO.new(@forward, @forward) if @capture
|
37
|
+
launch_accept UNIXServer.new(socket)
|
53
38
|
end
|
54
39
|
|
55
40
|
def start_stdio
|
56
41
|
return if @started
|
57
42
|
@started = true
|
58
43
|
|
59
|
-
|
44
|
+
stream = STDIO.new
|
45
|
+
STDIN.close
|
46
|
+
@ios = CapturedIO.new(false, @forward) if @capture
|
47
|
+
launch stream
|
60
48
|
end
|
61
49
|
|
62
50
|
def wait_for_client
|
@@ -71,30 +59,58 @@ module Byebug
|
|
71
59
|
|
72
60
|
private
|
73
61
|
|
74
|
-
def
|
62
|
+
def log
|
63
|
+
if @ios
|
64
|
+
@ios.log
|
65
|
+
elsif defined?(LOG)
|
66
|
+
LOG
|
67
|
+
else
|
68
|
+
STDERR
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def launch(stream)
|
75
73
|
DebugThread.new do
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
else
|
81
|
-
debug server
|
82
|
-
end
|
74
|
+
debug stream
|
75
|
+
|
76
|
+
ensure
|
77
|
+
@ios&.restore
|
83
78
|
end
|
84
79
|
|
85
80
|
self
|
86
81
|
end
|
87
82
|
|
88
|
-
def
|
89
|
-
|
90
|
-
|
83
|
+
def launch_accept(server)
|
84
|
+
DebugThread.new do
|
85
|
+
while socket = server.accept
|
86
|
+
debug socket
|
87
|
+
end
|
88
|
+
|
89
|
+
ensure
|
90
|
+
@ios&.restore
|
91
|
+
end
|
92
|
+
|
93
|
+
self
|
94
|
+
end
|
91
95
|
|
92
|
-
|
96
|
+
def debug(connection)
|
97
|
+
session = Byebug::DAP::Session.new(connection, @ios) do
|
93
98
|
@mu.synchronize do
|
94
99
|
@configured = true
|
95
100
|
@cond.broadcast
|
96
101
|
end
|
97
|
-
end
|
102
|
+
end
|
103
|
+
|
104
|
+
session.execute
|
105
|
+
|
106
|
+
rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
|
107
|
+
log.puts "Client disconnected"
|
108
|
+
|
109
|
+
rescue StandardError => e
|
110
|
+
log.puts "#{e.message} (#{e.class})", *e.backtrace
|
111
|
+
|
112
|
+
ensure
|
113
|
+
session.stop!
|
98
114
|
end
|
99
115
|
end
|
100
116
|
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
module Byebug
|
2
|
+
module DAP
|
3
|
+
class Session
|
4
|
+
include SafeHelpers
|
5
|
+
|
6
|
+
@@children = []
|
7
|
+
|
8
|
+
def self.child_spawned(name, pid, socket)
|
9
|
+
child = ChildSpawnedEventBody.new(name: name, pid: pid, socket: socket)
|
10
|
+
@@children << child
|
11
|
+
|
12
|
+
session = Context.interface
|
13
|
+
session.event! child if session.is_a?(Byebug::DAP::Session)
|
14
|
+
|
15
|
+
return true
|
16
|
+
|
17
|
+
rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
|
18
|
+
return false
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(connection, ios, &block)
|
22
|
+
@connection = connection
|
23
|
+
@ios = ios
|
24
|
+
@on_configured = block
|
25
|
+
@pid = Process.pid
|
26
|
+
@log_points = {}
|
27
|
+
@frame_ids = Handles.new
|
28
|
+
@variable_refs = Handles.new
|
29
|
+
@trace = TracePoint.new(:thread_begin, :thread_end) { |t| process_trace t }
|
30
|
+
|
31
|
+
notify_of_children
|
32
|
+
end
|
33
|
+
|
34
|
+
def log(*args)
|
35
|
+
logger =
|
36
|
+
if @ios
|
37
|
+
@ios.log
|
38
|
+
elsif defined?(LOG)
|
39
|
+
LOG
|
40
|
+
else
|
41
|
+
STDERR
|
42
|
+
end
|
43
|
+
logger.puts(*args)
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute
|
47
|
+
Context.interface = self
|
48
|
+
Context.processor = Byebug::DAP::CommandProcessor
|
49
|
+
|
50
|
+
Command.execute(self, receive) until @connection.closed?
|
51
|
+
|
52
|
+
Context.interface = LocalInterface.new
|
53
|
+
end
|
54
|
+
|
55
|
+
def invalidate_handles!
|
56
|
+
@frame_ids.clear!
|
57
|
+
@variable_refs.clear!
|
58
|
+
end
|
59
|
+
|
60
|
+
def start!(mode)
|
61
|
+
@trace.enable
|
62
|
+
Byebug.mode = mode
|
63
|
+
Byebug.start
|
64
|
+
@exit_on_stop = true if mode == :launched
|
65
|
+
end
|
66
|
+
|
67
|
+
def configured!
|
68
|
+
return unless @on_configured
|
69
|
+
|
70
|
+
callback, @on_configured = @on_configured, callback
|
71
|
+
callback.call
|
72
|
+
end
|
73
|
+
|
74
|
+
def stop!
|
75
|
+
exit if @exit_on_stop && @pid == Process.pid
|
76
|
+
|
77
|
+
Byebug.mode = :off
|
78
|
+
Byebug.stop
|
79
|
+
@trace.disable
|
80
|
+
@connection.close
|
81
|
+
end
|
82
|
+
|
83
|
+
def event!(event, **values)
|
84
|
+
if (cls = event.class.name.split('::').last) && cls.end_with?('EventBody')
|
85
|
+
body, event = event, cls[0].downcase + cls[1...-9]
|
86
|
+
|
87
|
+
elsif event.is_a?(String) && !values.empty?
|
88
|
+
body = ::DAP.const_get("#{event[0].upcase}#{event[1..]}EventBody").new(values)
|
89
|
+
end
|
90
|
+
|
91
|
+
send ::DAP::Event.new(event: event, body: body)
|
92
|
+
end
|
93
|
+
|
94
|
+
def respond!(request, body = nil, success: true, message: 'Success', **values)
|
95
|
+
send ::DAP::Response.new(
|
96
|
+
request_seq: request.seq,
|
97
|
+
command: request.command,
|
98
|
+
success: success,
|
99
|
+
message: message,
|
100
|
+
body: body,
|
101
|
+
**values)
|
102
|
+
end
|
103
|
+
|
104
|
+
def save_variables(*args)
|
105
|
+
@variable_refs << args
|
106
|
+
end
|
107
|
+
|
108
|
+
def restore_variables(ref)
|
109
|
+
@variable_refs[ref]
|
110
|
+
end
|
111
|
+
|
112
|
+
def save_frame(*args)
|
113
|
+
@frame_ids << args
|
114
|
+
end
|
115
|
+
|
116
|
+
def restore_frame(id)
|
117
|
+
@frame_ids[id]
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_log_point(breakpoint)
|
121
|
+
@log_points[breakpoint.id]
|
122
|
+
end
|
123
|
+
|
124
|
+
def set_log_point(breakpoint, expr)
|
125
|
+
if expr.nil? || expr.empty?
|
126
|
+
@log_points.delete(breakpoint.id)
|
127
|
+
else
|
128
|
+
@log_points[breakpoint.id] = expr
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def clear_breakpoints(*breakpoints)
|
133
|
+
breakpoints.each do |breakpoint|
|
134
|
+
Byebug.breakpoints.delete(breakpoint)
|
135
|
+
@log_points.delete(breakpoint.id)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def notify_of_children
|
142
|
+
@@children.each { |c| event! c }
|
143
|
+
rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
|
144
|
+
# client is closed
|
145
|
+
end
|
146
|
+
|
147
|
+
def send(message)
|
148
|
+
log "#{Process.pid} > #{message.to_wire}" if Debug.protocol
|
149
|
+
message.validate!
|
150
|
+
@connection.write ::DAP::Encoding.encode(message)
|
151
|
+
end
|
152
|
+
|
153
|
+
def receive
|
154
|
+
m = ::DAP::Encoding.decode(@connection)
|
155
|
+
log "#{Process.pid} < #{m.to_wire}" if Debug.protocol
|
156
|
+
m
|
157
|
+
end
|
158
|
+
|
159
|
+
def process_trace(trace)
|
160
|
+
return unless Byebug.started?
|
161
|
+
|
162
|
+
ctx = Byebug.contexts.find { |c| c.thread == Thread.current }
|
163
|
+
|
164
|
+
case trace.event
|
165
|
+
when :thread_begin
|
166
|
+
event! 'thread', reason: 'started', threadId: ctx.thnum
|
167
|
+
when :thread_end
|
168
|
+
event! 'thread', reason: 'exited', threadId: ctx.thnum
|
169
|
+
end
|
170
|
+
|
171
|
+
rescue IOError, Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED
|
172
|
+
# client disconnected, ignore error
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/lib/byebug/gem.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module Byebug
|
2
|
+
module DAP
|
3
|
+
NAME = 'byebug-dap'
|
4
|
+
VERSION = '0.1.3'
|
5
|
+
SUMMARY = 'Debug Adapter Protocol for Byebug'
|
6
|
+
DESCRIPTION = 'Implements a Debug Adapter Protocol interface for Byebug'
|
7
|
+
AUTHORS = ['Ethan Reesor']
|
8
|
+
WEBSITE = 'https://gitlab.com/firelizzard/byebug-dap'
|
9
|
+
LICENSE = 'Apache-2.0'
|
10
|
+
end
|
11
|
+
end
|