diff --git a/bin/rdebug-ide b/bin/rdebug-ide index 69b2c60..34d563d 100755 --- a/bin/rdebug-ide +++ b/bin/rdebug-ide @@ -36,6 +36,17 @@ Usage: rdebug-ide is supposed to be called from RDT, NetBeans, RubyMine, or EOB opts.separator "" opts.separator "Options:" + + ENV['DEBUGGER_MEMORY_LIMIT'] = '10' + opts.on("-m", "--memory-limit LIMIT", Integer, "evaluation memory limit in mb (default: 10)") do |limit| + ENV['DEBUGGER_MEMORY_LIMIT'] = limit + end + + ENV['INSPECT_TIME_LIMIT'] = '100' + opts.on("-t", "--time-limit LIMIT", Integer, "evaluation time limit in milliseconds (default: 100)") do |limit| + ENV['INSPECT_TIME_LIMIT'] = limit + end + opts.on("-h", "--host HOST", "Host name used for remote debugging") {|host| options.host = host} opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port} opts.on("--dispatcher-port PORT", Integer, "Port used for multi-process debugging dispatcher") do |dp| diff --git a/lib/ruby-debug-ide/xml_printer.rb b/lib/ruby-debug-ide/xml_printer.rb index 00f08b5..0b0e607 100644 --- a/lib/ruby-debug-ide/xml_printer.rb +++ b/lib/ruby-debug-ide/xml_printer.rb @@ -1,9 +1,30 @@ require 'stringio' require 'cgi' require 'monitor' +require 'objspace' module Debugger + class MemoryLimitError < StandardError + attr_reader :message + attr_reader :backtrace + + def initialize(message, backtrace = '') + @message = message + @backtrace = backtrace + end + end + + class TimeLimitError < StandardError + attr_reader :message + attr_reader :backtrace + + def initialize(message, backtrace = '') + @message = message + @backtrace = backtrace + end + end + class XmlPrinter # :nodoc: class ExceptionProxy instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$|^instance_variables$|^instance_eval$)/ } @@ -137,7 +158,45 @@ def print_string(string) print_variable('encoding', string.encoding, 'instance') if string.respond_to?('encoding') end end - + + def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, return_message_if_overflow) + curr_thread = Thread.current + result = nil + inspect_thread = DebugThread.start { + start_alloc_size = ObjectSpace.memsize_of_all + start_time = Time.now.to_f + + trace = TracePoint.new(:c_call, :call) do |tp| + + if(rand > 0.75) + curr_alloc_size = ObjectSpace.memsize_of_all + curr_time = Time.now.to_f + + if((curr_time - start_time) * 1e3 > time_limit) + curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", "#{caller.map{|l| "\t#{l}"}.join("\n")}") + inspect_thread.kill + end + + start_alloc_size = curr_alloc_size if (curr_alloc_size < start_alloc_size) + + if(curr_alloc_size - start_alloc_size > 1e6 * memory_limit) + curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", "#{caller.map{|l| "\t#{l}"}.join("\n")}") + inspect_thread.kill + end + end + end.enable { + result = value.send exec_method + } + } + inspect_thread.join + inspect_thread.kill + return result + rescue MemoryLimitError, TimeLimitError => e + print_debug(e.message + "\n" + e.backtrace) + + return return_message_if_overflow ? e.message : nil + end + def print_variable(name, value, kind) name = name.to_s if value.nil? @@ -157,7 +216,13 @@ def print_variable(name, value, kind) 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: #{$!}>" + + value_str = if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) + value.to_s || 'nil' rescue "<#to_s method raised exception: #{$!}>" + else + exec_with_allocation_control(value, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :to_s, false) || 'nil' rescue "<#to_s method raised exception: #{$!}>" + end + unless value_str.is_a?(String) value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String." end @@ -382,11 +447,17 @@ def max_compact_name_size def compact_array_str(value) slice = value[0..10] - compact = slice.inspect - if value.size != slice.size + + compact = if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0) + slice.inspect + else + exec_with_allocation_control(slice, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :inspect, true) + end + + if compact && value.size != slice.size compact[0..compact.size-2] + ", ...]" end - compact + compact end def compact_hash_str(value)