readapt 1.4.4 → 2.0.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 (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