readapt 0.3.1

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