readapt 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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