Skip to content

Commit 03a6101

Browse files
viuginick1valich
authored andcommitted
inspect memory limit fix (#100)
Add time and memory limit checks for `inspect` and `to_s` Some automatic UI-related queries to the debugger may cause OOM or hangs in the case of recursive or just very large data structures being inspected. This commit adds the possibility (enabled by default) to add time and memory limits for dangerous calls to objects from a user codebase. Related: RUBY-18275, RUBY-18121, RUBY-19520. Commits reviewed and squashed: * inspect memory limit * cleanup * Option to disable memory check is added * disable memory limit in the same option * cleanup: formating fix, rename variable, rewrite on "compact = if (...)" * fixed exeption message * memory limit logging added * debug output deleted * print_msg -> print_debug * object_id deleted from the exeption message * Fixes according to the review * timelimit for inspect evaluation added * The handler is called with a probability of 25% * cleanup * inspect_with_alloc_control -> exec_with_... and heuristic break for variable inspect * fixes according to reiew
1 parent 43b57a0 commit 03a6101

File tree

2 files changed

+87
-5
lines changed

2 files changed

+87
-5
lines changed

bin/rdebug-ide

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ Usage: rdebug-ide is supposed to be called from RDT, NetBeans, RubyMine, or
3636
EOB
3737
opts.separator ""
3838
opts.separator "Options:"
39+
40+
ENV['DEBUGGER_MEMORY_LIMIT'] = '10'
41+
opts.on("-m", "--memory-limit LIMIT", Integer, "evaluation memory limit in mb (default: 10)") do |limit|
42+
ENV['DEBUGGER_MEMORY_LIMIT'] = limit
43+
end
44+
45+
ENV['INSPECT_TIME_LIMIT'] = '100'
46+
opts.on("-t", "--time-limit LIMIT", Integer, "evaluation time limit in milliseconds (default: 100)") do |limit|
47+
ENV['INSPECT_TIME_LIMIT'] = limit
48+
end
49+
3950
opts.on("-h", "--host HOST", "Host name used for remote debugging") {|host| options.host = host}
4051
opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port}
4152
opts.on("--dispatcher-port PORT", Integer, "Port used for multi-process debugging dispatcher") do |dp|

lib/ruby-debug-ide/xml_printer.rb

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,30 @@
11
require 'stringio'
22
require 'cgi'
33
require 'monitor'
4+
require 'objspace'
45

56
module Debugger
67

8+
class MemoryLimitError < StandardError
9+
attr_reader :message
10+
attr_reader :backtrace
11+
12+
def initialize(message, backtrace = '')
13+
@message = message
14+
@backtrace = backtrace
15+
end
16+
end
17+
18+
class TimeLimitError < StandardError
19+
attr_reader :message
20+
attr_reader :backtrace
21+
22+
def initialize(message, backtrace = '')
23+
@message = message
24+
@backtrace = backtrace
25+
end
26+
end
27+
728
class XmlPrinter # :nodoc:
829
class ExceptionProxy
930
instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$|^instance_variables$|^instance_eval$)/ }
@@ -137,7 +158,45 @@ def print_string(string)
137158
print_variable('encoding', string.encoding, 'instance') if string.respond_to?('encoding')
138159
end
139160
end
140-
161+
162+
def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, return_message_if_overflow)
163+
curr_thread = Thread.current
164+
result = nil
165+
inspect_thread = DebugThread.start {
166+
start_alloc_size = ObjectSpace.memsize_of_all
167+
start_time = Time.now.to_f
168+
169+
trace = TracePoint.new(:c_call, :call) do |tp|
170+
171+
if(rand > 0.75)
172+
curr_alloc_size = ObjectSpace.memsize_of_all
173+
curr_time = Time.now.to_f
174+
175+
if((curr_time - start_time) * 1e3 > time_limit)
176+
curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", "#{caller.map{|l| "\t#{l}"}.join("\n")}")
177+
inspect_thread.kill
178+
end
179+
180+
start_alloc_size = curr_alloc_size if (curr_alloc_size < start_alloc_size)
181+
182+
if(curr_alloc_size - start_alloc_size > 1e6 * memory_limit)
183+
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")}")
184+
inspect_thread.kill
185+
end
186+
end
187+
end.enable {
188+
result = value.send exec_method
189+
}
190+
}
191+
inspect_thread.join
192+
inspect_thread.kill
193+
return result
194+
rescue MemoryLimitError, TimeLimitError => e
195+
print_debug(e.message + "\n" + e.backtrace)
196+
197+
return return_message_if_overflow ? e.message : nil
198+
end
199+
141200
def print_variable(name, value, kind)
142201
name = name.to_s
143202
if value.nil?
@@ -157,7 +216,13 @@ def print_variable(name, value, kind)
157216
value_str = value
158217
else
159218
has_children = !value.instance_variables.empty? || !value.class.class_variables.empty?
160-
value_str = value.to_s || 'nil' rescue "<#to_s method raised exception: #{$!}>"
219+
220+
value_str = if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0)
221+
value.to_s || 'nil' rescue "<#to_s method raised exception: #{$!}>"
222+
else
223+
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: #{$!}>"
224+
end
225+
161226
unless value_str.is_a?(String)
162227
value_str = "ERROR: #{value.class}.to_s method returns #{value_str.class}. Should return String."
163228
end
@@ -382,11 +447,17 @@ def max_compact_name_size
382447

383448
def compact_array_str(value)
384449
slice = value[0..10]
385-
compact = slice.inspect
386-
if value.size != slice.size
450+
451+
compact = if (defined?(JRUBY_VERSION) || ENV['DEBUGGER_MEMORY_LIMIT'].to_i <= 0)
452+
slice.inspect
453+
else
454+
exec_with_allocation_control(slice, ENV['DEBUGGER_MEMORY_LIMIT'].to_i, ENV['INSPECT_TIME_LIMIT'].to_i, :inspect, true)
455+
end
456+
457+
if compact && value.size != slice.size
387458
compact[0..compact.size-2] + ", ...]"
388459
end
389-
compact
460+
compact
390461
end
391462

392463
def compact_hash_str(value)

0 commit comments

Comments
 (0)