ruby-debug-ide22 0.7.4 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +75 -75
  3. data/ChangeLog.archive +1073 -1073
  4. data/ChangeLog.md +594 -594
  5. data/Gemfile +38 -38
  6. data/MIT-LICENSE +24 -24
  7. data/Rakefile +92 -92
  8. data/bin/gdb_wrapper +96 -96
  9. data/bin/rdebug-ide +200 -200
  10. data/ext/mkrf_conf.rb +44 -44
  11. data/lib/ruby-debug-ide/attach/debugger_loader.rb +20 -20
  12. data/lib/ruby-debug-ide/attach/gdb.rb +73 -73
  13. data/lib/ruby-debug-ide/attach/lldb.rb +71 -71
  14. data/lib/ruby-debug-ide/attach/native_debugger.rb +133 -133
  15. data/lib/ruby-debug-ide/attach/process_thread.rb +54 -54
  16. data/lib/ruby-debug-ide/attach/util.rb +114 -114
  17. data/lib/ruby-debug-ide/command.rb +187 -187
  18. data/lib/ruby-debug-ide/commands/breakpoints.rb +128 -128
  19. data/lib/ruby-debug-ide/commands/catchpoint.rb +64 -64
  20. data/lib/ruby-debug-ide/commands/condition.rb +51 -51
  21. data/lib/ruby-debug-ide/commands/control.rb +164 -158
  22. data/lib/ruby-debug-ide/commands/enable.rb +203 -203
  23. data/lib/ruby-debug-ide/commands/eval.rb +64 -64
  24. data/lib/ruby-debug-ide/commands/expression_info.rb +71 -71
  25. data/lib/ruby-debug-ide/commands/file_filtering.rb +106 -106
  26. data/lib/ruby-debug-ide/commands/frame.rb +155 -155
  27. data/lib/ruby-debug-ide/commands/inspect.rb +25 -25
  28. data/lib/ruby-debug-ide/commands/load.rb +17 -17
  29. data/lib/ruby-debug-ide/commands/stepping.rb +108 -108
  30. data/lib/ruby-debug-ide/commands/threads.rb +178 -178
  31. data/lib/ruby-debug-ide/commands/variables.rb +154 -154
  32. data/lib/ruby-debug-ide/event_processor.rb +71 -71
  33. data/lib/ruby-debug-ide/greeter.rb +42 -42
  34. data/lib/ruby-debug-ide/helper.rb +33 -33
  35. data/lib/ruby-debug-ide/ide_processor.rb +155 -155
  36. data/lib/ruby-debug-ide/interface.rb +47 -45
  37. data/lib/ruby-debug-ide/multiprocess/monkey.rb +46 -46
  38. data/lib/ruby-debug-ide/multiprocess/pre_child.rb +58 -58
  39. data/lib/ruby-debug-ide/multiprocess/starter.rb +10 -10
  40. data/lib/ruby-debug-ide/multiprocess/unmonkey.rb +30 -30
  41. data/lib/ruby-debug-ide/multiprocess.rb +22 -22
  42. data/lib/ruby-debug-ide/thread_alias.rb +26 -26
  43. data/lib/ruby-debug-ide/version.rb +3 -3
  44. data/lib/ruby-debug-ide/xml_printer.rb +570 -570
  45. data/lib/ruby-debug-ide.rb +230 -228
  46. data/ruby-debug-ide.gemspec +47 -47
  47. metadata +4 -4
@@ -1,571 +1,571 @@
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 do_print_hash_key_value(hash)
148
- print_element("variables", {:type => 'hashItem'}) do
149
- hash.each {|(k, v)|
150
- print_variable('key', k, 'instance')
151
- print_variable('value', v, 'instance')
152
- }
153
- end
154
- end
155
-
156
- def do_print_hash(hash)
157
- print_element("variables") do
158
- hash.each {|(k, v)|
159
- if k.class.name == "String"
160
- name = '\'' + k + '\''
161
- else
162
- name = exec_with_allocation_control(k, :to_s, OverflowMessageType::EXCEPTION_MESSAGE)
163
- end
164
-
165
- if k.nil?
166
- name = 'nil'
167
- end
168
-
169
- print_variable(name, v, 'instance')
170
- }
171
- end
172
- end
173
-
174
- def print_hash(hash)
175
- if Debugger.key_value_mode
176
- do_print_hash_key_value(hash)
177
- else
178
- do_print_hash(hash)
179
- end
180
- end
181
-
182
- def print_string(string)
183
- print_element("variables") do
184
- if string.respond_to?('bytes')
185
- bytes = string.bytes.to_a
186
- InspectCommand.reference_result(bytes)
187
- print_variable('bytes', bytes, 'instance')
188
- end
189
- print_variable('encoding', string.encoding, 'instance') if string.respond_to?('encoding')
190
- end
191
- end
192
-
193
- def exec_with_timeout(sec, error_message)
194
- return yield if sec == nil or sec.zero?
195
- if Thread.respond_to?(:critical) and Thread.critical
196
- raise ThreadError, "timeout within critical session"
197
- end
198
- begin
199
- x = Thread.current
200
- y = DebugThread.start {
201
- sleep sec
202
- x.raise SimpleTimeLimitError.new(error_message) if x.alive?
203
- }
204
- yield sec
205
- ensure
206
- y.kill if y and y.alive?
207
- end
208
- end
209
-
210
- def exec_with_allocation_control(value, exec_method, overflow_message_type)
211
- return value.__send__ exec_method unless Debugger.trace_to_s
212
-
213
- memory_limit = Debugger.debugger_memory_limit
214
- time_limit = Debugger.inspect_time_limit
215
-
216
- if defined?(JRUBY_VERSION) || RUBY_VERSION < '2.0' || memory_limit <= 0
217
- return exec_with_timeout(time_limit * 1e-3, "Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.") { value.__send__ exec_method }
218
- end
219
-
220
- require 'objspace'
221
- trace_queue = Queue.new
222
-
223
- inspect_thread = DebugThread.start do
224
- start_alloc_size = ObjectSpace.memsize_of_all
225
- start_time = Time.now.to_f
226
-
227
- trace_point = TracePoint.new(:c_call, :call) do |tp|
228
- curr_time = Time.now.to_f
229
-
230
- if (curr_time - start_time) * 1e3 > time_limit
231
- trace_queue << TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", caller.to_a)
232
- trace_point.disable
233
- inspect_thread.kill
234
- end
235
-
236
- next unless rand > 0.75
237
-
238
- curr_alloc_size = ObjectSpace.memsize_of_all
239
- start_alloc_size = curr_alloc_size if curr_alloc_size < start_alloc_size
240
-
241
- if curr_alloc_size - start_alloc_size > 1e6 * memory_limit
242
- trace_queue << MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", caller.to_a)
243
- trace_point.disable
244
- inspect_thread.kill
245
- end
246
- end
247
- trace_point.enable
248
- result = value.__send__ exec_method
249
- trace_queue << result
250
- trace_point.disable
251
- end
252
-
253
- while(mes = trace_queue.pop)
254
- if(mes.is_a? TimeLimitError or mes.is_a? MemoryLimitError)
255
- print_debug(mes.message + "\n" + mes.backtrace.map {|l| "\t#{l}"}.join("\n"))
256
- return overflow_message_type.call(mes)
257
- else
258
- return mes
259
- end
260
- end
261
- rescue SimpleTimeLimitError => e
262
- print_debug(e.message)
263
- return overflow_message_type.call(e)
264
- end
265
-
266
- def print_variable(name, value, kind)
267
- name = name.to_s
268
-
269
- if value.nil?
270
- print("<variable name=\"%s\" kind=\"%s\"/>", CGI.escapeHTML(name), kind)
271
- return
272
- end
273
- if value.is_a?(Array) || value.is_a?(Hash)
274
- has_children = !value.empty?
275
- if has_children
276
- size = value.size
277
- value_str = "#{value.class} (#{value.size} element#{size > 1 ? "s" : "" })"
278
- else
279
- value_str = "Empty #{value.class}"
280
- end
281
- elsif value.is_a?(String)
282
- has_children = value.respond_to?('bytes') || value.respond_to?('encoding')
283
- value_str = value
284
- else
285
- has_children = !value.instance_variables.empty? || !value.class.class_variables.empty?
286
-
287
- value_str = exec_with_allocation_control(value, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) || 'nil' rescue "<#to_s method raised exception: #{$!}>"
288
- unless value_str.is_a?(String)
289
- value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String."
290
- end
291
- end
292
-
293
- if value_str.respond_to?('encode')
294
- # noinspection RubyEmptyRescueBlockInspection
295
- begin
296
- value_str = value_str.encode("UTF-8")
297
- rescue
298
- end
299
- end
300
- value_str = handle_binary_data(value_str)
301
- escaped_value_str = CGI.escapeHTML(value_str)
302
- print("<variable name=\"%s\" %s kind=\"%s\" %s type=\"%s\" hasChildren=\"%s\" objectId=\"%#+x\">",
303
- CGI.escapeHTML(name), build_compact_value_attr(value, value_str), kind,
304
- build_value_attr(escaped_value_str), value.class,
305
- has_children, value.object_id)
306
-
307
- print("<value><![CDATA[%s]]></value>", escaped_value_str) if Debugger.value_as_nested_element
308
- print('</variable>')
309
- rescue StandardError => e
310
- print_debug "Unexpected exception \"%s\"\n%s", e.to_s, e.backtrace.join("\n")
311
- print("<variable name=\"%s\" kind=\"%s\" value=\"%s\"/>",
312
- CGI.escapeHTML(name), kind, CGI.escapeHTML(safe_to_string(value)))
313
- end
314
-
315
- def print_file_included(file)
316
- print("<fileIncluded file=\"%s\"/>", file)
317
- end
318
-
319
- def print_file_excluded(file)
320
- print("<fileExcluded file=\"%s\"/>", file)
321
- end
322
-
323
- def print_file_filter_status(status)
324
- print("<fileFilter status=\"%s\"/>", status)
325
- end
326
-
327
- def print_breakpoints(breakpoints)
328
- print_element 'breakpoints' do
329
- breakpoints.sort_by {|b| b.id}.each do |b|
330
- print "<breakpoint n=\"%d\" file=\"%s\" line=\"%s\" />", b.id, CGI.escapeHTML(b.source), b.pos.to_s
331
- end
332
- end
333
- end
334
-
335
- def print_breakpoint_added(b)
336
- print "<breakpointAdded no=\"%s\" location=\"%s:%s\"/>", b.id, CGI.escapeHTML(b.source), b.pos
337
- end
338
-
339
- def print_breakpoint_deleted(b)
340
- print "<breakpointDeleted no=\"%s\"/>", b.id
341
- end
342
-
343
- def print_breakpoint_enabled(b)
344
- print "<breakpointEnabled bp_id=\"%s\"/>", b.id
345
- end
346
-
347
- def print_breakpoint_disabled(b)
348
- print "<breakpointDisabled bp_id=\"%s\"/>", b.id
349
- end
350
-
351
- def print_contdition_set(bp_id)
352
- print "<conditionSet bp_id=\"%d\"/>", bp_id
353
- end
354
-
355
- def print_catchpoint_set(exception_class_name)
356
- print "<catchpointSet exception=\"%s\"/>", exception_class_name
357
- end
358
-
359
- def print_catchpoint_deleted(exception_class_name)
360
- if Debugger.catchpoint_deleted_event
361
- print "<catchpointDeleted exception=\"%s\"/>", exception_class_name
362
- else
363
- print_catchpoint_set(exception_class_name)
364
- end
365
- end
366
-
367
- def print_expressions(exps)
368
- print_element "expressions" do
369
- exps.each_with_index do |(exp, value), idx|
370
- print_expression(exp, value, idx + 1)
371
- end
372
- end unless exps.empty?
373
- end
374
-
375
- def print_expression(exp, value, idx)
376
- print "<dispay name=\"%s\" value=\"%s\" no=\"%d\" />", exp, value, idx
377
- end
378
-
379
- def print_expression_info(incomplete, prompt, indent)
380
- print "<expressionInfo incomplete=\"%s\" prompt=\"%s\" indent=\"%s\"></expressionInfo>",
381
- incomplete, CGI.escapeHTML(prompt), indent
382
- end
383
-
384
- def print_eval(exp, value)
385
- print "<eval expression=\"%s\" value=\"%s\" />", CGI.escapeHTML(exp), value
386
- end
387
-
388
- def print_pp(value)
389
- print value
390
- end
391
-
392
- def print_list(b, e, file, line)
393
- print "[%d, %d] in %s\n", b, e, file
394
- if (lines = Debugger.source_for(file))
395
- b.upto(e) do |n|
396
- if n > 0 && lines[n - 1]
397
- if n == line
398
- print "=> %d %s\n", n, lines[n - 1].chomp
399
- else
400
- print " %d %s\n", n, lines[n - 1].chomp
401
- end
402
- end
403
- end
404
- else
405
- print "No source-file available for %s\n", file
406
- end
407
- end
408
-
409
- def print_methods(methods)
410
- print_element "methods" do
411
- methods.each do |method|
412
- print "<method name=\"%s\" />", method
413
- end
414
- end
415
- end
416
-
417
- # Events
418
-
419
- def print_breakpoint(_, breakpoint)
420
- print("<breakpoint file=\"%s\" line=\"%s\" threadId=\"%d\"/>",
421
- CGI.escapeHTML(breakpoint.source), breakpoint.pos, Debugger.current_context.thnum)
422
- end
423
-
424
- def print_catchpoint(exception)
425
- context = Debugger.current_context
426
- print("<exception file=\"%s\" line=\"%s\" type=\"%s\" message=\"%s\" threadId=\"%d\"/>",
427
- CGI.escapeHTML(context.frame_file(0)), context.frame_line(0), exception.class, CGI.escapeHTML(exception.to_s), context.thnum)
428
- end
429
-
430
- def print_trace(context, file, line)
431
- Debugger::print_debug "trace: location=\"%s:%s\", threadId=%d", file, line, context.thnum
432
- # TBD: do we want to clog fronend with the <trace> elements? There are tons of them.
433
- # print "<trace file=\"%s\" line=\"%s\" threadId=\"%d\" />", file, line, context.thnum
434
- end
435
-
436
- def print_at_line(context, file, line)
437
- print "<suspended file=\"%s\" line=\"%s\" threadId=\"%d\" frames=\"%d\"/>",
438
- CGI.escapeHTML(File.expand_path(file)), line, context.thnum, context.stack_size
439
- end
440
-
441
- def print_exception(exception, _)
442
- print_element("variables") do
443
- proxy = ExceptionProxy.new(exception)
444
- InspectCommand.reference_result(proxy)
445
- print_variable('error', proxy, 'exception')
446
- end
447
- rescue Exception
448
- print "<processingException type=\"%s\" message=\"%s\"/>",
449
- exception.class, CGI.escapeHTML(exception.to_s)
450
- end
451
-
452
- def print_inspect(eval_result)
453
- print_element("variables") do
454
- print_variable("eval_result", eval_result, 'local')
455
- end
456
- end
457
-
458
- def print_load_result(file, exception = nil)
459
- if exception
460
- print("<loadResult file=\"%s\" exceptionType=\"%s\" exceptionMessage=\"%s\"/>", file, exception.class, CGI.escapeHTML(exception.to_s))
461
- else
462
- print("<loadResult file=\"%s\" status=\"OK\"/>", file)
463
- end
464
- end
465
-
466
- def print_element(name, additional_tags = nil)
467
- additional_tags_presentation = additional_tags.nil? ? '' : additional_tags.map {|tag, value| " #{tag}=\"#{value}\""}.reduce(:+)
468
-
469
- print("<#{name}#{additional_tags_presentation}>")
470
- begin
471
- yield if block_given?
472
- ensure
473
- print("</#{name}>")
474
- end
475
- end
476
-
477
- private
478
-
479
- def print(*params)
480
- Debugger::print_debug(*params)
481
- @interface.print(*params)
482
- end
483
-
484
- def handle_binary_data(value)
485
- return '[Binary Data]' if (value.respond_to?('is_binary_data?') && value.is_binary_data?)
486
- return '[Invalid encoding]' if (value.respond_to?('valid_encoding?') && !value.valid_encoding?)
487
- value
488
- end
489
-
490
- def current_thread_attr(context)
491
- if context.thread == Thread.current
492
- 'current="yes"'
493
- else
494
- ''
495
- end
496
- end
497
-
498
- def build_compact_name(value, value_str)
499
- return compact_array_str(value) if value.is_a?(Array)
500
- return compact_hash_str(value) if value.is_a?(Hash)
501
- return value_str[0..max_compact_name_size - 3] + '...' if value_str.size > max_compact_name_size
502
- nil
503
- rescue ::Exception => e
504
- print_debug(e)
505
- nil
506
- end
507
-
508
- def max_compact_name_size
509
- # todo: do we want to configure it?
510
- 50
511
- end
512
-
513
- def compact_array_str(value)
514
- slice = value[0..10]
515
-
516
- compact = exec_with_allocation_control(slice, :inspect, OverflowMessageType::NIL_MESSAGE)
517
-
518
- if compact && value.size != slice.size
519
- compact[0..compact.size - 2] + ", ...]"
520
- end
521
- compact
522
- end
523
-
524
- def compact_hash_str(value)
525
- keys_strings = Hash.new
526
-
527
- slice = value.sort_by do |k, _|
528
- keys_string = exec_with_allocation_control(k, :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE)
529
- keys_strings[k] = keys_string
530
- keys_string
531
- end[0..5]
532
-
533
- compact = slice.map do |kv|
534
- key_string = keys_strings[kv[0]]
535
- value_string = exec_with_allocation_control(kv[1], :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE)
536
- "#{key_string}: #{handle_binary_data(value_string)}"
537
- end.join(", ")
538
- "{" + compact + (slice.size != value.size ? ", ..." : "") + "}"
539
- end
540
-
541
- def build_compact_value_attr(value, value_str)
542
- compact_value_str = build_compact_name(value, value_str)
543
- compact_value_str.nil? ? '' : "compactValue=\"#{CGI.escapeHTML(compact_value_str)}\""
544
- end
545
-
546
- def safe_to_string(value)
547
- begin
548
- str = value.to_s
549
- rescue NoMethodError
550
- str = "(Object doesn't support #to_s)"
551
- end
552
- return str unless str.nil?
553
-
554
- string_io = StringIO.new
555
- string_io.write(value)
556
- string_io.string
557
- end
558
-
559
- def build_value_attr(escaped_value_str)
560
- Debugger.value_as_nested_element ? '' : "value=\"#{escaped_value_str}\""
561
- end
562
-
563
- instance_methods.each do |m|
564
- if m.to_s.index('print_') == 0
565
- protect m
566
- end
567
- end
568
-
569
- end
570
-
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 do_print_hash_key_value(hash)
148
+ print_element("variables", {:type => 'hashItem'}) do
149
+ hash.each {|(k, v)|
150
+ print_variable('key', k, 'instance')
151
+ print_variable('value', v, 'instance')
152
+ }
153
+ end
154
+ end
155
+
156
+ def do_print_hash(hash)
157
+ print_element("variables") do
158
+ hash.each {|(k, v)|
159
+ if k.class.name == "String"
160
+ name = '\'' + k + '\''
161
+ else
162
+ name = exec_with_allocation_control(k, :to_s, OverflowMessageType::EXCEPTION_MESSAGE)
163
+ end
164
+
165
+ if k.nil?
166
+ name = 'nil'
167
+ end
168
+
169
+ print_variable(name, v, 'instance')
170
+ }
171
+ end
172
+ end
173
+
174
+ def print_hash(hash)
175
+ if Debugger.key_value_mode
176
+ do_print_hash_key_value(hash)
177
+ else
178
+ do_print_hash(hash)
179
+ end
180
+ end
181
+
182
+ def print_string(string)
183
+ print_element("variables") do
184
+ if string.respond_to?('bytes')
185
+ bytes = string.bytes.to_a
186
+ InspectCommand.reference_result(bytes)
187
+ print_variable('bytes', bytes, 'instance')
188
+ end
189
+ print_variable('encoding', string.encoding, 'instance') if string.respond_to?('encoding')
190
+ end
191
+ end
192
+
193
+ def exec_with_timeout(sec, error_message)
194
+ return yield if sec == nil or sec.zero?
195
+ if Thread.respond_to?(:critical) and Thread.critical
196
+ raise ThreadError, "timeout within critical session"
197
+ end
198
+ begin
199
+ x = Thread.current
200
+ y = DebugThread.start {
201
+ sleep sec
202
+ x.raise SimpleTimeLimitError.new(error_message) if x.alive?
203
+ }
204
+ yield sec
205
+ ensure
206
+ y.kill if y and y.alive?
207
+ end
208
+ end
209
+
210
+ def exec_with_allocation_control(value, exec_method, overflow_message_type)
211
+ return value.__send__ exec_method unless Debugger.trace_to_s
212
+
213
+ memory_limit = Debugger.debugger_memory_limit
214
+ time_limit = Debugger.inspect_time_limit
215
+
216
+ if defined?(JRUBY_VERSION) || RUBY_VERSION < '2.0' || memory_limit <= 0
217
+ return exec_with_timeout(time_limit * 1e-3, "Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.") { value.__send__ exec_method }
218
+ end
219
+
220
+ require 'objspace'
221
+ trace_queue = Queue.new
222
+
223
+ inspect_thread = DebugThread.start do
224
+ start_alloc_size = ObjectSpace.memsize_of_all
225
+ start_time = Time.now.to_f
226
+
227
+ trace_point = TracePoint.new(:c_call, :call) do |tp|
228
+ curr_time = Time.now.to_f
229
+
230
+ if (curr_time - start_time) * 1e3 > time_limit
231
+ trace_queue << TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", caller.to_a)
232
+ trace_point.disable
233
+ inspect_thread.kill
234
+ end
235
+
236
+ next unless rand > 0.75
237
+
238
+ curr_alloc_size = ObjectSpace.memsize_of_all
239
+ start_alloc_size = curr_alloc_size if curr_alloc_size < start_alloc_size
240
+
241
+ if curr_alloc_size - start_alloc_size > 1e6 * memory_limit
242
+ trace_queue << MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", caller.to_a)
243
+ trace_point.disable
244
+ inspect_thread.kill
245
+ end
246
+ end
247
+ trace_point.enable
248
+ result = value.__send__ exec_method
249
+ trace_queue << result
250
+ trace_point.disable
251
+ end
252
+
253
+ while(mes = trace_queue.pop)
254
+ if(mes.is_a? TimeLimitError or mes.is_a? MemoryLimitError)
255
+ print_debug(mes.message + "\n" + mes.backtrace.map {|l| "\t#{l}"}.join("\n"))
256
+ return overflow_message_type.call(mes)
257
+ else
258
+ return mes
259
+ end
260
+ end
261
+ rescue SimpleTimeLimitError => e
262
+ print_debug(e.message)
263
+ return overflow_message_type.call(e)
264
+ end
265
+
266
+ def print_variable(name, value, kind)
267
+ name = name.to_s
268
+
269
+ if value.nil?
270
+ print("<variable name=\"%s\" kind=\"%s\"/>", CGI.escapeHTML(name), kind)
271
+ return
272
+ end
273
+ if value.is_a?(Array) || value.is_a?(Hash)
274
+ has_children = !value.empty?
275
+ if has_children
276
+ size = value.size
277
+ value_str = "#{value.class} (#{value.size} element#{size > 1 ? "s" : "" })"
278
+ else
279
+ value_str = "Empty #{value.class}"
280
+ end
281
+ elsif value.is_a?(String)
282
+ has_children = value.respond_to?('bytes') || value.respond_to?('encoding')
283
+ value_str = value
284
+ else
285
+ has_children = !value.instance_variables.empty? || !value.class.class_variables.empty?
286
+
287
+ value_str = exec_with_allocation_control(value, :to_s, OverflowMessageType::EXCEPTION_MESSAGE) || 'nil' rescue "<#to_s method raised exception: #{$!}>"
288
+ unless value_str.is_a?(String)
289
+ value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String."
290
+ end
291
+ end
292
+
293
+ if value_str.respond_to?('encode')
294
+ # noinspection RubyEmptyRescueBlockInspection
295
+ begin
296
+ value_str = value_str.encode("UTF-8")
297
+ rescue
298
+ end
299
+ end
300
+ value_str = handle_binary_data(value_str)
301
+ escaped_value_str = CGI.escapeHTML(value_str)
302
+ print("<variable name=\"%s\" %s kind=\"%s\" %s type=\"%s\" hasChildren=\"%s\" objectId=\"%#+x\">",
303
+ CGI.escapeHTML(name), build_compact_value_attr(value, value_str), kind,
304
+ build_value_attr(escaped_value_str), value.class,
305
+ has_children, value.object_id)
306
+
307
+ print("<value><![CDATA[%s]]></value>", escaped_value_str) if Debugger.value_as_nested_element
308
+ print('</variable>')
309
+ rescue StandardError => e
310
+ print_debug "Unexpected exception \"%s\"\n%s", e.to_s, e.backtrace.join("\n")
311
+ print("<variable name=\"%s\" kind=\"%s\" value=\"%s\"/>",
312
+ CGI.escapeHTML(name), kind, CGI.escapeHTML(safe_to_string(value)))
313
+ end
314
+
315
+ def print_file_included(file)
316
+ print("<fileIncluded file=\"%s\"/>", file)
317
+ end
318
+
319
+ def print_file_excluded(file)
320
+ print("<fileExcluded file=\"%s\"/>", file)
321
+ end
322
+
323
+ def print_file_filter_status(status)
324
+ print("<fileFilter status=\"%s\"/>", status)
325
+ end
326
+
327
+ def print_breakpoints(breakpoints)
328
+ print_element 'breakpoints' do
329
+ breakpoints.sort_by {|b| b.id}.each do |b|
330
+ print "<breakpoint n=\"%d\" file=\"%s\" line=\"%s\" />", b.id, CGI.escapeHTML(b.source), b.pos.to_s
331
+ end
332
+ end
333
+ end
334
+
335
+ def print_breakpoint_added(b)
336
+ print "<breakpointAdded no=\"%s\" location=\"%s:%s\"/>", b.id, CGI.escapeHTML(b.source), b.pos
337
+ end
338
+
339
+ def print_breakpoint_deleted(b)
340
+ print "<breakpointDeleted no=\"%s\"/>", b.id
341
+ end
342
+
343
+ def print_breakpoint_enabled(b)
344
+ print "<breakpointEnabled bp_id=\"%s\"/>", b.id
345
+ end
346
+
347
+ def print_breakpoint_disabled(b)
348
+ print "<breakpointDisabled bp_id=\"%s\"/>", b.id
349
+ end
350
+
351
+ def print_contdition_set(bp_id)
352
+ print "<conditionSet bp_id=\"%d\"/>", bp_id
353
+ end
354
+
355
+ def print_catchpoint_set(exception_class_name)
356
+ print "<catchpointSet exception=\"%s\"/>", exception_class_name
357
+ end
358
+
359
+ def print_catchpoint_deleted(exception_class_name)
360
+ if Debugger.catchpoint_deleted_event
361
+ print "<catchpointDeleted exception=\"%s\"/>", exception_class_name
362
+ else
363
+ print_catchpoint_set(exception_class_name)
364
+ end
365
+ end
366
+
367
+ def print_expressions(exps)
368
+ print_element "expressions" do
369
+ exps.each_with_index do |(exp, value), idx|
370
+ print_expression(exp, value, idx + 1)
371
+ end
372
+ end unless exps.empty?
373
+ end
374
+
375
+ def print_expression(exp, value, idx)
376
+ print "<dispay name=\"%s\" value=\"%s\" no=\"%d\" />", exp, value, idx
377
+ end
378
+
379
+ def print_expression_info(incomplete, prompt, indent)
380
+ print "<expressionInfo incomplete=\"%s\" prompt=\"%s\" indent=\"%s\"></expressionInfo>",
381
+ incomplete, CGI.escapeHTML(prompt), indent
382
+ end
383
+
384
+ def print_eval(exp, value)
385
+ print "<eval expression=\"%s\" value=\"%s\" />", CGI.escapeHTML(exp), value
386
+ end
387
+
388
+ def print_pp(value)
389
+ print value
390
+ end
391
+
392
+ def print_list(b, e, file, line)
393
+ print "[%d, %d] in %s\n", b, e, file
394
+ if (lines = Debugger.source_for(file))
395
+ b.upto(e) do |n|
396
+ if n > 0 && lines[n - 1]
397
+ if n == line
398
+ print "=> %d %s\n", n, lines[n - 1].chomp
399
+ else
400
+ print " %d %s\n", n, lines[n - 1].chomp
401
+ end
402
+ end
403
+ end
404
+ else
405
+ print "No source-file available for %s\n", file
406
+ end
407
+ end
408
+
409
+ def print_methods(methods)
410
+ print_element "methods" do
411
+ methods.each do |method|
412
+ print "<method name=\"%s\" />", method
413
+ end
414
+ end
415
+ end
416
+
417
+ # Events
418
+
419
+ def print_breakpoint(_, breakpoint)
420
+ print("<breakpoint file=\"%s\" line=\"%s\" threadId=\"%d\"/>",
421
+ CGI.escapeHTML(breakpoint.source), breakpoint.pos, Debugger.current_context.thnum)
422
+ end
423
+
424
+ def print_catchpoint(exception)
425
+ context = Debugger.current_context
426
+ print("<exception file=\"%s\" line=\"%s\" type=\"%s\" message=\"%s\" threadId=\"%d\"/>",
427
+ CGI.escapeHTML(context.frame_file(0)), context.frame_line(0), exception.class, CGI.escapeHTML(exception.to_s), context.thnum)
428
+ end
429
+
430
+ def print_trace(context, file, line)
431
+ Debugger::print_debug "trace: location=\"%s:%s\", threadId=%d", file, line, context.thnum
432
+ # TBD: do we want to clog fronend with the <trace> elements? There are tons of them.
433
+ # print "<trace file=\"%s\" line=\"%s\" threadId=\"%d\" />", file, line, context.thnum
434
+ end
435
+
436
+ def print_at_line(context, file, line)
437
+ print "<suspended file=\"%s\" line=\"%s\" threadId=\"%d\" frames=\"%d\"/>",
438
+ CGI.escapeHTML(File.expand_path(file)), line, context.thnum, context.stack_size
439
+ end
440
+
441
+ def print_exception(exception, _)
442
+ print_element("variables") do
443
+ proxy = ExceptionProxy.new(exception)
444
+ InspectCommand.reference_result(proxy)
445
+ print_variable('error', proxy, 'exception')
446
+ end
447
+ rescue Exception
448
+ print "<processingException type=\"%s\" message=\"%s\"/>",
449
+ exception.class, CGI.escapeHTML(exception.to_s)
450
+ end
451
+
452
+ def print_inspect(eval_result)
453
+ print_element("variables") do
454
+ print_variable("eval_result", eval_result, 'local')
455
+ end
456
+ end
457
+
458
+ def print_load_result(file, exception = nil)
459
+ if exception
460
+ print("<loadResult file=\"%s\" exceptionType=\"%s\" exceptionMessage=\"%s\"/>", file, exception.class, CGI.escapeHTML(exception.to_s))
461
+ else
462
+ print("<loadResult file=\"%s\" status=\"OK\"/>", file)
463
+ end
464
+ end
465
+
466
+ def print_element(name, additional_tags = nil)
467
+ additional_tags_presentation = additional_tags.nil? ? '' : additional_tags.map {|tag, value| " #{tag}=\"#{value}\""}.reduce(:+)
468
+
469
+ print("<#{name}#{additional_tags_presentation}>")
470
+ begin
471
+ yield if block_given?
472
+ ensure
473
+ print("</#{name}>")
474
+ end
475
+ end
476
+
477
+ private
478
+
479
+ def print(*params)
480
+ Debugger::print_debug(*params)
481
+ @interface.print(*params)
482
+ end
483
+
484
+ def handle_binary_data(value)
485
+ return '[Binary Data]' if (value.respond_to?('is_binary_data?') && value.is_binary_data?)
486
+ return '[Invalid encoding]' if (value.respond_to?('valid_encoding?') && !value.valid_encoding?)
487
+ value
488
+ end
489
+
490
+ def current_thread_attr(context)
491
+ if context.thread == Thread.current
492
+ 'current="yes"'
493
+ else
494
+ ''
495
+ end
496
+ end
497
+
498
+ def build_compact_name(value, value_str)
499
+ return compact_array_str(value) if value.is_a?(Array)
500
+ return compact_hash_str(value) if value.is_a?(Hash)
501
+ return value_str[0..max_compact_name_size - 3] + '...' if value_str.size > max_compact_name_size
502
+ nil
503
+ rescue ::Exception => e
504
+ print_debug(e)
505
+ nil
506
+ end
507
+
508
+ def max_compact_name_size
509
+ # todo: do we want to configure it?
510
+ 50
511
+ end
512
+
513
+ def compact_array_str(value)
514
+ slice = value[0..10]
515
+
516
+ compact = exec_with_allocation_control(slice, :inspect, OverflowMessageType::NIL_MESSAGE)
517
+
518
+ if compact && value.size != slice.size
519
+ compact[0..compact.size - 2] + ", ...]"
520
+ end
521
+ compact
522
+ end
523
+
524
+ def compact_hash_str(value)
525
+ keys_strings = Hash.new
526
+
527
+ slice = value.sort_by do |k, _|
528
+ keys_string = exec_with_allocation_control(k, :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE)
529
+ keys_strings[k] = keys_string
530
+ keys_string
531
+ end[0..5]
532
+
533
+ compact = slice.map do |kv|
534
+ key_string = keys_strings[kv[0]]
535
+ value_string = exec_with_allocation_control(kv[1], :to_s, OverflowMessageType::SPECIAL_SYMBOL_MESSAGE)
536
+ "#{key_string}: #{handle_binary_data(value_string)}"
537
+ end.join(", ")
538
+ "{" + compact + (slice.size != value.size ? ", ..." : "") + "}"
539
+ end
540
+
541
+ def build_compact_value_attr(value, value_str)
542
+ compact_value_str = build_compact_name(value, value_str)
543
+ compact_value_str.nil? ? '' : "compactValue=\"#{CGI.escapeHTML(compact_value_str)}\""
544
+ end
545
+
546
+ def safe_to_string(value)
547
+ begin
548
+ str = value.to_s
549
+ rescue NoMethodError
550
+ str = "(Object doesn't support #to_s)"
551
+ end
552
+ return str unless str.nil?
553
+
554
+ string_io = StringIO.new
555
+ string_io.write(value)
556
+ string_io.string
557
+ end
558
+
559
+ def build_value_attr(escaped_value_str)
560
+ Debugger.value_as_nested_element ? '' : "value=\"#{escaped_value_str}\""
561
+ end
562
+
563
+ instance_methods.each do |m|
564
+ if m.to_s.index('print_') == 0
565
+ protect m
566
+ end
567
+ end
568
+
569
+ end
570
+
571
571
  end