readapt 1.3.0 → 1.4.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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +16 -16
  3. data/.rspec +2 -2
  4. data/.travis.yml +19 -18
  5. data/CHANGELOG.md +87 -83
  6. data/Gemfile +4 -4
  7. data/LICENSE.txt +21 -21
  8. data/README.md +37 -37
  9. data/Rakefile +14 -14
  10. data/bin/console +14 -14
  11. data/bin/setup +8 -8
  12. data/exe/readapt +5 -5
  13. data/ext/readapt/breakpoints.c +83 -83
  14. data/ext/readapt/breakpoints.h +11 -11
  15. data/ext/readapt/extconf.rb +0 -0
  16. data/ext/readapt/frame.c +137 -137
  17. data/ext/readapt/frame.h +17 -17
  18. data/ext/readapt/inspector.c +51 -51
  19. data/ext/readapt/inspector.h +8 -8
  20. data/ext/readapt/lookup_table.c +211 -211
  21. data/ext/readapt/lookup_table.h +30 -30
  22. data/ext/readapt/monitor.c +0 -0
  23. data/ext/readapt/monitor.h +0 -0
  24. data/ext/readapt/normalize.c +59 -59
  25. data/ext/readapt/normalize.h +7 -7
  26. data/ext/readapt/readapt.c +18 -18
  27. data/ext/readapt/stack.c +86 -86
  28. data/ext/readapt/stack.h +20 -20
  29. data/ext/readapt/threads.c +0 -0
  30. data/ext/readapt/threads.h +0 -0
  31. data/lib/readapt.rb +21 -21
  32. data/lib/readapt/adapter.rb +98 -98
  33. data/lib/readapt/breakpoint.rb +21 -21
  34. data/lib/readapt/data_reader.rb +62 -62
  35. data/lib/readapt/debugger.rb +228 -220
  36. data/lib/readapt/error.rb +63 -63
  37. data/lib/readapt/finder.rb +34 -34
  38. data/lib/readapt/frame.rb +40 -40
  39. data/lib/readapt/input.rb +7 -7
  40. data/lib/readapt/message.rb +62 -62
  41. data/lib/readapt/message/attach.rb +11 -11
  42. data/lib/readapt/message/base.rb +32 -32
  43. data/lib/readapt/message/configuration_done.rb +11 -11
  44. data/lib/readapt/message/continue.rb +15 -15
  45. data/lib/readapt/message/disconnect.rb +13 -13
  46. data/lib/readapt/message/evaluate.rb +18 -18
  47. data/lib/readapt/message/initialize.rb +21 -13
  48. data/lib/readapt/message/launch.rb +11 -11
  49. data/lib/readapt/message/next.rb +12 -12
  50. data/lib/readapt/message/pause.rb +11 -11
  51. data/lib/readapt/message/scopes.rb +26 -26
  52. data/lib/readapt/message/set_breakpoints.rb +25 -25
  53. data/lib/readapt/message/set_exception_breakpoints.rb +11 -8
  54. data/lib/readapt/message/stack_trace.rb +38 -38
  55. data/lib/readapt/message/step_in.rb +11 -11
  56. data/lib/readapt/message/step_out.rb +11 -11
  57. data/lib/readapt/message/threads.rb +18 -18
  58. data/lib/readapt/message/variables.rb +53 -53
  59. data/lib/readapt/monitor.rb +0 -0
  60. data/lib/readapt/output.rb +25 -25
  61. data/lib/readapt/references.rb +27 -27
  62. data/lib/readapt/server.rb +22 -22
  63. data/lib/readapt/shell.rb +104 -104
  64. data/lib/readapt/snapshot.rb +0 -0
  65. data/lib/readapt/thread.rb +25 -25
  66. data/lib/readapt/variable.rb +0 -0
  67. data/lib/readapt/version.rb +3 -3
  68. data/readapt.gemspec +39 -39
  69. metadata +7 -7
File without changes
File without changes
data/lib/readapt.rb CHANGED
@@ -1,21 +1,21 @@
1
- require 'backport'
2
-
3
- require 'readapt/version'
4
- require 'readapt/readapt'
5
- require 'readapt/references'
6
- require 'readapt/breakpoint'
7
- require 'readapt/thread'
8
- require 'readapt/frame'
9
- require 'readapt/monitor'
10
- require 'readapt/snapshot'
11
- require 'readapt/finder'
12
- require 'readapt/debugger'
13
- require 'readapt/message'
14
- require 'readapt/variable'
15
- require 'readapt/data_reader'
16
- require 'readapt/server'
17
- require 'readapt/adapter'
18
- require 'readapt/input'
19
- require 'readapt/output'
20
- require 'readapt/error'
21
- require 'readapt/shell'
1
+ require 'backport'
2
+
3
+ require 'readapt/version'
4
+ require 'readapt/readapt'
5
+ require 'readapt/references'
6
+ require 'readapt/breakpoint'
7
+ require 'readapt/thread'
8
+ require 'readapt/frame'
9
+ require 'readapt/monitor'
10
+ require 'readapt/snapshot'
11
+ require 'readapt/finder'
12
+ require 'readapt/debugger'
13
+ require 'readapt/message'
14
+ require 'readapt/variable'
15
+ require 'readapt/data_reader'
16
+ require 'readapt/server'
17
+ require 'readapt/adapter'
18
+ require 'readapt/input'
19
+ require 'readapt/output'
20
+ require 'readapt/error'
21
+ require 'readapt/shell'
@@ -1,98 +1,98 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module Readapt
6
- module Adapter
7
- # @!parse include Backport::Adapter
8
-
9
- @@debugger = nil
10
-
11
- def self.host debugger
12
- @@debugger = debugger
13
- end
14
-
15
- def self.procid= pid
16
- @@procid = pid
17
- end
18
-
19
- def procid
20
- @@procid
21
- end
22
-
23
- def open_message
24
- @@open_message ||= "<readapt-#{procid}>"
25
- end
26
-
27
- def close_message
28
- @@close_message ||= "</readapt-#{procid}>"
29
- end
30
-
31
- def format result
32
- write_line result.to_protocol.to_json
33
- end
34
-
35
- def opening
36
- @@debugger.add_observer self
37
- @data_reader = DataReader.new
38
- @data_reader.set_message_handler do |message|
39
- process message
40
- end
41
- end
42
-
43
- def closing
44
- @@debugger.delete_observer(self)
45
- end
46
-
47
- def receiving data
48
- @data_reader.receive data
49
- end
50
-
51
- def update event, data
52
- obj = {
53
- type: 'event',
54
- event: event
55
- }
56
- obj[:body] = data unless data.nil?
57
- json = obj.to_json
58
- envelope = "#{open_message}Content-Length: #{json.bytesize}\r\n\r\n#{json}#{close_message}"
59
- write envelope
60
- write "#{open_message}__TERMINATE__#{close_message}" if event == 'terminated'
61
- end
62
-
63
- private
64
-
65
- # @param data [Hash]
66
- # @return [void]
67
- def process data
68
- message = Message.process(data, @@debugger)
69
- if data['seq']
70
- json = {
71
- type: 'response',
72
- request_seq: data['seq'],
73
- success: true,
74
- command: data['command'],
75
- body: message.body
76
- }.to_json
77
- envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
78
- write "#{open_message}#{envelope}#{close_message}"
79
- if data['command'] == 'disconnect'
80
- @@debugger.disconnect
81
- # @todo It does not appear necessary to close the adapter after
82
- # disconnecting the debugger.
83
- # close
84
- end
85
- return unless data['command'] == 'initialize'
86
- json = {
87
- type: 'event',
88
- event: 'initialized'
89
- }.to_json
90
- envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
91
- write "#{open_message}#{envelope}#{close_message}"
92
- end
93
- rescue RuntimeError => e
94
- STDERR.puts "[#{e.class}] #{e.message}"
95
- STDERR.puts e.backtrace.join
96
- end
97
- end
98
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Readapt
6
+ module Adapter
7
+ # @!parse include Backport::Adapter
8
+
9
+ @@debugger = nil
10
+
11
+ def self.host debugger
12
+ @@debugger = debugger
13
+ end
14
+
15
+ def self.procid= pid
16
+ @@procid = pid
17
+ end
18
+
19
+ def procid
20
+ @@procid
21
+ end
22
+
23
+ def open_message
24
+ @@open_message ||= "<readapt-#{procid}>"
25
+ end
26
+
27
+ def close_message
28
+ @@close_message ||= "</readapt-#{procid}>"
29
+ end
30
+
31
+ def format result
32
+ write_line result.to_protocol.to_json
33
+ end
34
+
35
+ def opening
36
+ @@debugger.add_observer self
37
+ @data_reader = DataReader.new
38
+ @data_reader.set_message_handler do |message|
39
+ process message
40
+ end
41
+ end
42
+
43
+ def closing
44
+ @@debugger.delete_observer(self)
45
+ end
46
+
47
+ def receiving data
48
+ @data_reader.receive data
49
+ end
50
+
51
+ def update event, data
52
+ obj = {
53
+ type: 'event',
54
+ event: event
55
+ }
56
+ obj[:body] = data unless data.nil?
57
+ json = obj.to_json
58
+ envelope = "#{open_message}Content-Length: #{json.bytesize}\r\n\r\n#{json}#{close_message}"
59
+ write envelope
60
+ write "#{open_message}__TERMINATE__#{close_message}" if event == 'terminated'
61
+ end
62
+
63
+ private
64
+
65
+ # @param data [Hash]
66
+ # @return [void]
67
+ def process data
68
+ message = Message.process(data, @@debugger)
69
+ if data['seq']
70
+ json = {
71
+ type: 'response',
72
+ request_seq: data['seq'],
73
+ success: true,
74
+ command: data['command'],
75
+ body: message.body
76
+ }.to_json
77
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
78
+ write "#{open_message}#{envelope}#{close_message}"
79
+ if data['command'] == 'disconnect'
80
+ @@debugger.disconnect
81
+ # @todo It does not appear necessary to close the adapter after
82
+ # disconnecting the debugger.
83
+ # close
84
+ end
85
+ return unless data['command'] == 'initialize'
86
+ json = {
87
+ type: 'event',
88
+ event: 'initialized'
89
+ }.to_json
90
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
91
+ write "#{open_message}#{envelope}#{close_message}"
92
+ end
93
+ rescue RuntimeError => e
94
+ STDERR.puts "[#{e.class}] #{e.message}"
95
+ STDERR.puts e.backtrace.join
96
+ end
97
+ end
98
+ end
@@ -1,21 +1,21 @@
1
- module Readapt
2
- class Breakpoint
3
- attr_reader :source
4
- attr_reader :line
5
- attr_reader :condition
6
- attr_reader :hit_condition
7
- attr_writer :hit_cursor
8
-
9
- def initialize source, line, condition, hit_condition
10
- @source = source
11
- @line = line
12
- @condition = condition
13
- @hit_condition = hit_condition
14
- end
15
-
16
- # @return [Integer]
17
- def hit_cursor
18
- @hit_cursor ||= 0
19
- end
20
- end
21
- end
1
+ module Readapt
2
+ class Breakpoint
3
+ attr_reader :source
4
+ attr_reader :line
5
+ attr_reader :condition
6
+ attr_reader :hit_condition
7
+ attr_writer :hit_cursor
8
+
9
+ def initialize source, line, condition, hit_condition
10
+ @source = source
11
+ @line = line
12
+ @condition = condition
13
+ @hit_condition = hit_condition
14
+ end
15
+
16
+ # @return [Integer]
17
+ def hit_cursor
18
+ @hit_cursor ||= 0
19
+ end
20
+ end
21
+ end
@@ -1,62 +1,62 @@
1
- module Readapt
2
- class DataReader
3
- def initialize
4
- @in_header = true
5
- @content_length = 0
6
- @buffer = String.new
7
- end
8
-
9
- # Declare a block to be executed for each message received from the
10
- # client.
11
- #
12
- # @yieldparam [Hash] The message received from the client
13
- def set_message_handler &block
14
- @message_handler = block
15
- end
16
-
17
- # Process raw data received from the client. The data will be parsed
18
- # into messages based on the JSON-RPC protocol. Each message will be
19
- # passed to the block declared via set_message_handler. Incomplete data
20
- # will be buffered and subsequent data will be appended to the buffer.
21
- #
22
- # @param data [String]
23
- def receive data
24
- data.each_char do |char|
25
- @buffer.concat char
26
- if @in_header
27
- prepare_to_parse_message if @buffer.end_with?("\r\n\r\n")
28
- else
29
- parse_message_from_buffer if @buffer.bytesize == @content_length
30
- end
31
- end
32
- end
33
-
34
- private
35
-
36
- def prepare_to_parse_message
37
- @in_header = false
38
- @buffer.each_line do |line|
39
- parts = line.split(':').map(&:strip)
40
- if parts[0] == 'Content-Length'
41
- @content_length = parts[1].to_i
42
- break
43
- end
44
- end
45
- @buffer.clear
46
- end
47
-
48
- def parse_message_from_buffer
49
- begin
50
- msg = JSON.parse(@buffer)
51
- @message_handler.call msg unless @message_handler.nil?
52
- rescue JSON::ParserError => e
53
- Solargraph::Logging.logger.warn "Failed to parse request: #{e.message}"
54
- Solargraph::Logging.logger.debug "Buffer: #{@buffer}"
55
- ensure
56
- @buffer.clear
57
- @in_header = true
58
- @content_length = 0
59
- end
60
- end
61
- end
62
- end
1
+ module Readapt
2
+ class DataReader
3
+ def initialize
4
+ @in_header = true
5
+ @content_length = 0
6
+ @buffer = String.new
7
+ end
8
+
9
+ # Declare a block to be executed for each message received from the
10
+ # client.
11
+ #
12
+ # @yieldparam [Hash] The message received from the client
13
+ def set_message_handler &block
14
+ @message_handler = block
15
+ end
16
+
17
+ # Process raw data received from the client. The data will be parsed
18
+ # into messages based on the JSON-RPC protocol. Each message will be
19
+ # passed to the block declared via set_message_handler. Incomplete data
20
+ # will be buffered and subsequent data will be appended to the buffer.
21
+ #
22
+ # @param data [String]
23
+ def receive data
24
+ data.each_char do |char|
25
+ @buffer.concat char
26
+ if @in_header
27
+ prepare_to_parse_message if @buffer.end_with?("\r\n\r\n")
28
+ else
29
+ parse_message_from_buffer if @buffer.bytesize == @content_length
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def prepare_to_parse_message
37
+ @in_header = false
38
+ @buffer.each_line do |line|
39
+ parts = line.split(':').map(&:strip)
40
+ if parts[0] == 'Content-Length'
41
+ @content_length = parts[1].to_i
42
+ break
43
+ end
44
+ end
45
+ @buffer.clear
46
+ end
47
+
48
+ def parse_message_from_buffer
49
+ begin
50
+ msg = JSON.parse(@buffer)
51
+ @message_handler.call msg unless @message_handler.nil?
52
+ rescue JSON::ParserError => e
53
+ Solargraph::Logging.logger.warn "Failed to parse request: #{e.message}"
54
+ Solargraph::Logging.logger.debug "Buffer: #{@buffer}"
55
+ ensure
56
+ @buffer.clear
57
+ @in_header = true
58
+ @content_length = 0
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,220 +1,228 @@
1
- # frozen_string_literal: true
2
-
3
- require 'backport'
4
- require 'observer'
5
- require 'set'
6
-
7
- module Readapt
8
- class Debugger
9
- include Observable
10
- include Finder
11
-
12
- attr_reader :monitor
13
-
14
- attr_reader :file
15
-
16
- def initialize
17
- @stack = []
18
- @frames = {}
19
- @running = false
20
- @attached = false
21
- @request = nil
22
- @config = {}
23
- @original_argv = ARGV.clone
24
- @original_prog = $0
25
- @breakpoints = {}
26
- end
27
-
28
- def config arguments, request
29
- @file = Readapt.normalize_path(find(arguments['program']))
30
- @config = arguments
31
- @request = request
32
- rescue LoadError => e
33
- STDERR.puts e.message
34
- end
35
-
36
- # @return [Readapt::Thread]
37
- def thread id
38
- Thread.find(id)
39
- end
40
-
41
- def threads
42
- Thread.all
43
- end
44
-
45
- def frame id
46
- @frames[id] || Frame::NULL_FRAME
47
- end
48
-
49
- def launched?
50
- @request == :launch
51
- end
52
-
53
- def attached?
54
- @request == :attach
55
- end
56
-
57
- def start
58
- ::Thread.new do
59
- set_program_args
60
- run { load @file }
61
- set_original_args
62
- end
63
- end
64
-
65
- def run
66
- # raise RuntimeError, 'Debugger is already running' if @running
67
- @running = true
68
- send_event('process', {
69
- name: @file
70
- })
71
- Monitor.start @file do |snapshot|
72
- debug snapshot
73
- end
74
- yield if block_given?
75
- rescue StandardError => e
76
- STDERR.puts "[#{e.class}] #{e.message}"
77
- STDERR.puts e.backtrace.join("\n")
78
- ensure
79
- Monitor.stop
80
- @running = false
81
- STDOUT.flush #unless STDOUT.closed?
82
- STDERR.flush #unless STDERR.closed?
83
- changed
84
- send_event 'terminated', nil
85
- end
86
-
87
- def output data, category = :console
88
- send_event('output', {
89
- output: data,
90
- category: category
91
- })
92
- end
93
-
94
- def get_breakpoint source, line
95
- @breakpoints["#{source}:#{line}"] || Breakpoint.new(source, line, nil, 0)
96
- end
97
-
98
- def set_breakpoint source, line, condition, hitcount
99
- @breakpoints["#{source}:#{line}"] = Breakpoint.new(source, line, condition, hitcount)
100
- end
101
-
102
- def clear_breakpoints source
103
- @breakpoints.delete_if { |_key, value|
104
- value.source == source
105
- }
106
- end
107
-
108
- def disconnect
109
- shutdown if launched?
110
- @request = nil
111
- end
112
-
113
- def self.run &block
114
- new.run &block
115
- end
116
-
117
- private
118
-
119
- # @param [Snapshot]
120
- # return [void]
121
- def debug snapshot
122
- sleep 0.001 # @todo Trying to let thread data sync
123
- References.clear
124
- if snapshot.event == :thread_begin || snapshot.event == :entry
125
- thr = Thread.find(snapshot.thread_id)
126
- thr.control = :continue
127
- send_event('thread', {
128
- reason: 'started',
129
- threadId: snapshot.thread_id
130
- }, true)
131
- snapshot.control = :continue
132
- elsif snapshot.event == :thread_end
133
- thr = thread(snapshot.thread_id)
134
- thr.control = :continue
135
- send_event('thread', {
136
- reason: 'exited',
137
- threadId: snapshot.thread_id
138
- })
139
- snapshot.control = :continue
140
- else
141
- confirmed_pause = true
142
- thread = self.thread(snapshot.thread_id)
143
- if snapshot.event == :breakpoint
144
- bp = get_breakpoint(snapshot.file, snapshot.line)
145
- unless bp.condition.nil? || bp.condition.empty?
146
- # @type [Binding]
147
- bnd = thread.frames.first.frame_binding
148
- begin
149
- unless bnd.eval(bp.condition)
150
- confirmed_pause = false
151
- end
152
- rescue Exception => e
153
- STDERR.puts "Breakpoint condition raised an error"
154
- STDERR.puts "#{snapshot.file}:#{snapshot.line} - `#{bp.condition}`"
155
- STDERR.puts "[#{e.class}] #{e.message}"
156
- confirmed_pause = false
157
- end
158
- end
159
- unless !confirmed_pause || bp.hit_condition.nil? || bp.hit_condition.empty?
160
- bp.hit_cursor += 1
161
- bnd = thread.frames.first.frame_binding
162
- begin
163
- hit_count = bnd.eval(bp.hit_condition)
164
- if bp.hit_cursor == hit_count
165
- bp.hit_cursor = 0
166
- else
167
- confirmed_pause = false
168
- end
169
- rescue Exception => e
170
- STDERR.puts "Breakpoint condition raised an error"
171
- STDERR.puts "#{snapshot.file}:#{snapshot.line} - `#{bp.condition}`"
172
- STDERR.puts "[#{e.class}] #{e.message}"
173
- confirmed_pause = false
174
- end
175
- end
176
- end
177
- if confirmed_pause
178
- changed
179
- thread.control = :pause
180
- thread.frames.each do |frm|
181
- @frames[frm.local_id] = frm
182
- end
183
- send_event('stopped', {
184
- reason: snapshot.event,
185
- threadId: thread.id
186
- })
187
- sleep 0.01 until thread.control != :pause || !Thread.include?(thread.id)
188
- thread.frames.each do |frm|
189
- @frames.delete frm.local_id
190
- end
191
- else
192
- thread.control = :continue
193
- end
194
- snapshot.control = thread.control
195
- end
196
- end
197
-
198
- def set_program_args
199
- $0 = file
200
- ARGV.clear
201
- ARGV.replace(@config['programArgs'] || [])
202
- end
203
-
204
- def set_original_args
205
- $0 = @original_prog
206
- ARGV.clear
207
- ARGV.replace @original_argv
208
- end
209
-
210
- def shutdown
211
- exit
212
- end
213
-
214
- def send_event event, data, wait = false
215
- changed
216
- notify_observers event, data
217
- sleep 0.01 if wait
218
- end
219
- end
220
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'backport'
4
+ require 'observer'
5
+ require 'set'
6
+
7
+ module Readapt
8
+ class Debugger
9
+ include Observable
10
+ include Finder
11
+
12
+ attr_reader :monitor
13
+
14
+ attr_reader :file
15
+
16
+ attr_writer :pause_on_raise
17
+
18
+ def initialize
19
+ @stack = []
20
+ @frames = {}
21
+ @running = false
22
+ @attached = false
23
+ @request = nil
24
+ @config = {}
25
+ @original_argv = ARGV.clone
26
+ @original_prog = $0
27
+ @breakpoints = {}
28
+ end
29
+
30
+ def config arguments, request
31
+ @file = Readapt.normalize_path(find(arguments['program']))
32
+ @config = arguments
33
+ @request = request
34
+ rescue LoadError => e
35
+ STDERR.puts e.message
36
+ end
37
+
38
+ def pause_on_raise?
39
+ @pause_on_raise ||= false
40
+ end
41
+
42
+ # @return [Readapt::Thread]
43
+ def thread id
44
+ Thread.find(id)
45
+ end
46
+
47
+ def threads
48
+ Thread.all
49
+ end
50
+
51
+ def frame id
52
+ @frames[id] || Frame::NULL_FRAME
53
+ end
54
+
55
+ def launched?
56
+ @request == :launch
57
+ end
58
+
59
+ def attached?
60
+ @request == :attach
61
+ end
62
+
63
+ def start
64
+ ::Thread.new do
65
+ set_program_args
66
+ run { load @file }
67
+ set_original_args
68
+ end
69
+ end
70
+
71
+ def run
72
+ # raise RuntimeError, 'Debugger is already running' if @running
73
+ @running = true
74
+ send_event('process', {
75
+ name: @file
76
+ })
77
+ Monitor.start @file do |snapshot|
78
+ debug snapshot
79
+ end
80
+ yield if block_given?
81
+ rescue StandardError => e
82
+ STDERR.puts "[#{e.class}] #{e.message}"
83
+ STDERR.puts e.backtrace.join("\n")
84
+ ensure
85
+ Monitor.stop
86
+ @running = false
87
+ STDOUT.flush #unless STDOUT.closed?
88
+ STDERR.flush #unless STDERR.closed?
89
+ changed
90
+ send_event 'terminated', nil
91
+ end
92
+
93
+ def output data, category = :console
94
+ send_event('output', {
95
+ output: data,
96
+ category: category
97
+ })
98
+ end
99
+
100
+ def get_breakpoint source, line
101
+ @breakpoints["#{source}:#{line}"] || Breakpoint.new(source, line, nil, 0)
102
+ end
103
+
104
+ def set_breakpoint source, line, condition, hitcount
105
+ @breakpoints["#{source}:#{line}"] = Breakpoint.new(source, line, condition, hitcount)
106
+ end
107
+
108
+ def clear_breakpoints source
109
+ @breakpoints.delete_if { |_key, value|
110
+ value.source == source
111
+ }
112
+ end
113
+
114
+ def disconnect
115
+ shutdown if launched?
116
+ @request = nil
117
+ end
118
+
119
+ def self.run &block
120
+ new.run &block
121
+ end
122
+
123
+ private
124
+
125
+ # @param [Snapshot]
126
+ # return [void]
127
+ def debug snapshot
128
+ sleep 0.001 # @todo Trying to let thread data sync
129
+ References.clear
130
+ if snapshot.event == :thread_begin || snapshot.event == :entry
131
+ thr = Thread.find(snapshot.thread_id)
132
+ thr.control = :continue
133
+ send_event('thread', {
134
+ reason: 'started',
135
+ threadId: snapshot.thread_id
136
+ }, true)
137
+ snapshot.control = :continue
138
+ elsif snapshot.event == :thread_end
139
+ thr = thread(snapshot.thread_id)
140
+ thr.control = :continue
141
+ send_event('thread', {
142
+ reason: 'exited',
143
+ threadId: snapshot.thread_id
144
+ })
145
+ snapshot.control = :continue
146
+ else
147
+ confirmed_pause = true
148
+ thread = self.thread(snapshot.thread_id)
149
+ if snapshot.event == :breakpoint
150
+ bp = get_breakpoint(snapshot.file, snapshot.line)
151
+ unless bp.condition.nil? || bp.condition.empty?
152
+ # @type [Binding]
153
+ bnd = thread.frames.first.frame_binding
154
+ begin
155
+ unless bnd.eval(bp.condition)
156
+ confirmed_pause = false
157
+ end
158
+ rescue Exception => e
159
+ STDERR.puts "Breakpoint condition raised an error"
160
+ STDERR.puts "#{snapshot.file}:#{snapshot.line} - `#{bp.condition}`"
161
+ STDERR.puts "[#{e.class}] #{e.message}"
162
+ confirmed_pause = false
163
+ end
164
+ end
165
+ unless !confirmed_pause || bp.hit_condition.nil? || bp.hit_condition.empty?
166
+ bp.hit_cursor += 1
167
+ bnd = thread.frames.first.frame_binding
168
+ begin
169
+ hit_count = bnd.eval(bp.hit_condition)
170
+ if bp.hit_cursor == hit_count
171
+ bp.hit_cursor = 0
172
+ else
173
+ confirmed_pause = false
174
+ end
175
+ rescue Exception => e
176
+ STDERR.puts "Breakpoint condition raised an error"
177
+ STDERR.puts "#{snapshot.file}:#{snapshot.line} - `#{bp.condition}`"
178
+ STDERR.puts "[#{e.class}] #{e.message}"
179
+ confirmed_pause = false
180
+ end
181
+ end
182
+ elsif snapshot.event == :raise && !pause_on_raise?
183
+ confirmed_pause = false
184
+ end
185
+ if confirmed_pause
186
+ changed
187
+ thread.control = :pause
188
+ thread.frames.each do |frm|
189
+ @frames[frm.local_id] = frm
190
+ end
191
+ send_event('stopped', {
192
+ reason: snapshot.event,
193
+ threadId: thread.id
194
+ })
195
+ sleep 0.01 until thread.control != :pause || !Thread.include?(thread.id)
196
+ thread.frames.each do |frm|
197
+ @frames.delete frm.local_id
198
+ end
199
+ else
200
+ thread.control = :continue
201
+ end
202
+ snapshot.control = thread.control
203
+ end
204
+ end
205
+
206
+ def set_program_args
207
+ $0 = file
208
+ ARGV.clear
209
+ ARGV.replace(@config['programArgs'] || [])
210
+ end
211
+
212
+ def set_original_args
213
+ $0 = @original_prog
214
+ ARGV.clear
215
+ ARGV.replace @original_argv
216
+ end
217
+
218
+ def shutdown
219
+ exit
220
+ end
221
+
222
+ def send_event event, data, wait = false
223
+ changed
224
+ notify_observers event, data
225
+ sleep 0.01 if wait
226
+ end
227
+ end
228
+ end