ruby-debug-ide-docker 0.6.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/CHANGES +75 -0
- data/ChangeLog.archive +1073 -0
- data/ChangeLog.md +594 -0
- data/Gemfile +28 -0
- data/MIT-LICENSE +24 -0
- data/Rakefile +42 -0
- data/bin/gdb_wrapper +96 -0
- data/bin/rdebug-ide +183 -0
- data/ext/mkrf_conf.rb +48 -0
- data/lib/ruby-debug-ide.rb +173 -0
- data/lib/ruby-debug-ide/attach/debugger_loader.rb +20 -0
- data/lib/ruby-debug-ide/attach/gdb.rb +73 -0
- data/lib/ruby-debug-ide/attach/lldb.rb +71 -0
- data/lib/ruby-debug-ide/attach/native_debugger.rb +133 -0
- data/lib/ruby-debug-ide/attach/process_thread.rb +54 -0
- data/lib/ruby-debug-ide/attach/util.rb +115 -0
- data/lib/ruby-debug-ide/command.rb +177 -0
- data/lib/ruby-debug-ide/commands/breakpoints.rb +128 -0
- data/lib/ruby-debug-ide/commands/catchpoint.rb +64 -0
- data/lib/ruby-debug-ide/commands/condition.rb +51 -0
- data/lib/ruby-debug-ide/commands/control.rb +158 -0
- data/lib/ruby-debug-ide/commands/enable.rb +203 -0
- data/lib/ruby-debug-ide/commands/eval.rb +64 -0
- data/lib/ruby-debug-ide/commands/expression_info.rb +71 -0
- data/lib/ruby-debug-ide/commands/file_filtering.rb +107 -0
- data/lib/ruby-debug-ide/commands/frame.rb +155 -0
- data/lib/ruby-debug-ide/commands/inspect.rb +25 -0
- data/lib/ruby-debug-ide/commands/jump.rb +73 -0
- data/lib/ruby-debug-ide/commands/load.rb +18 -0
- data/lib/ruby-debug-ide/commands/pause.rb +33 -0
- data/lib/ruby-debug-ide/commands/set_type.rb +47 -0
- data/lib/ruby-debug-ide/commands/stepping.rb +108 -0
- data/lib/ruby-debug-ide/commands/threads.rb +178 -0
- data/lib/ruby-debug-ide/commands/variables.rb +154 -0
- data/lib/ruby-debug-ide/event_processor.rb +71 -0
- data/lib/ruby-debug-ide/greeter.rb +40 -0
- data/lib/ruby-debug-ide/helper.rb +33 -0
- data/lib/ruby-debug-ide/ide_processor.rb +155 -0
- data/lib/ruby-debug-ide/interface.rb +45 -0
- data/lib/ruby-debug-ide/multiprocess.rb +23 -0
- data/lib/ruby-debug-ide/multiprocess/monkey.rb +47 -0
- data/lib/ruby-debug-ide/multiprocess/pre_child.rb +67 -0
- data/lib/ruby-debug-ide/multiprocess/starter.rb +11 -0
- data/lib/ruby-debug-ide/multiprocess/unmonkey.rb +31 -0
- data/lib/ruby-debug-ide/version.rb +3 -0
- data/lib/ruby-debug-ide/xml_printer.rb +545 -0
- data/ruby-debug-ide-docker.gemspec +51 -0
- metadata +110 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Debugger
|
4
|
+
class Interface
|
5
|
+
end
|
6
|
+
|
7
|
+
class LocalInterface < Interface
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
class RemoteInterface < Interface # :nodoc:
|
12
|
+
attr_accessor :command_queue
|
13
|
+
|
14
|
+
def initialize(socket)
|
15
|
+
@socket = socket
|
16
|
+
@command_queue = Queue.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def read_command
|
20
|
+
result = non_blocking_gets
|
21
|
+
raise IOError unless result
|
22
|
+
result.chomp
|
23
|
+
end
|
24
|
+
|
25
|
+
def print(*args)
|
26
|
+
@socket.printf(*args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def close
|
30
|
+
@socket.close
|
31
|
+
rescue IOError, SystemCallError
|
32
|
+
end
|
33
|
+
|
34
|
+
# Workaround for JRuby issue http://jira.codehaus.org/browse/JRUBY-2063
|
35
|
+
def non_blocking_gets
|
36
|
+
loop do
|
37
|
+
result, _, _ = IO.select( [@socket], nil, nil, 0.2 )
|
38
|
+
next unless result
|
39
|
+
return result[0].gets
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
if RUBY_VERSION < '1.9'
|
2
|
+
require 'ruby-debug-ide/multiprocess/pre_child'
|
3
|
+
else
|
4
|
+
require_relative 'multiprocess/pre_child'
|
5
|
+
end
|
6
|
+
|
7
|
+
module Debugger
|
8
|
+
module MultiProcess
|
9
|
+
class << self
|
10
|
+
def do_monkey
|
11
|
+
load File.expand_path(File.dirname(__FILE__) + '/multiprocess/monkey.rb')
|
12
|
+
end
|
13
|
+
|
14
|
+
def undo_monkey
|
15
|
+
if ENV['IDE_PROCESS_DISPATCHER']
|
16
|
+
load File.expand_path(File.dirname(__FILE__) + '/multiprocess/unmonkey.rb')
|
17
|
+
ruby_opts = ENV['RUBYOPT'].split(' ')
|
18
|
+
ENV['RUBYOPT'] = ruby_opts.keep_if {|opt| !opt.end_with?('ruby-debug-ide/multiprocess/starter')}.join(' ')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Debugger
|
2
|
+
module MultiProcess
|
3
|
+
def self.create_mp_fork(private=false)
|
4
|
+
%Q{
|
5
|
+
alias pre_debugger_fork fork
|
6
|
+
|
7
|
+
#{private ? "private" : ""}
|
8
|
+
def fork(*args)
|
9
|
+
if block_given?
|
10
|
+
return pre_debugger_fork{Debugger::MultiProcess::pre_child; yield}
|
11
|
+
end
|
12
|
+
result = pre_debugger_fork
|
13
|
+
Debugger::MultiProcess::pre_child unless result
|
14
|
+
result
|
15
|
+
end
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.create_mp_exec(private=false)
|
20
|
+
%Q{
|
21
|
+
alias pre_debugger_exec exec
|
22
|
+
|
23
|
+
#{private ? "private" : ""}
|
24
|
+
def exec(*args)
|
25
|
+
Debugger.interface.close
|
26
|
+
pre_debugger_exec(*args)
|
27
|
+
end
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module Kernel
|
34
|
+
class << self
|
35
|
+
module_eval Debugger::MultiProcess.create_mp_fork
|
36
|
+
module_eval Debugger::MultiProcess.create_mp_exec
|
37
|
+
end
|
38
|
+
module_eval Debugger::MultiProcess.create_mp_fork(true)
|
39
|
+
module_eval Debugger::MultiProcess.create_mp_exec(true)
|
40
|
+
end
|
41
|
+
|
42
|
+
module Process
|
43
|
+
class << self
|
44
|
+
module_eval Debugger::MultiProcess.create_mp_fork
|
45
|
+
module_eval Debugger::MultiProcess.create_mp_exec
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Debugger
|
2
|
+
module MultiProcess
|
3
|
+
class << self
|
4
|
+
def pre_child(options = nil)
|
5
|
+
require 'socket'
|
6
|
+
require 'ostruct'
|
7
|
+
|
8
|
+
host = ENV['DEBUGGER_HOST']
|
9
|
+
|
10
|
+
options ||= OpenStruct.new(
|
11
|
+
'frame_bind' => false,
|
12
|
+
'host' => host,
|
13
|
+
'load_mode' => false,
|
14
|
+
'port' => find_free_port(host),
|
15
|
+
'stop' => false,
|
16
|
+
'tracing' => false,
|
17
|
+
'int_handler' => true,
|
18
|
+
'cli_debug' => (ENV['DEBUGGER_CLI_DEBUG'] == 'true'),
|
19
|
+
'notify_dispatcher' => true,
|
20
|
+
'evaluation_timeout' => 10,
|
21
|
+
'trace_to_s' => false,
|
22
|
+
'debugger_memory_limit' => 10,
|
23
|
+
'inspect_time_limit' => 100
|
24
|
+
)
|
25
|
+
|
26
|
+
if(options.ignore_port)
|
27
|
+
options.port = find_free_port(options.host)
|
28
|
+
options.notify_dispatcher = true
|
29
|
+
end
|
30
|
+
|
31
|
+
start_debugger(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def start_debugger(options)
|
35
|
+
if Debugger.started?
|
36
|
+
# we're in forked child, only need to restart control thread
|
37
|
+
Debugger.breakpoints.clear
|
38
|
+
Debugger.control_thread = nil
|
39
|
+
Debugger.start_control(options.host, options.port, options.notify_dispatcher)
|
40
|
+
end
|
41
|
+
|
42
|
+
if options.int_handler
|
43
|
+
# install interruption handler
|
44
|
+
trap('INT') { Debugger.interrupt_last }
|
45
|
+
end
|
46
|
+
|
47
|
+
# set options
|
48
|
+
Debugger.keep_frame_binding = options.frame_bind
|
49
|
+
Debugger.tracing = options.tracing
|
50
|
+
Debugger.evaluation_timeout = options.evaluation_timeout
|
51
|
+
Debugger.trace_to_s = options.trace_to_s
|
52
|
+
Debugger.debugger_memory_limit = options.debugger_memory_limit
|
53
|
+
Debugger.inspect_time_limit = options.inspect_time_limit
|
54
|
+
Debugger.cli_debug = options.cli_debug
|
55
|
+
Debugger.prepare_debugger(options)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def find_free_port(host)
|
60
|
+
server = TCPServer.open(host, 58438)
|
61
|
+
port = server.addr[1]
|
62
|
+
server.close
|
63
|
+
port
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
if ENV['IDE_PROCESS_DISPATCHER']
|
2
|
+
require 'rubygems'
|
3
|
+
ENV['DEBUGGER_STORED_RUBYLIB'].split(File::PATH_SEPARATOR).each do |path|
|
4
|
+
next unless path =~ /ruby-debug-ide|ruby-debug-base|linecache|debase/
|
5
|
+
$LOAD_PATH << path
|
6
|
+
end
|
7
|
+
require 'ruby-debug-ide'
|
8
|
+
require 'ruby-debug-ide/multiprocess'
|
9
|
+
Debugger::MultiProcess::do_monkey
|
10
|
+
Debugger::MultiProcess::pre_child
|
11
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Debugger
|
2
|
+
module MultiProcess
|
3
|
+
def self.restore_fork
|
4
|
+
%Q{
|
5
|
+
alias fork pre_debugger_fork
|
6
|
+
}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.restore_exec
|
10
|
+
%Q{
|
11
|
+
alias exec pre_debugger_exec
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Kernel
|
18
|
+
class << self
|
19
|
+
module_eval Debugger::MultiProcess.restore_fork
|
20
|
+
module_eval Debugger::MultiProcess.restore_exec
|
21
|
+
end
|
22
|
+
module_eval Debugger::MultiProcess.restore_fork
|
23
|
+
module_eval Debugger::MultiProcess.restore_exec
|
24
|
+
end
|
25
|
+
|
26
|
+
module Process
|
27
|
+
class << self
|
28
|
+
module_eval Debugger::MultiProcess.restore_fork
|
29
|
+
module_eval Debugger::MultiProcess.restore_exec
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,545 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'cgi'
|
3
|
+
require 'monitor'
|
4
|
+
|
5
|
+
module Debugger
|
6
|
+
|
7
|
+
module OverflowMessageType
|
8
|
+
NIL_MESSAGE = lambda {|e| nil}
|
9
|
+
EXCEPTION_MESSAGE = lambda {|e| e.message}
|
10
|
+
SPECIAL_SYMBOL_MESSAGE = lambda {|e| '<?>'}
|
11
|
+
end
|
12
|
+
|
13
|
+
class ExecError
|
14
|
+
attr_reader :message
|
15
|
+
attr_reader :backtrace
|
16
|
+
|
17
|
+
def initialize(message, backtrace = [])
|
18
|
+
@message = message
|
19
|
+
@backtrace = backtrace
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class SimpleTimeLimitError < StandardError
|
24
|
+
attr_reader :message
|
25
|
+
|
26
|
+
def initialize(message)
|
27
|
+
@message = message
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class MemoryLimitError < ExecError;
|
32
|
+
end
|
33
|
+
|
34
|
+
class TimeLimitError < ExecError;
|
35
|
+
end
|
36
|
+
|
37
|
+
class XmlPrinter # :nodoc:
|
38
|
+
class ExceptionProxy
|
39
|
+
instance_methods.each {|m| undef_method m unless m =~ /(^__|^send$|^object_id$|^instance_variables$|^instance_eval$)/}
|
40
|
+
|
41
|
+
def initialize(exception)
|
42
|
+
@exception = exception
|
43
|
+
@message = exception.message
|
44
|
+
@backtrace = Debugger.cleanup_backtrace(exception.backtrace)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def method_missing(called, *args, &block)
|
49
|
+
@exception.__send__(called, *args, &block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.protect(mname)
|
54
|
+
return if instance_methods.include?("__#{mname}")
|
55
|
+
alias_method "__#{mname}", mname
|
56
|
+
class_eval %{
|
57
|
+
def #{mname}(*args, &block)
|
58
|
+
@@monitor.synchronize do
|
59
|
+
return unless @interface
|
60
|
+
__#{mname}(*args, &block)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
@@monitor = Monitor.new
|
67
|
+
attr_accessor :interface
|
68
|
+
|
69
|
+
def initialize(interface)
|
70
|
+
@interface = interface
|
71
|
+
end
|
72
|
+
|
73
|
+
def print_msg(*args)
|
74
|
+
msg, *args = args
|
75
|
+
xml_message = CGI.escapeHTML(msg % args)
|
76
|
+
print "<message>#{xml_message}</message>"
|
77
|
+
end
|
78
|
+
|
79
|
+
# Sends debug message to the frontend if XML debug logging flag (--xml-debug) is on.
|
80
|
+
def print_debug(*args)
|
81
|
+
Debugger.print_debug(*args)
|
82
|
+
if Debugger.xml_debug
|
83
|
+
msg, *args = args
|
84
|
+
xml_message = CGI.escapeHTML(msg % args)
|
85
|
+
@interface.print("<message debug='true'>#{xml_message}</message>")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def print_error(*args)
|
90
|
+
print_element("error") do
|
91
|
+
msg, *args = args
|
92
|
+
print CGI.escapeHTML(msg % args)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def print_frames(context, current_frame_id)
|
97
|
+
print_element("frames") do
|
98
|
+
(0...context.stack_size).each do |id|
|
99
|
+
print_frame(context, id, current_frame_id)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def print_current_frame(frame_pos)
|
105
|
+
print_debug "Selected frame no #{frame_pos}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def print_frame(context, frame_id, current_frame_id)
|
109
|
+
# idx + 1: one-based numbering as classic-debugger
|
110
|
+
file = context.frame_file(frame_id)
|
111
|
+
print "<frame no=\"%s\" file=\"%s\" line=\"%s\" #{"current='true' " if frame_id == current_frame_id}/>",
|
112
|
+
frame_id + 1, CGI.escapeHTML(File.expand_path(file)), context.frame_line(frame_id)
|
113
|
+
end
|
114
|
+
|
115
|
+
def print_contexts(contexts)
|
116
|
+
print_element("threads") do
|
117
|
+
contexts.each do |c|
|
118
|
+
print_context(c) unless c.ignored?
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def print_context(context)
|
124
|
+
print "<thread id=\"%s\" status=\"%s\" pid=\"%s\" #{current_thread_attr(context)}/>", context.thnum, context.thread.status, Process.pid
|
125
|
+
end
|
126
|
+
|
127
|
+
def print_variables(vars, kind)
|
128
|
+
print_element("variables") do
|
129
|
+
# print self at top position
|
130
|
+
print_variable('self', yield('self'), kind) if vars.include?('self')
|
131
|
+
vars.sort.each do |v|
|
132
|
+
print_variable(v, yield(v), kind) unless v == 'self'
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def print_array(array)
|
138
|
+
print_element("variables") do
|
139
|
+
index = 0
|
140
|
+
array.each {|e|
|
141
|
+
print_variable('[' + index.to_s + ']', e, 'instance')
|
142
|
+
index += 1
|
143
|
+
}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def print_hash(hash)
|
148
|
+
print_element("variables") do
|
149
|
+
hash.keys.each {|k|
|
150
|
+
if k.class.name == "String"
|
151
|
+
name = '\'' + k + '\''
|
152
|
+
else
|
153
|
+
name = exec_with_allocation_control(k, :to_s, OverflowMessageType::EXCEPTION_MESSAGE)
|
154
|
+
end
|
155
|
+
print_variable(name, hash[k], 'instance')
|
156
|
+
}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def print_string(string)
|
161
|
+
print_element("variables") do
|
162
|
+
if string.respond_to?('bytes')
|
163
|
+
bytes = string.bytes.to_a
|
164
|
+
InspectCommand.reference_result(bytes)
|
165
|
+
print_variable('bytes', bytes, 'instance')
|
166
|
+
end
|
167
|
+
print_variable('encoding', string.encoding, 'instance') if string.respond_to?('encoding')
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def exec_with_timeout(sec, error_message)
|
172
|
+
return yield if sec == nil or sec.zero?
|
173
|
+
if Thread.respond_to?(:critical) and Thread.critical
|
174
|
+
raise ThreadError, "timeout within critical session"
|
175
|
+
end
|
176
|
+
begin
|
177
|
+
x = Thread.current
|
178
|
+
y = DebugThread.start {
|
179
|
+
sleep sec
|
180
|
+
x.raise SimpleTimeLimitError.new(error_message) if x.alive?
|
181
|
+
}
|
182
|
+
yield sec
|
183
|
+
ensure
|
184
|
+
y.kill if y and y.alive?
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def exec_with_allocation_control(value, exec_method, overflow_message_type)
|
189
|
+
return value.send exec_method unless Debugger.trace_to_s
|
190
|
+
|
191
|
+
memory_limit = Debugger.debugger_memory_limit
|
192
|
+
time_limit = Debugger.inspect_time_limit
|
193
|
+
|
194
|
+
if defined?(JRUBY_VERSION) || RUBY_VERSION < '2.0' || memory_limit <= 0
|
195
|
+
return exec_with_timeout(time_limit * 1e-3, "Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.") { value.send exec_method }
|
196
|
+
end
|
197
|
+
|
198
|
+
require 'objspace'
|
199
|
+
trace_queue = Queue.new
|
200
|
+
|
201
|
+
inspect_thread = DebugThread.start do
|
202
|
+
start_alloc_size = ObjectSpace.memsize_of_all
|
203
|
+
start_time = Time.now.to_f
|
204
|
+
|
205
|
+
trace_point = TracePoint.new(:c_call, :call) do |tp|
|
206
|
+
curr_time = Time.now.to_f
|
207
|
+
|
208
|
+
if (curr_time - start_time) * 1e3 > time_limit
|
209
|
+
trace_queue << TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", caller.to_a)
|
210
|
+
trace_point.disable
|
211
|
+
inspect_thread.kill
|
212
|
+
end
|
213
|
+
|
214
|
+
next unless rand > 0.75
|
215
|
+
|
216
|
+
curr_alloc_size = ObjectSpace.memsize_of_all
|
217
|
+
start_alloc_size = curr_alloc_size if curr_alloc_size < start_alloc_size
|
218
|
+
|
219
|
+
if curr_alloc_size - start_alloc_size > 1e6 * memory_limit
|
220
|
+
trace_queue << MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", caller.to_a)
|
221
|
+
trace_point.disable
|
222
|
+
inspect_thread.kill
|
223
|
+
end
|
224
|
+
end
|
225
|
+
trace_point.enable
|
226
|
+
result = value.send exec_method
|
227
|
+
trace_queue << result
|
228
|
+
trace_point.disable
|
229
|
+
end
|
230
|
+
|
231
|
+
while(mes = trace_queue.pop)
|
232
|
+
if(mes.is_a? TimeLimitError or mes.is_a? MemoryLimitError)
|
233
|
+
print_debug(mes.message + "\n" + mes.backtrace.map {|l| "\t#{l}"}.join("\n"))
|
234
|
+
return overflow_message_type.call(mes)
|
235
|
+
else
|
236
|
+
return mes
|
237
|
+
end
|
238
|
+
end
|
239
|
+
rescue SimpleTimeLimitError => e
|
240
|
+
print_debug(e.message)
|
241
|
+
return overflow_message_type.call(e)
|
242
|
+
end
|
243
|
+
|
244
|
+
def print_variable(name, value, kind)
|
245
|
+
name = name.to_s
|
246
|
+
if value.nil?
|
247
|
+
print("<variable name=\"%s\" kind=\"%s\"/>", CGI.escapeHTML(name), kind)
|
248
|
+
return
|
249
|
+
end
|
250
|
+
if value.is_a?(Array) || value.is_a?(Hash)
|
251
|
+
has_children = !value.empty?
|
252
|
+
if has_children
|
253
|
+
size = value.size
|
254
|
+
value_str = "#{value.class} (#{value.size} element#{size > 1 ? "s" : "" })"
|
255
|
+
else
|
256
|
+
value_str = "Empty #{value.class}"
|
257
|
+
end
|
258
|
+
elsif value.is_a?(String)
|
259
|
+
has_children = value.respond_to?('bytes') || value.respond_to?('encoding')
|
260
|
+
value_str = value
|
261
|
+
else
|
262
|
+
has_children = !value.instance_variables.empty? || !value.class.class_variables.empty?
|
263
|
+
|
264
|
+
value_str = exec_with_allocation_control(value, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) || 'nil' rescue "<#to_s method raised exception: #{$!}>"
|
265
|
+
unless value_str.is_a?(String)
|
266
|
+
value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String."
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
if value_str.respond_to?('encode')
|
271
|
+
# noinspection RubyEmptyRescueBlockInspection
|
272
|
+
begin
|
273
|
+
value_str = value_str.encode("UTF-8")
|
274
|
+
rescue
|
275
|
+
end
|
276
|
+
end
|
277
|
+
value_str = handle_binary_data(value_str)
|
278
|
+
escaped_value_str = CGI.escapeHTML(value_str)
|
279
|
+
print("<variable name=\"%s\" %s kind=\"%s\" %s type=\"%s\" hasChildren=\"%s\" objectId=\"%#+x\">",
|
280
|
+
CGI.escapeHTML(name), build_compact_value_attr(value, value_str), kind,
|
281
|
+
build_value_attr(escaped_value_str), value.class,
|
282
|
+
has_children, value.object_id)
|
283
|
+
print("<value><![CDATA[%s]]></value>", escaped_value_str) if Debugger.value_as_nested_element
|
284
|
+
print('</variable>')
|
285
|
+
rescue StandardError => e
|
286
|
+
print_debug "Unexpected exception \"%s\"\n%s", e.to_s, e.backtrace.join("\n")
|
287
|
+
print("<variable name=\"%s\" kind=\"%s\" value=\"%s\"/>",
|
288
|
+
CGI.escapeHTML(name), kind, CGI.escapeHTML(safe_to_string(value)))
|
289
|
+
end
|
290
|
+
|
291
|
+
def print_file_included(file)
|
292
|
+
print("<fileIncluded file=\"%s\"/>", file)
|
293
|
+
end
|
294
|
+
|
295
|
+
def print_file_excluded(file)
|
296
|
+
print("<fileExcluded file=\"%s\"/>", file)
|
297
|
+
end
|
298
|
+
|
299
|
+
def print_file_filter_status(status)
|
300
|
+
print("<fileFilter status=\"%s\"/>", status)
|
301
|
+
end
|
302
|
+
|
303
|
+
def print_breakpoints(breakpoints)
|
304
|
+
print_element 'breakpoints' do
|
305
|
+
breakpoints.sort_by {|b| b.id}.each do |b|
|
306
|
+
print "<breakpoint n=\"%d\" file=\"%s\" line=\"%s\" />", b.id, CGI.escapeHTML(b.source), b.pos.to_s
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def print_breakpoint_added(b)
|
312
|
+
print "<breakpointAdded no=\"%s\" location=\"%s:%s\"/>", b.id, CGI.escapeHTML(b.source), b.pos
|
313
|
+
end
|
314
|
+
|
315
|
+
def print_breakpoint_deleted(b)
|
316
|
+
print "<breakpointDeleted no=\"%s\"/>", b.id
|
317
|
+
end
|
318
|
+
|
319
|
+
def print_breakpoint_enabled(b)
|
320
|
+
print "<breakpointEnabled bp_id=\"%s\"/>", b.id
|
321
|
+
end
|
322
|
+
|
323
|
+
def print_breakpoint_disabled(b)
|
324
|
+
print "<breakpointDisabled bp_id=\"%s\"/>", b.id
|
325
|
+
end
|
326
|
+
|
327
|
+
def print_contdition_set(bp_id)
|
328
|
+
print "<conditionSet bp_id=\"%d\"/>", bp_id
|
329
|
+
end
|
330
|
+
|
331
|
+
def print_catchpoint_set(exception_class_name)
|
332
|
+
print "<catchpointSet exception=\"%s\"/>", exception_class_name
|
333
|
+
end
|
334
|
+
|
335
|
+
def print_catchpoint_deleted(exception_class_name)
|
336
|
+
if Debugger.catchpoint_deleted_event
|
337
|
+
print "<catchpointDeleted exception=\"%s\"/>", exception_class_name
|
338
|
+
else
|
339
|
+
print_catchpoint_set(exception_class_name)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def print_expressions(exps)
|
344
|
+
print_element "expressions" do
|
345
|
+
exps.each_with_index do |(exp, value), idx|
|
346
|
+
print_expression(exp, value, idx + 1)
|
347
|
+
end
|
348
|
+
end unless exps.empty?
|
349
|
+
end
|
350
|
+
|
351
|
+
def print_expression(exp, value, idx)
|
352
|
+
print "<dispay name=\"%s\" value=\"%s\" no=\"%d\" />", exp, value, idx
|
353
|
+
end
|
354
|
+
|
355
|
+
def print_expression_info(incomplete, prompt, indent)
|
356
|
+
print "<expressionInfo incomplete=\"%s\" prompt=\"%s\" indent=\"%s\"></expressionInfo>",
|
357
|
+
incomplete, CGI.escapeHTML(prompt), indent
|
358
|
+
end
|
359
|
+
|
360
|
+
def print_eval(exp, value)
|
361
|
+
print "<eval expression=\"%s\" value=\"%s\" />", CGI.escapeHTML(exp), value
|
362
|
+
end
|
363
|
+
|
364
|
+
def print_pp(value)
|
365
|
+
print value
|
366
|
+
end
|
367
|
+
|
368
|
+
def print_list(b, e, file, line)
|
369
|
+
print "[%d, %d] in %s\n", b, e, file
|
370
|
+
if (lines = Debugger.source_for(file))
|
371
|
+
b.upto(e) do |n|
|
372
|
+
if n > 0 && lines[n - 1]
|
373
|
+
if n == line
|
374
|
+
print "=> %d %s\n", n, lines[n - 1].chomp
|
375
|
+
else
|
376
|
+
print " %d %s\n", n, lines[n - 1].chomp
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
else
|
381
|
+
print "No source-file available for %s\n", file
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def print_methods(methods)
|
386
|
+
print_element "methods" do
|
387
|
+
methods.each do |method|
|
388
|
+
print "<method name=\"%s\" />", method
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
# Events
|
394
|
+
|
395
|
+
def print_breakpoint(_, breakpoint)
|
396
|
+
print("<breakpoint file=\"%s\" line=\"%s\" threadId=\"%d\"/>",
|
397
|
+
CGI.escapeHTML(breakpoint.source), breakpoint.pos, Debugger.current_context.thnum)
|
398
|
+
end
|
399
|
+
|
400
|
+
def print_catchpoint(exception)
|
401
|
+
context = Debugger.current_context
|
402
|
+
print("<exception file=\"%s\" line=\"%s\" type=\"%s\" message=\"%s\" threadId=\"%d\"/>",
|
403
|
+
CGI.escapeHTML(context.frame_file(0)), context.frame_line(0), exception.class, CGI.escapeHTML(exception.to_s), context.thnum)
|
404
|
+
end
|
405
|
+
|
406
|
+
def print_trace(context, file, line)
|
407
|
+
Debugger::print_debug "trace: location=\"%s:%s\", threadId=%d", file, line, context.thnum
|
408
|
+
# TBD: do we want to clog fronend with the <trace> elements? There are tons of them.
|
409
|
+
# print "<trace file=\"%s\" line=\"%s\" threadId=\"%d\" />", file, line, context.thnum
|
410
|
+
end
|
411
|
+
|
412
|
+
def print_at_line(context, file, line)
|
413
|
+
print "<suspended file=\"%s\" line=\"%s\" threadId=\"%d\" frames=\"%d\"/>",
|
414
|
+
CGI.escapeHTML(File.expand_path(file)), line, context.thnum, context.stack_size
|
415
|
+
end
|
416
|
+
|
417
|
+
def print_exception(exception, _)
|
418
|
+
print_element("variables") do
|
419
|
+
proxy = ExceptionProxy.new(exception)
|
420
|
+
InspectCommand.reference_result(proxy)
|
421
|
+
print_variable('error', proxy, 'exception')
|
422
|
+
end
|
423
|
+
rescue Exception
|
424
|
+
print "<processingException type=\"%s\" message=\"%s\"/>",
|
425
|
+
exception.class, CGI.escapeHTML(exception.to_s)
|
426
|
+
end
|
427
|
+
|
428
|
+
def print_inspect(eval_result)
|
429
|
+
print_element("variables") do
|
430
|
+
print_variable("eval_result", eval_result, 'local')
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
def print_load_result(file, exception = nil)
|
435
|
+
if exception
|
436
|
+
print("<loadResult file=\"%s\" exceptionType=\"%s\" exceptionMessage=\"%s\"/>", file, exception.class, CGI.escapeHTML(exception.to_s))
|
437
|
+
else
|
438
|
+
print("<loadResult file=\"%s\" status=\"OK\"/>", file)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def print_element(name)
|
443
|
+
print("<#{name}>")
|
444
|
+
begin
|
445
|
+
yield
|
446
|
+
ensure
|
447
|
+
print("</#{name}>")
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
private
|
452
|
+
|
453
|
+
def print(*params)
|
454
|
+
Debugger::print_debug(*params)
|
455
|
+
@interface.print(*params)
|
456
|
+
end
|
457
|
+
|
458
|
+
def handle_binary_data(value)
|
459
|
+
return '[Binary Data]' if (value.respond_to?('is_binary_data?') && value.is_binary_data?)
|
460
|
+
return '[Invalid encoding]' if (value.respond_to?('valid_encoding?') && !value.valid_encoding?)
|
461
|
+
value
|
462
|
+
end
|
463
|
+
|
464
|
+
def current_thread_attr(context)
|
465
|
+
if context.thread == Thread.current
|
466
|
+
'current="yes"'
|
467
|
+
else
|
468
|
+
''
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
def build_compact_name(value, value_str)
|
473
|
+
return compact_array_str(value) if value.is_a?(Array)
|
474
|
+
return compact_hash_str(value) if value.is_a?(Hash)
|
475
|
+
return value_str[0..max_compact_name_size - 3] + '...' if value_str.size > max_compact_name_size
|
476
|
+
nil
|
477
|
+
rescue ::Exception => e
|
478
|
+
print_debug(e)
|
479
|
+
nil
|
480
|
+
end
|
481
|
+
|
482
|
+
def max_compact_name_size
|
483
|
+
# todo: do we want to configure it?
|
484
|
+
50
|
485
|
+
end
|
486
|
+
|
487
|
+
def compact_array_str(value)
|
488
|
+
slice = value[0..10]
|
489
|
+
|
490
|
+
compact = exec_with_allocation_control(slice, :inspect, OverflowMessageType::NIL_MESSAGE)
|
491
|
+
|
492
|
+
if compact && value.size != slice.size
|
493
|
+
compact[0..compact.size - 2] + ", ...]"
|
494
|
+
end
|
495
|
+
compact
|
496
|
+
end
|
497
|
+
|
498
|
+
def compact_hash_str(value)
|
499
|
+
keys_strings = Hash.new
|
500
|
+
|
501
|
+
slice = value.sort_by do |k, _|
|
502
|
+
keys_string = exec_with_allocation_control(k, :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE)
|
503
|
+
keys_strings[k] = keys_string
|
504
|
+
keys_string
|
505
|
+
end[0..5]
|
506
|
+
|
507
|
+
compact = slice.map do |kv|
|
508
|
+
key_string = keys_strings[kv[0]]
|
509
|
+
value_string = exec_with_allocation_control(kv[1], :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE)
|
510
|
+
"#{key_string}: #{handle_binary_data(value_string)}"
|
511
|
+
end.join(", ")
|
512
|
+
"{" + compact + (slice.size != value.size ? ", ..." : "") + "}"
|
513
|
+
end
|
514
|
+
|
515
|
+
def build_compact_value_attr(value, value_str)
|
516
|
+
compact_value_str = build_compact_name(value, value_str)
|
517
|
+
compact_value_str.nil? ? '' : "compactValue=\"#{CGI.escapeHTML(compact_value_str)}\""
|
518
|
+
end
|
519
|
+
|
520
|
+
def safe_to_string(value)
|
521
|
+
begin
|
522
|
+
str = value.to_s
|
523
|
+
rescue NoMethodError
|
524
|
+
str = "(Object doesn't support #to_s)"
|
525
|
+
end
|
526
|
+
return str unless str.nil?
|
527
|
+
|
528
|
+
string_io = StringIO.new
|
529
|
+
string_io.write(value)
|
530
|
+
string_io.string
|
531
|
+
end
|
532
|
+
|
533
|
+
def build_value_attr(escaped_value_str)
|
534
|
+
Debugger.value_as_nested_element ? '' : "value=\"#{escaped_value_str}\""
|
535
|
+
end
|
536
|
+
|
537
|
+
instance_methods.each do |m|
|
538
|
+
if m.to_s.index('print_') == 0
|
539
|
+
protect m
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
end
|
544
|
+
|
545
|
+
end
|