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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +75 -0
  3. data/ChangeLog.archive +1073 -0
  4. data/ChangeLog.md +594 -0
  5. data/Gemfile +28 -0
  6. data/MIT-LICENSE +24 -0
  7. data/Rakefile +42 -0
  8. data/bin/gdb_wrapper +96 -0
  9. data/bin/rdebug-ide +183 -0
  10. data/ext/mkrf_conf.rb +48 -0
  11. data/lib/ruby-debug-ide.rb +173 -0
  12. data/lib/ruby-debug-ide/attach/debugger_loader.rb +20 -0
  13. data/lib/ruby-debug-ide/attach/gdb.rb +73 -0
  14. data/lib/ruby-debug-ide/attach/lldb.rb +71 -0
  15. data/lib/ruby-debug-ide/attach/native_debugger.rb +133 -0
  16. data/lib/ruby-debug-ide/attach/process_thread.rb +54 -0
  17. data/lib/ruby-debug-ide/attach/util.rb +115 -0
  18. data/lib/ruby-debug-ide/command.rb +177 -0
  19. data/lib/ruby-debug-ide/commands/breakpoints.rb +128 -0
  20. data/lib/ruby-debug-ide/commands/catchpoint.rb +64 -0
  21. data/lib/ruby-debug-ide/commands/condition.rb +51 -0
  22. data/lib/ruby-debug-ide/commands/control.rb +158 -0
  23. data/lib/ruby-debug-ide/commands/enable.rb +203 -0
  24. data/lib/ruby-debug-ide/commands/eval.rb +64 -0
  25. data/lib/ruby-debug-ide/commands/expression_info.rb +71 -0
  26. data/lib/ruby-debug-ide/commands/file_filtering.rb +107 -0
  27. data/lib/ruby-debug-ide/commands/frame.rb +155 -0
  28. data/lib/ruby-debug-ide/commands/inspect.rb +25 -0
  29. data/lib/ruby-debug-ide/commands/jump.rb +73 -0
  30. data/lib/ruby-debug-ide/commands/load.rb +18 -0
  31. data/lib/ruby-debug-ide/commands/pause.rb +33 -0
  32. data/lib/ruby-debug-ide/commands/set_type.rb +47 -0
  33. data/lib/ruby-debug-ide/commands/stepping.rb +108 -0
  34. data/lib/ruby-debug-ide/commands/threads.rb +178 -0
  35. data/lib/ruby-debug-ide/commands/variables.rb +154 -0
  36. data/lib/ruby-debug-ide/event_processor.rb +71 -0
  37. data/lib/ruby-debug-ide/greeter.rb +40 -0
  38. data/lib/ruby-debug-ide/helper.rb +33 -0
  39. data/lib/ruby-debug-ide/ide_processor.rb +155 -0
  40. data/lib/ruby-debug-ide/interface.rb +45 -0
  41. data/lib/ruby-debug-ide/multiprocess.rb +23 -0
  42. data/lib/ruby-debug-ide/multiprocess/monkey.rb +47 -0
  43. data/lib/ruby-debug-ide/multiprocess/pre_child.rb +67 -0
  44. data/lib/ruby-debug-ide/multiprocess/starter.rb +11 -0
  45. data/lib/ruby-debug-ide/multiprocess/unmonkey.rb +31 -0
  46. data/lib/ruby-debug-ide/version.rb +3 -0
  47. data/lib/ruby-debug-ide/xml_printer.rb +545 -0
  48. data/ruby-debug-ide-docker.gemspec +51 -0
  49. 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,3 @@
1
+ module Debugger
2
+ IDE_VERSION='0.6.1'
3
+ 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