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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +29 -0
- data/Rakefile +25 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/readapt +5 -0
- data/ext/readapt/extconf.rb +3 -0
- data/ext/readapt/monitor.c +332 -0
- data/ext/readapt/monitor.h +1 -0
- data/ext/readapt/readapt.c +12 -0
- data/ext/readapt/threads.c +101 -0
- data/ext/readapt/threads.h +18 -0
- data/lib/readapt.rb +34 -0
- data/lib/readapt/adapter.rb +138 -0
- data/lib/readapt/breakpoint.rb +16 -0
- data/lib/readapt/breakpoints.rb +58 -0
- data/lib/readapt/debugger.rb +173 -0
- data/lib/readapt/finder.rb +20 -0
- data/lib/readapt/frame.rb +68 -0
- data/lib/readapt/location.rb +25 -0
- data/lib/readapt/message.rb +57 -0
- data/lib/readapt/message/attach.rb +11 -0
- data/lib/readapt/message/base.rb +32 -0
- data/lib/readapt/message/configuration_done.rb +11 -0
- data/lib/readapt/message/continue.rb +15 -0
- data/lib/readapt/message/disconnect.rb +14 -0
- data/lib/readapt/message/initialize.rb +13 -0
- data/lib/readapt/message/launch.rb +11 -0
- data/lib/readapt/message/next.rb +12 -0
- data/lib/readapt/message/pause.rb +11 -0
- data/lib/readapt/message/scopes.rb +25 -0
- data/lib/readapt/message/set_breakpoints.rb +26 -0
- data/lib/readapt/message/set_exception_breakpoints.rb +8 -0
- data/lib/readapt/message/stack_trace.rb +26 -0
- data/lib/readapt/message/step_in.rb +11 -0
- data/lib/readapt/message/step_out.rb +11 -0
- data/lib/readapt/message/threads.rb +18 -0
- data/lib/readapt/message/variables.rb +57 -0
- data/lib/readapt/monitor.rb +31 -0
- data/lib/readapt/shell.rb +48 -0
- data/lib/readapt/snapshot.rb +50 -0
- data/lib/readapt/thread.rb +39 -0
- data/lib/readapt/variable.rb +70 -0
- data/lib/readapt/version.rb +3 -0
- data/readapt.gemspec +39 -0
- metadata +184 -0
@@ -0,0 +1 @@
|
|
1
|
+
void initialize_monitor(VALUE);
|
@@ -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
|