readapt 0.3.1

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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +13 -0
  5. data/CHANGELOG.md +15 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +29 -0
  9. data/Rakefile +25 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/exe/readapt +5 -0
  13. data/ext/readapt/extconf.rb +3 -0
  14. data/ext/readapt/monitor.c +332 -0
  15. data/ext/readapt/monitor.h +1 -0
  16. data/ext/readapt/readapt.c +12 -0
  17. data/ext/readapt/threads.c +101 -0
  18. data/ext/readapt/threads.h +18 -0
  19. data/lib/readapt.rb +34 -0
  20. data/lib/readapt/adapter.rb +138 -0
  21. data/lib/readapt/breakpoint.rb +16 -0
  22. data/lib/readapt/breakpoints.rb +58 -0
  23. data/lib/readapt/debugger.rb +173 -0
  24. data/lib/readapt/finder.rb +20 -0
  25. data/lib/readapt/frame.rb +68 -0
  26. data/lib/readapt/location.rb +25 -0
  27. data/lib/readapt/message.rb +57 -0
  28. data/lib/readapt/message/attach.rb +11 -0
  29. data/lib/readapt/message/base.rb +32 -0
  30. data/lib/readapt/message/configuration_done.rb +11 -0
  31. data/lib/readapt/message/continue.rb +15 -0
  32. data/lib/readapt/message/disconnect.rb +14 -0
  33. data/lib/readapt/message/initialize.rb +13 -0
  34. data/lib/readapt/message/launch.rb +11 -0
  35. data/lib/readapt/message/next.rb +12 -0
  36. data/lib/readapt/message/pause.rb +11 -0
  37. data/lib/readapt/message/scopes.rb +25 -0
  38. data/lib/readapt/message/set_breakpoints.rb +26 -0
  39. data/lib/readapt/message/set_exception_breakpoints.rb +8 -0
  40. data/lib/readapt/message/stack_trace.rb +26 -0
  41. data/lib/readapt/message/step_in.rb +11 -0
  42. data/lib/readapt/message/step_out.rb +11 -0
  43. data/lib/readapt/message/threads.rb +18 -0
  44. data/lib/readapt/message/variables.rb +57 -0
  45. data/lib/readapt/monitor.rb +31 -0
  46. data/lib/readapt/shell.rb +48 -0
  47. data/lib/readapt/snapshot.rb +50 -0
  48. data/lib/readapt/thread.rb +39 -0
  49. data/lib/readapt/variable.rb +70 -0
  50. data/lib/readapt/version.rb +3 -0
  51. data/readapt.gemspec +39 -0
  52. metadata +184 -0
@@ -0,0 +1 @@
1
+ void initialize_monitor(VALUE);
@@ -0,0 +1,12 @@
1
+ #include "ruby.h"
2
+ #include "ruby/debug.h"
3
+ #include "monitor.h"
4
+
5
+ static VALUE m_Readapt;
6
+
7
+ void Init_readapt()
8
+ {
9
+ m_Readapt = rb_define_module("Readapt");
10
+
11
+ initialize_monitor(m_Readapt);
12
+ }
@@ -0,0 +1,101 @@
1
+ #include "ruby.h"
2
+ #include "ruby/debug.h"
3
+ #include "threads.h"
4
+
5
+ static VALUE threads;
6
+
7
+ void thread_reference_free(void* data)
8
+ {
9
+ free(data);
10
+ }
11
+
12
+ size_t thread_reference_size(const void* data)
13
+ {
14
+ return sizeof(thread_reference_t);
15
+ }
16
+
17
+ static const rb_data_type_t thread_reference_type = {
18
+ .wrap_struct_name = "thread_reference",
19
+ .function = {
20
+ .dmark = NULL,
21
+ .dfree = thread_reference_free,
22
+ .dsize = thread_reference_size,
23
+ },
24
+ .data = NULL,
25
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
26
+ };
27
+
28
+ void initialize_threads()
29
+ {
30
+ threads = rb_hash_new();
31
+ rb_global_variable(&threads);
32
+ }
33
+
34
+ VALUE thread_reference_new(VALUE thr)
35
+ {
36
+ thread_reference_t *data = malloc(sizeof(thread_reference_t));
37
+ VALUE obj = TypedData_Make_Struct(rb_cData, thread_reference_t, &thread_reference_type, data);
38
+ data->id = NUM2LONG(rb_funcall(thr, rb_intern("object_id"), 0));
39
+ data->depth = 0;
40
+ data->cursor = 0;
41
+ data->control = rb_intern("continue");
42
+ return obj;
43
+ }
44
+
45
+ thread_reference_t *thread_reference_pointer(VALUE ref)
46
+ {
47
+ thread_reference_t *ptr;
48
+ TypedData_Get_Struct(ref, thread_reference_t, &thread_reference_type, ptr);
49
+ return ptr;
50
+ }
51
+
52
+ VALUE thread_current_reference()
53
+ {
54
+ return thread_reference(rb_thread_current());
55
+ }
56
+
57
+ VALUE thread_reference(VALUE thr)
58
+ {
59
+ return rb_hash_aref(threads, rb_obj_id(thr));
60
+ }
61
+
62
+ VALUE thread_reference_id(VALUE id)
63
+ {
64
+ return rb_hash_aref(threads, id);
65
+ }
66
+
67
+ VALUE thread_add_reference(VALUE thr)
68
+ {
69
+ VALUE ref;
70
+
71
+ ref = thread_reference_new(thr);
72
+ rb_hash_aset(threads, rb_obj_id(thr), ref);
73
+ return ref;
74
+ }
75
+
76
+ VALUE thread_delete_reference(VALUE thr)
77
+ {
78
+ rb_hash_delete(threads, thr);
79
+ return Qnil;
80
+ }
81
+
82
+ void thread_pause()
83
+ {
84
+ VALUE refs, r;
85
+ thread_reference_t *ptr;
86
+ long len, i;
87
+
88
+ refs = rb_funcall(threads, rb_intern("values"), 0);
89
+ len = rb_array_len(refs);
90
+ for (i = 0; i < len; i++)
91
+ {
92
+ r = rb_ary_entry(refs, i);
93
+ ptr = thread_reference_pointer(r);
94
+ ptr->control = rb_intern("pause");
95
+ }
96
+ }
97
+
98
+ void thread_reset()
99
+ {
100
+ rb_funcall(threads, rb_intern("clear"), 0);
101
+ }
@@ -0,0 +1,18 @@
1
+ typedef struct thread_reference_struct {
2
+ long id;
3
+ int depth;
4
+ int cursor;
5
+ VALUE prev_file;
6
+ int prev_line;
7
+ ID control;
8
+ } thread_reference_t;
9
+
10
+ void initialize_threads();
11
+ VALUE thread_current_reference();
12
+ VALUE thread_reference(VALUE);
13
+ VALUE thread_reference_id(VALUE);
14
+ VALUE thread_add_reference(VALUE);
15
+ VALUE thread_delete_reference(VALUE);
16
+ thread_reference_t *thread_reference_pointer(VALUE);
17
+ void thread_pause();
18
+ void thread_reset();
data/lib/readapt.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'backport'
2
+
3
+ require "readapt/version"
4
+ require 'readapt/location'
5
+ require 'readapt/breakpoint'
6
+ require 'readapt/breakpoints'
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/adapter'
16
+ require 'readapt/readapt'
17
+ require 'readapt/shell'
18
+
19
+ module Readapt
20
+ class Error < StandardError; end
21
+ # Your code goes here...
22
+ end
23
+
24
+ Readapt.module_exec do
25
+ if (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
26
+ define_singleton_method :normalize_path do |path|
27
+ path[0].upcase + path[1..-1].gsub('\\', '/')
28
+ end
29
+ else
30
+ define_singleton_method :normalize_path do |path|
31
+ path
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,138 @@
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 format result
16
+ write_line result.to_protocol.to_json
17
+ end
18
+
19
+ def opening
20
+ @@debugger.add_observer self
21
+ @data_reader = DataReader.new
22
+ @data_reader.set_message_handler do |message|
23
+ process message
24
+ end
25
+ end
26
+
27
+ def closing
28
+ @@debugger.delete_observer(self)
29
+ end
30
+
31
+ def receiving data
32
+ @data_reader.receive data
33
+ end
34
+
35
+ def update event, data
36
+ obj = {
37
+ type: 'event',
38
+ event: event
39
+ }
40
+ obj[:body] = data unless data.nil?
41
+ json = obj.to_json
42
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
43
+ write envelope
44
+ end
45
+
46
+ private
47
+
48
+ # @param data [Hash]
49
+ # @return [void]
50
+ def process data
51
+ # @todo Better solution than nil frames
52
+ message = Message.process(data, @@debugger)
53
+ if data['seq']
54
+ json = {
55
+ type: 'response',
56
+ request_seq: data['seq'],
57
+ success: true,
58
+ command: data['command'],
59
+ body: message.body
60
+ }.to_json
61
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
62
+ write envelope
63
+ close if data['command'] == 'disconnect'
64
+ return unless data['command'] == 'initialize'
65
+ json = {
66
+ type: 'event',
67
+ event: 'initialized'
68
+ }.to_json
69
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
70
+ write envelope
71
+ end
72
+ rescue Exception => e
73
+ STDERR.puts e.message
74
+ STDERR.puts e.backtrace
75
+ end
76
+ end
77
+
78
+ class DataReader
79
+ def initialize
80
+ @in_header = true
81
+ @content_length = 0
82
+ @buffer = String.new
83
+ end
84
+
85
+ # Declare a block to be executed for each message received from the
86
+ # client.
87
+ #
88
+ # @yieldparam [Hash] The message received from the client
89
+ def set_message_handler &block
90
+ @message_handler = block
91
+ end
92
+
93
+ # Process raw data received from the client. The data will be parsed
94
+ # into messages based on the JSON-RPC protocol. Each message will be
95
+ # passed to the block declared via set_message_handler. Incomplete data
96
+ # will be buffered and subsequent data will be appended to the buffer.
97
+ #
98
+ # @param data [String]
99
+ def receive data
100
+ data.each_char do |char|
101
+ @buffer.concat char
102
+ if @in_header
103
+ prepare_to_parse_message if @buffer.end_with?("\r\n\r\n")
104
+ else
105
+ parse_message_from_buffer if @buffer.bytesize == @content_length
106
+ end
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def prepare_to_parse_message
113
+ @in_header = false
114
+ @buffer.each_line do |line|
115
+ parts = line.split(':').map(&:strip)
116
+ if parts[0] == 'Content-Length'
117
+ @content_length = parts[1].to_i
118
+ break
119
+ end
120
+ end
121
+ @buffer.clear
122
+ end
123
+
124
+ def parse_message_from_buffer
125
+ begin
126
+ msg = JSON.parse(@buffer)
127
+ @message_handler.call msg unless @message_handler.nil?
128
+ rescue JSON::ParserError => e
129
+ Solargraph::Logging.logger.warn "Failed to parse request: #{e.message}"
130
+ Solargraph::Logging.logger.debug "Buffer: #{@buffer}"
131
+ ensure
132
+ @buffer.clear
133
+ @in_header = true
134
+ @content_length = 0
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Readapt
4
+ class Breakpoint < Location
5
+ attr_reader :verified
6
+
7
+ def initialize file, line, verified = true
8
+ super(file, line)
9
+ @verified = verified
10
+ end
11
+
12
+ def enabled?
13
+ @verified
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,58 @@
1
+ require 'observer'
2
+
3
+ module Readapt
4
+ class Breakpoints
5
+ include Observable
6
+
7
+ def initialize
8
+ @sources = {}
9
+ @concatting = false
10
+ end
11
+
12
+ # @return [Array<String>]
13
+ def sources
14
+ @sources.keys
15
+ end
16
+
17
+ # @param breakpoint [Breakpoint]
18
+ # @return [void]
19
+ def add breakpoint
20
+ @sources[breakpoint.file] ||= []
21
+ @sources[breakpoint.file].push breakpoint
22
+ changed
23
+ notify_observers unless @concatting
24
+ end
25
+
26
+ # @param breakpoints [Array<Breakpoint>]
27
+ # @return [void]
28
+ def concat breakpoints
29
+ @concatting = true
30
+ breakpoints.each { |bp| add bp }
31
+ @concatting = false
32
+ notify_observers
33
+ end
34
+
35
+ # @param file [String]
36
+ # @return [void]
37
+ def clear file
38
+ @sources.delete file
39
+ changed
40
+ notify_observers
41
+ end
42
+
43
+ # @param file [String]
44
+ # @return [Array<Breakpoint>]
45
+ def for file
46
+ @sources[file] || []
47
+ end
48
+
49
+ # @return [Array<Breakpoint>]
50
+ def all
51
+ @sources.values.flatten
52
+ end
53
+
54
+ def empty?
55
+ @sources.empty?
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,173 @@
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 machine = Machine.new
17
+ @stack = []
18
+ @threads = {}
19
+ @frames = {}
20
+ @running = false
21
+ @attached = false
22
+ @request = nil
23
+ @config = {}
24
+ @original_argv = ARGV.clone
25
+ @machine = machine
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
+ @threads[id] || Thread::NULL_THREAD
39
+ end
40
+
41
+ def threads
42
+ @threads.values
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
+ run { load @file }
60
+ end
61
+ end
62
+
63
+ def run
64
+ # raise RuntimeError, 'Debugger is already running' if @running
65
+ set_program_args
66
+ @running = true
67
+ send_event('process', {
68
+ name: @file
69
+ })
70
+ Monitor.start do |snapshot|
71
+ debug snapshot
72
+ end
73
+ yield if block_given?
74
+ rescue StandardError => e
75
+ STDERR.puts e.message
76
+ STDERR.puts e.backtrace.join("\n")
77
+ rescue SystemExit
78
+ # Ignore
79
+ ensure
80
+ Monitor.stop
81
+ @running = false
82
+ set_original_args
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 disconnect
95
+ shutdown if launched?
96
+ @request = nil
97
+ end
98
+
99
+ def self.run &block
100
+ new.run &block
101
+ end
102
+
103
+ private
104
+
105
+ # @param [Snapshot]
106
+ # return [void]
107
+ def debug snapshot
108
+ if (snapshot.event == :thread_begin)
109
+ thr = Thread.new(snapshot.thread_id)
110
+ thr.control = :continue
111
+ @threads[snapshot.thread_id] = thr
112
+ send_event('thread', {
113
+ reason: 'started',
114
+ threadId: snapshot.thread_id
115
+ }, true)
116
+ snapshot.control = :continue
117
+ elsif (snapshot.event == :thread_end)
118
+ thr = thread(snapshot.thread_id)
119
+ thr.control = :continue
120
+ @threads.delete snapshot.thread_id
121
+ send_event('thread', {
122
+ reason: 'exited',
123
+ threadId: snapshot.thread_id
124
+ })
125
+ snapshot.control = :continue
126
+ elsif snapshot.event == :initialize
127
+ if snapshot.file != @file
128
+ snapshot.control = :wait
129
+ else
130
+ snapshot.control = :ready
131
+ end
132
+ elsif snapshot.event == :entry
133
+ snapshot.control = :continue
134
+ else
135
+ changed
136
+ thread = self.thread(snapshot.thread_id)
137
+ thread.control = :pause
138
+ frame = Frame.new(Location.new(snapshot.file, snapshot.line), snapshot.binding_id)
139
+ thread.frames.push frame
140
+ @frames[frame.local_id] = frame
141
+ send_event('stopped', {
142
+ reason: snapshot.event,
143
+ threadId: ::Thread.current.object_id
144
+ })
145
+ sleep 0.01 until thread.control != :pause || !@threads.key?(thread.id)
146
+ @frames.delete frame.local_id
147
+ thread.frames.delete frame
148
+ snapshot.control = thread.control
149
+ end
150
+ end
151
+
152
+ def set_program_args
153
+ ARGV.clear
154
+ ARGV.replace(@config['programArgs'] || [])
155
+ end
156
+
157
+ def set_original_args
158
+ ARGV.clear
159
+ ARGV.replace @original_argv
160
+ end
161
+
162
+ def shutdown
163
+ @machine.stop
164
+ exit
165
+ end
166
+
167
+ def send_event event, data, wait = false
168
+ changed
169
+ notify_observers event, data
170
+ sleep 0.01 if wait
171
+ end
172
+ end
173
+ end