readapt 1.4.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +38 -0
  3. data/.gitignore +16 -16
  4. data/.rspec +2 -2
  5. data/.travis.yml +19 -19
  6. data/CHANGELOG.md +103 -100
  7. data/Gemfile +4 -4
  8. data/LICENSE.txt +21 -21
  9. data/README.md +37 -37
  10. data/Rakefile +14 -14
  11. data/bin/console +14 -14
  12. data/bin/setup +8 -8
  13. data/exe/readapt +5 -5
  14. data/ext/readapt/breakpoints.c +83 -83
  15. data/ext/readapt/breakpoints.h +11 -11
  16. data/ext/readapt/extconf.rb +0 -0
  17. data/ext/readapt/frame.c +137 -137
  18. data/ext/readapt/frame.h +17 -17
  19. data/ext/readapt/inspector.c +51 -51
  20. data/ext/readapt/inspector.h +8 -8
  21. data/ext/readapt/lookup_table.c +211 -211
  22. data/ext/readapt/lookup_table.h +30 -30
  23. data/ext/readapt/monitor.c +0 -0
  24. data/ext/readapt/monitor.h +0 -0
  25. data/ext/readapt/normalize.c +62 -62
  26. data/ext/readapt/normalize.h +7 -7
  27. data/ext/readapt/readapt.c +18 -18
  28. data/ext/readapt/stack.c +86 -86
  29. data/ext/readapt/stack.h +20 -20
  30. data/ext/readapt/threads.c +1 -1
  31. data/ext/readapt/threads.h +0 -0
  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 +227 -227
  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/attach.rb +11 -11
  41. data/lib/readapt/message/base.rb +32 -32
  42. data/lib/readapt/message/configuration_done.rb +11 -11
  43. data/lib/readapt/message/continue.rb +15 -15
  44. data/lib/readapt/message/disconnect.rb +13 -13
  45. data/lib/readapt/message/evaluate.rb +19 -19
  46. data/lib/readapt/message/initialize.rb +21 -21
  47. data/lib/readapt/message/launch.rb +11 -11
  48. data/lib/readapt/message/next.rb +12 -12
  49. data/lib/readapt/message/pause.rb +11 -11
  50. data/lib/readapt/message/scopes.rb +26 -26
  51. data/lib/readapt/message/set_breakpoints.rb +25 -25
  52. data/lib/readapt/message/set_exception_breakpoints.rb +11 -11
  53. data/lib/readapt/message/stack_trace.rb +38 -38
  54. data/lib/readapt/message/step_in.rb +11 -11
  55. data/lib/readapt/message/step_out.rb +11 -11
  56. data/lib/readapt/message/threads.rb +18 -18
  57. data/lib/readapt/message/variables.rb +53 -53
  58. data/lib/readapt/message.rb +62 -62
  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 +20 -20
  66. data/lib/readapt/variable.rb +0 -0
  67. data/lib/readapt/version.rb +3 -3
  68. data/lib/readapt.rb +21 -21
  69. data/readapt.gemspec +39 -39
  70. metadata +8 -7
@@ -1,227 +1,227 @@
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
- References.clear
129
- if snapshot.event == :thread_begin || snapshot.event == :entry
130
- thr = Thread.find(snapshot.thread_id)
131
- thr.control = :continue
132
- send_event('thread', {
133
- reason: 'started',
134
- threadId: snapshot.thread_id
135
- }, true)
136
- snapshot.control = :continue
137
- elsif snapshot.event == :thread_end
138
- thr = thread(snapshot.thread_id)
139
- thr.control = :continue
140
- send_event('thread', {
141
- reason: 'exited',
142
- threadId: snapshot.thread_id
143
- })
144
- snapshot.control = :continue
145
- else
146
- confirmed_pause = true
147
- thread = self.thread(snapshot.thread_id)
148
- if snapshot.event == :breakpoint
149
- bp = get_breakpoint(snapshot.file, snapshot.line)
150
- unless bp.condition.nil? || bp.condition.empty?
151
- # @type [Binding]
152
- bnd = thread.frames.first.frame_binding
153
- begin
154
- unless bnd.eval(bp.condition)
155
- confirmed_pause = false
156
- end
157
- rescue StandardError => e
158
- STDERR.puts "Breakpoint condition raised an error"
159
- STDERR.puts "#{snapshot.file}:#{snapshot.line} - `#{bp.condition}`"
160
- STDERR.puts "[#{e.class}] #{e.message}"
161
- confirmed_pause = false
162
- end
163
- end
164
- unless !confirmed_pause || bp.hit_condition.nil? || bp.hit_condition.empty?
165
- bp.hit_cursor += 1
166
- bnd = thread.frames.first.frame_binding
167
- begin
168
- hit_count = bnd.eval(bp.hit_condition)
169
- if bp.hit_cursor == hit_count
170
- bp.hit_cursor = 0
171
- else
172
- confirmed_pause = false
173
- end
174
- rescue StandardError => e
175
- STDERR.puts "Breakpoint condition raised an error"
176
- STDERR.puts "#{snapshot.file}:#{snapshot.line} - `#{bp.condition}`"
177
- STDERR.puts "[#{e.class}] #{e.message}"
178
- confirmed_pause = false
179
- end
180
- end
181
- elsif snapshot.event == :raise && !pause_on_raise?
182
- confirmed_pause = false
183
- end
184
- if confirmed_pause
185
- changed
186
- thread.control = :pause
187
- thread.frames.each do |frm|
188
- @frames[frm.local_id] = frm
189
- end
190
- send_event('stopped', {
191
- reason: snapshot.event,
192
- threadId: thread.id
193
- })
194
- sleep 0.01 until thread.control != :pause || !Thread.include?(thread.id)
195
- thread.frames.each do |frm|
196
- @frames.delete frm.local_id
197
- end
198
- else
199
- thread.control = :continue
200
- end
201
- snapshot.control = thread.control
202
- end
203
- end
204
-
205
- def set_program_args
206
- $0 = file
207
- ARGV.clear
208
- ARGV.replace(@config['programArgs'] || [])
209
- end
210
-
211
- def set_original_args
212
- $0 = @original_prog
213
- ARGV.clear
214
- ARGV.replace @original_argv
215
- end
216
-
217
- def shutdown
218
- exit
219
- end
220
-
221
- def send_event event, data, wait = false
222
- changed
223
- notify_observers event, data
224
- sleep 0.01 if wait
225
- end
226
- end
227
- 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
+ References.clear
129
+ if snapshot.event == :thread_begin || snapshot.event == :entry
130
+ thr = Thread.find(snapshot.thread_id)
131
+ thr.control = :continue
132
+ send_event('thread', {
133
+ reason: 'started',
134
+ threadId: snapshot.thread_id
135
+ }, true)
136
+ snapshot.control = :continue
137
+ elsif snapshot.event == :thread_end
138
+ thr = thread(snapshot.thread_id)
139
+ thr.control = :continue
140
+ send_event('thread', {
141
+ reason: 'exited',
142
+ threadId: snapshot.thread_id
143
+ })
144
+ snapshot.control = :continue
145
+ else
146
+ confirmed_pause = true
147
+ thread = self.thread(snapshot.thread_id)
148
+ if snapshot.event == :breakpoint
149
+ bp = get_breakpoint(snapshot.file, snapshot.line)
150
+ unless bp.condition.nil? || bp.condition.empty?
151
+ # @type [Binding]
152
+ bnd = thread.frames.first.frame_binding
153
+ begin
154
+ unless bnd.eval(bp.condition)
155
+ confirmed_pause = false
156
+ end
157
+ rescue StandardError => e
158
+ STDERR.puts "Breakpoint condition raised an error"
159
+ STDERR.puts "#{snapshot.file}:#{snapshot.line} - `#{bp.condition}`"
160
+ STDERR.puts "[#{e.class}] #{e.message}"
161
+ confirmed_pause = false
162
+ end
163
+ end
164
+ unless !confirmed_pause || bp.hit_condition.nil? || bp.hit_condition.empty?
165
+ bp.hit_cursor += 1
166
+ bnd = thread.frames.first.frame_binding
167
+ begin
168
+ hit_count = bnd.eval(bp.hit_condition)
169
+ if bp.hit_cursor == hit_count
170
+ bp.hit_cursor = 0
171
+ else
172
+ confirmed_pause = false
173
+ end
174
+ rescue StandardError => e
175
+ STDERR.puts "Breakpoint condition raised an error"
176
+ STDERR.puts "#{snapshot.file}:#{snapshot.line} - `#{bp.condition}`"
177
+ STDERR.puts "[#{e.class}] #{e.message}"
178
+ confirmed_pause = false
179
+ end
180
+ end
181
+ elsif snapshot.event == :raise && !pause_on_raise?
182
+ confirmed_pause = false
183
+ end
184
+ if confirmed_pause
185
+ changed
186
+ thread.control = :pause
187
+ thread.frames.each do |frm|
188
+ @frames[frm.local_id] = frm
189
+ end
190
+ send_event('stopped', {
191
+ reason: snapshot.event,
192
+ threadId: thread.id
193
+ })
194
+ sleep 0.01 until thread.control != :pause || !Thread.include?(thread.id)
195
+ thread.frames.each do |frm|
196
+ @frames.delete frm.local_id
197
+ end
198
+ else
199
+ thread.control = :continue
200
+ end
201
+ snapshot.control = thread.control
202
+ end
203
+ end
204
+
205
+ def set_program_args
206
+ $0 = file
207
+ ARGV.clear
208
+ ARGV.replace(@config['programArgs'] || [])
209
+ end
210
+
211
+ def set_original_args
212
+ $0 = @original_prog
213
+ ARGV.clear
214
+ ARGV.replace @original_argv
215
+ end
216
+
217
+ def shutdown
218
+ exit
219
+ end
220
+
221
+ def send_event event, data, wait = false
222
+ changed
223
+ notify_observers event, data
224
+ sleep 0.01 if wait
225
+ end
226
+ end
227
+ end
data/lib/readapt/error.rb CHANGED
@@ -1,63 +1,63 @@
1
- require 'securerandom'
2
-
3
- module Readapt
4
- module Error
5
- class << self
6
- attr_accessor :adapter
7
- end
8
-
9
- def opening
10
- @buffer = ''
11
- end
12
-
13
- def receiving data
14
- output = ''
15
- data.each_char do |char|
16
- @buffer += char
17
- if open_message.start_with?(@buffer) || @buffer.start_with?(open_message)
18
- if @buffer.end_with?(close_message)
19
- msg = @buffer[open_message.length..-(close_message.length+1)]
20
- exit if msg == '__TERMINATE__'
21
- Error.adapter.write msg
22
- @buffer.clear
23
- end
24
- else
25
- output += @buffer
26
- @buffer.clear
27
- end
28
- end
29
- return if output.empty?
30
- send_event('output', {
31
- output: output,
32
- category: 'stderr'
33
- })
34
- end
35
-
36
- def send_event event, data
37
- obj = {
38
- type: 'event',
39
- event: event
40
- }
41
- obj[:body] = data unless data.nil?
42
- json = obj.to_json
43
- envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
44
- Error.adapter.write envelope
45
- end
46
-
47
- def self.procid= pid
48
- @@procid = pid
49
- end
50
-
51
- def procid
52
- @@procid
53
- end
54
-
55
- def open_message
56
- @@open_message ||= "<readapt-#{procid}>"
57
- end
58
-
59
- def close_message
60
- @@close_message ||= "</readapt-#{procid}>"
61
- end
62
- end
63
- end
1
+ require 'securerandom'
2
+
3
+ module Readapt
4
+ module Error
5
+ class << self
6
+ attr_accessor :adapter
7
+ end
8
+
9
+ def opening
10
+ @buffer = ''
11
+ end
12
+
13
+ def receiving data
14
+ output = ''
15
+ data.each_char do |char|
16
+ @buffer += char
17
+ if open_message.start_with?(@buffer) || @buffer.start_with?(open_message)
18
+ if @buffer.end_with?(close_message)
19
+ msg = @buffer[open_message.length..-(close_message.length+1)]
20
+ exit if msg == '__TERMINATE__'
21
+ Error.adapter.write msg
22
+ @buffer.clear
23
+ end
24
+ else
25
+ output += @buffer
26
+ @buffer.clear
27
+ end
28
+ end
29
+ return if output.empty?
30
+ send_event('output', {
31
+ output: output,
32
+ category: 'stderr'
33
+ })
34
+ end
35
+
36
+ def send_event event, data
37
+ obj = {
38
+ type: 'event',
39
+ event: event
40
+ }
41
+ obj[:body] = data unless data.nil?
42
+ json = obj.to_json
43
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
44
+ Error.adapter.write envelope
45
+ end
46
+
47
+ def self.procid= pid
48
+ @@procid = pid
49
+ end
50
+
51
+ def procid
52
+ @@procid
53
+ end
54
+
55
+ def open_message
56
+ @@open_message ||= "<readapt-#{procid}>"
57
+ end
58
+
59
+ def close_message
60
+ @@close_message ||= "</readapt-#{procid}>"
61
+ end
62
+ end
63
+ end
@@ -1,34 +1,34 @@
1
- require 'pathname'
2
-
3
- module Readapt
4
- # Methods to find a program in the current directory or one of the
5
- # environment paths.
6
- #
7
- module Finder
8
- module_function
9
-
10
- # Get the program's fully qualified path. Search first in the current
11
- # directory, then the environment paths.
12
- #
13
- # @raise [LoadError] if the program was not found
14
- #
15
- # @param program [String] The name of the program
16
- # @return [String] The fully qualified path
17
- def find program
18
- return program if File.exist?(program)
19
- which(program) || raise(LoadError, "#{program} is not a valid Ruby file or program")
20
- end
21
-
22
- # Search the environment paths for the given program.
23
- #
24
- # @param program [String] The name of the program
25
- # @return [String] The fully qualified path, or nil if it was not found
26
- def which program
27
- ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
28
- exe = File.join(path, program)
29
- return exe if File.file?(exe)
30
- end
31
- nil
32
- end
33
- end
34
- end
1
+ require 'pathname'
2
+
3
+ module Readapt
4
+ # Methods to find a program in the current directory or one of the
5
+ # environment paths.
6
+ #
7
+ module Finder
8
+ module_function
9
+
10
+ # Get the program's fully qualified path. Search first in the current
11
+ # directory, then the environment paths.
12
+ #
13
+ # @raise [LoadError] if the program was not found
14
+ #
15
+ # @param program [String] The name of the program
16
+ # @return [String] The fully qualified path
17
+ def find program
18
+ return program if File.exist?(program)
19
+ which(program) || raise(LoadError, "#{program} is not a valid Ruby file or program")
20
+ end
21
+
22
+ # Search the environment paths for the given program.
23
+ #
24
+ # @param program [String] The name of the program
25
+ # @return [String] The fully qualified path, or nil if it was not found
26
+ def which program
27
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
28
+ exe = File.join(path, program)
29
+ return exe if File.file?(exe)
30
+ end
31
+ nil
32
+ end
33
+ end
34
+ end