require 'cgi' require 'yaml' require 'monitor' module Debugger class XmlPrinter # :nodoc: class ExceptionProxy instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$|^instance_variables$|^instance_eval$)/ } def initialize(exception) @exception = exception @message = exception.message @backtrace = Debugger.cleanup_backtrace(exception.backtrace) end private def method_missing(called, *args, &block) @exception.__send__(called, *args, &block) end end def self.protect(mname) return if instance_methods.include?("__#{mname}") alias_method "__#{mname}", mname class_eval %{ def #{mname}(*args, &block) @@monitor.synchronize do return unless @interface __#{mname}(*args, &block) end end } end @@monitor = Monitor.new attr_accessor :interface def initialize(interface) @interface = interface end def print_msg(*args) msg, *args = args xml_message = CGI.escapeHTML(msg % args) print "#{xml_message}" end # Sends debug message to the frontend if XML debug logging flag (--xml-debug) is on. def print_debug(*args) Debugger.print_debug(*args) if Debugger.xml_debug msg, *args = args xml_message = CGI.escapeHTML(msg % args) @interface.print("#{xml_message}") end end def print_error(*args) print_element("error") do msg, *args = args print CGI.escapeHTML(msg % args) end end def print_frames(context, current_frame_id) print_element("frames") do (0...context.stack_size).each do |id| print_frame(context, id, current_frame_id) end end end def print_current_frame(frame_pos) print_debug "Selected frame no #{frame_pos}" end def print_frame(context, frame_id, current_frame_id) # idx + 1: one-based numbering as classic-debugger file = context.frame_file(frame_id) print "", frame_id + 1, File.expand_path(file), context.frame_line(frame_id) end def print_contexts(contexts) print_element("threads") do contexts.each do |c| print_context(c) unless c.ignored? end end end def print_context(context) current = 'current="yes"' if context.thread == Thread.current print "", context.thnum, context.thread.status, Process.pid end def print_variables(vars, kind) print_element("variables") do # print self at top position print_variable('self', yield('self'), kind) if vars.include?('self') vars.sort.each do |v| print_variable(v, yield(v), kind) unless v == 'self' end end end def print_array(array) print_element("variables") do index = 0 array.each { |e| print_variable('[' + index.to_s + ']', e, 'instance') index += 1 } end end def print_hash(hash) print_element("variables") do hash.keys.each { | k | if k.class.name == "String" name = '\'' + k + '\'' else name = k.to_s end print_variable(name, hash[k], 'instance') } end end def print_string(string) print_element("variables") do if string.respond_to?('bytes') bytes = string.bytes.to_a InspectCommand.reference_result(bytes) print_variable('bytes', bytes, 'instance') end print_variable('encoding', string.encoding, 'instance') if string.respond_to?('encoding') end end def print_variable(name, value, kind) name = name.to_s if value.nil? print("", CGI.escapeHTML(name), kind) return end if value.is_a?(Array) || value.is_a?(Hash) has_children = !value.empty? unless has_children value_str = "Empty #{value.class}" else size = value.size value_str = "#{value.class} (#{value.size} element#{size > 1 ? "s" : "" })" end elsif value.is_a?(String) has_children = value.respond_to?('bytes') || value.respond_to?('encoding') value_str = value else has_children = !value.instance_variables.empty? || !value.class.class_variables.empty? value_str = value.to_s || 'nil' rescue "<#to_s method raised exception: #$!>" unless value_str.is_a?(String) value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String." end end if value_str.respond_to?('encode') begin value_str = value_str.encode("UTF-8") rescue end end value_str = "[Binary Data]" if (value_str.respond_to?('is_binary_data?') && value_str.is_binary_data?) compact_value_str = build_compact_name(value_str, value) print("", CGI.escapeHTML(name), CGI.escapeHTML(compact_value_str), kind, CGI.escapeHTML(value_str), value.class, has_children, value.respond_to?(:object_id) ? value.object_id : value.id) print("", CGI.escapeHTML(value_str)) print('') end def build_compact_name(value_str, value) compact = value_str if value.is_a?(Array) slice = value[0..10] compact = slice.inspect if (value.size != slice.size) compact = compact[0..compact.size-2] + ", ...]" end end if value.is_a?(Hash) slice = value.sort_by { |k,v| k.to_s }[0..5] compact = slice.map {|kv| "#{kv[0]}: #{kv[1]}"}.join(", ") compact = "{" + compact + (slice.size != value.size ? ", ..." : "") + "}" end compact end def print_breakpoints(breakpoints) print_element 'breakpoints' do breakpoints.sort_by{|b| b.id }.each do |b| print "", b.id, b.source, b.pos.to_s end end end def print_breakpoint_added(b) print "", b.id, b.source, b.pos end def print_breakpoint_deleted(b) print "", b.id end def print_breakpoint_enabled(b) print "", b.id end def print_breakpoint_disabled(b) print "", b.id end def print_contdition_set(bp_id) print "", bp_id end def print_catchpoint_set(exception_class_name) print "", exception_class_name end def print_expressions(exps) print_element "expressions" do exps.each_with_index do |(exp, value), idx| print_expression(exp, value, idx+1) end end unless exps.empty? end def print_expression(exp, value, idx) print "", exp, value, idx end def print_expression_info(incomplete, prompt, indent) print "", incomplete, CGI.escapeHTML(prompt), indent end def print_eval(exp, value) print "", CGI.escapeHTML(exp), value end def print_pp(value) print value end def print_list(b, e, file, line) print "[%d, %d] in %s\n", b, e, file if lines = Debugger.source_for(file) b.upto(e) do |n| if n > 0 && lines[n-1] if n == line print "=> %d %s\n", n, lines[n-1].chomp else print " %d %s\n", n, lines[n-1].chomp end end end else print "No sourcefile available for %s\n", file end end def print_methods(methods) print_element "methods" do methods.each do |method| print "", method end end end # Events def print_breakpoint(n, breakpoint) print("", breakpoint.source, breakpoint.pos, Debugger.current_context.thnum) end def print_catchpoint(exception) context = Debugger.current_context print("", context.frame_file(0), context.frame_line(0), exception.class, CGI.escapeHTML(exception.to_s), context.thnum) end def print_trace(context, file, line) Debugger::print_debug "trace: location=\"%s:%s\", threadId=%d", file, line, context.thnum # TBD: do we want to clog fronend with the elements? There are tons of them. # print "", file, line, context.thnum end def print_at_line(context, file, line) print "", File.expand_path(file), line, context.thnum, context.stack_size end def print_exception(exception, binding) print_element("variables") do proxy = ExceptionProxy.new(exception) InspectCommand.reference_result(proxy) print_variable('error', proxy, 'exception') end rescue Exception print "", exception.class, CGI.escapeHTML(exception.to_s) end def print_inspect(eval_result) print_element("variables") do print_variable("eval_result", eval_result, 'local') end end def print_load_result(file, exception=nil) if exception print("", file, exception.class, CGI.escapeHTML(exception.to_s)) else print("", file) end end def print_element(name) print("<#{name}>") begin yield ensure print("") end end private def print(*params) Debugger::print_debug(*params) @interface.print(*params) end instance_methods.each do |m| if m.to_s.index('print_') == 0 protect m end end end end