ruby-debug-ide-docker 0.6.1

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