1
0
Fork 0
mirror of https://github.com/pry/pry.git synced 2022-11-09 12:35:05 -05:00

Be more careful exception handling in Readline [Fixes #632, #605]

Firstly wrap the entire completion_proc in a begin/rescue/end so that
even if something we're not expecting goes wrong, we don't fail.

Secondly, only throw the user out of pry if reading from readline fails
five times in a row. This avoids a transient error (like a time-out, or
a failed tab completion) from kicking you out of pry; but also avoids
the infinite looping problem that can happen if the error is not transient.
This commit is contained in:
Conrad Irwin 2012-07-04 01:30:27 -07:00
parent 94fbfabfc6
commit b5cb8b142f
2 changed files with 115 additions and 125 deletions

View file

@ -43,160 +43,144 @@ class Pry
# @param [Array<String>] commands The array of Pry commands. # @param [Array<String>] commands The array of Pry commands.
def self.build_completion_proc(target, commands=[""]) def self.build_completion_proc(target, commands=[""])
proc do |input| proc do |input|
bind = target begin
bind = target
case input case input
when /^(\/[^\/]*\/)\.([^.]*)$/ when /^(\/[^\/]*\/)\.([^.]*)$/
# Regexp # Regexp
receiver = $1 receiver = $1
message = Regexp.quote($2) message = Regexp.quote($2)
candidates = Regexp.instance_methods.collect{|m| m.to_s} candidates = Regexp.instance_methods.collect{|m| m.to_s}
select_message(receiver, message, candidates) select_message(receiver, message, candidates)
when /^([^\]]*\])\.([^.]*)$/ when /^([^\]]*\])\.([^.]*)$/
# Array # Array
receiver = $1 receiver = $1
message = Regexp.quote($2) message = Regexp.quote($2)
candidates = Array.instance_methods.collect{|m| m.to_s} candidates = Array.instance_methods.collect{|m| m.to_s}
select_message(receiver, message, candidates) select_message(receiver, message, candidates)
when /^([^\}]*\})\.([^.]*)$/ when /^([^\}]*\})\.([^.]*)$/
# Proc or Hash # Proc or Hash
receiver = $1 receiver = $1
message = Regexp.quote($2) message = Regexp.quote($2)
candidates = Proc.instance_methods.collect{|m| m.to_s} candidates = Proc.instance_methods.collect{|m| m.to_s}
candidates |= Hash.instance_methods.collect{|m| m.to_s} candidates |= Hash.instance_methods.collect{|m| m.to_s}
select_message(receiver, message, candidates) select_message(receiver, message, candidates)
when /^(:[^:.]*)$/ when /^(:[^:.]*)$/
# Symbol # Symbol
if Symbol.respond_to?(:all_symbols) if Symbol.respond_to?(:all_symbols)
sym = Regexp.quote($1) sym = Regexp.quote($1)
candidates = Symbol.all_symbols.collect{|s| ":" + s.id2name} candidates = Symbol.all_symbols.collect{|s| ":" + s.id2name}
candidates.grep(/^#{sym}/) candidates.grep(/^#{sym}/)
else else
[] []
end end
when /^::([A-Z][^:\.\(]*)$/ when /^::([A-Z][^:\.\(]*)$/
# Absolute Constant or class methods # Absolute Constant or class methods
receiver = $1 receiver = $1
candidates = Object.constants.collect{|m| m.to_s} candidates = Object.constants.collect{|m| m.to_s}
candidates.grep(/^#{receiver}/).collect{|e| "::" + e} candidates.grep(/^#{receiver}/).collect{|e| "::" + e}
when /^([A-Z].*)::([^:.]*)$/ when /^([A-Z].*)::([^:.]*)$/
# Constant or class methods # Constant or class methods
receiver = $1 receiver = $1
message = Regexp.quote($2) message = Regexp.quote($2)
begin
candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind) candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind)
candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind) candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind)
rescue RescuableException candidates.grep(/^#{message}/).collect{|e| receiver + "::" + e}
candidates = []
end
candidates.grep(/^#{message}/).collect{|e| receiver + "::" + e}
when /^(:[^:.]+)\.([^.]*)$/ when /^(:[^:.]+)\.([^.]*)$/
# Symbol # Symbol
receiver = $1 receiver = $1
message = Regexp.quote($2) message = Regexp.quote($2)
candidates = Symbol.instance_methods.collect{|m| m.to_s} candidates = Symbol.instance_methods.collect{|m| m.to_s}
select_message(receiver, message, candidates) select_message(receiver, message, candidates)
when /^(-?(0[dbo])?[0-9_]+(\.[0-9_]+)?([eE]-?[0-9]+)?)\.([^.]*)$/ when /^(-?(0[dbo])?[0-9_]+(\.[0-9_]+)?([eE]-?[0-9]+)?)\.([^.]*)$/
# Numeric # Numeric
receiver = $1 receiver = $1
message = Regexp.quote($5) message = Regexp.quote($5)
begin
candidates = eval(receiver, bind).methods.collect{|m| m.to_s} candidates = eval(receiver, bind).methods.collect{|m| m.to_s}
rescue RescuableException select_message(receiver, message, candidates)
candidates = []
end
select_message(receiver, message, candidates)
when /^(-?0x[0-9a-fA-F_]+)\.([^.]*)$/ when /^(-?0x[0-9a-fA-F_]+)\.([^.]*)$/
# Numeric(0xFFFF) # Numeric(0xFFFF)
receiver = $1 receiver = $1
message = Regexp.quote($2) message = Regexp.quote($2)
begin
candidates = eval(receiver, bind).methods.collect{|m| m.to_s} candidates = eval(receiver, bind).methods.collect{|m| m.to_s}
rescue RescuableException select_message(receiver, message, candidates)
candidates = []
end
select_message(receiver, message, candidates)
when /^(\$[^.]*)$/ when /^(\$[^.]*)$/
regmessage = Regexp.new(Regexp.quote($1)) regmessage = Regexp.new(Regexp.quote($1))
candidates = global_variables.collect{|m| m.to_s}.grep(regmessage) candidates = global_variables.collect{|m| m.to_s}.grep(regmessage)
when /^([^."].*)\.([^.]*)$/ when /^([^."].*)\.([^.]*)$/
# variable # variable
receiver = $1 receiver = $1
message = Regexp.quote($2) message = Regexp.quote($2)
gv = eval("global_variables", bind).collect{|m| m.to_s} gv = eval("global_variables", bind).collect{|m| m.to_s}
lv = eval("local_variables", bind).collect{|m| m.to_s} lv = eval("local_variables", bind).collect{|m| m.to_s}
cv = eval("self.class.constants", bind).collect{|m| m.to_s} cv = eval("self.class.constants", bind).collect{|m| m.to_s}
if (gv | lv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver if (gv | lv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver
# foo.func and foo is local var. OR # foo.func and foo is local var. OR
# Foo::Bar.func # Foo::Bar.func
begin
candidates = eval("#{receiver}.methods", bind).collect{|m| m.to_s} candidates = eval("#{receiver}.methods", bind).collect{|m| m.to_s}
rescue RescuableException else
# func1.func2
candidates = [] candidates = []
end ObjectSpace.each_object(Module){|m|
else
# func1.func2
candidates = []
ObjectSpace.each_object(Module){|m|
begin
name = m.name.to_s name = m.name.to_s
rescue RescuableException next if name != "IRB::Context" and
name = "" /^(IRB|SLex|RubyLex|RubyToken)/ =~ name
end
next if name != "IRB::Context" and
/^(IRB|SLex|RubyLex|RubyToken)/ =~ name
# jruby doesn't always provide #instance_methods() on each # jruby doesn't always provide #instance_methods() on each
# object. # object.
if m.respond_to?(:instance_methods) if m.respond_to?(:instance_methods)
candidates.concat m.instance_methods(false).collect{|x| x.to_s} candidates.concat m.instance_methods(false).collect{|x| x.to_s}
end end
} }
candidates.sort! candidates.sort!
candidates.uniq! candidates.uniq!
end
select_message(receiver, message, candidates)
when /^\.([^.]*)$/
# unknown(maybe String)
receiver = ""
message = Regexp.quote($1)
candidates = String.instance_methods(true).collect{|m| m.to_s}
select_message(receiver, message, candidates)
else
candidates = eval(
"methods | private_methods | local_variables | " \
"self.class.constants | instance_variables",
bind
).collect{|m| m.to_s}
if eval("respond_to?(:class_variables)", bind)
candidates += eval("class_variables", bind).collect { |m| m.to_s }
end
(candidates|ReservedWords|commands).grep(/^#{Regexp.quote(input)}/)
end end
select_message(receiver, message, candidates) rescue RescuableException
[]
when /^\.([^.]*)$/
# unknown(maybe String)
receiver = ""
message = Regexp.quote($1)
candidates = String.instance_methods(true).collect{|m| m.to_s}
select_message(receiver, message, candidates)
else
candidates = eval(
"methods | private_methods | local_variables | " \
"self.class.constants | instance_variables",
bind
).collect{|m| m.to_s}
if eval("respond_to?(:class_variables)", bind)
candidates += eval("class_variables", bind).collect { |m| m.to_s }
end
(candidates|ReservedWords|commands).grep(/^#{Regexp.quote(input)}/)
end end
end end
end end

View file

@ -539,6 +539,7 @@ class Pry
# Manage switching of input objects on encountering EOFErrors # Manage switching of input objects on encountering EOFErrors
def handle_read_errors def handle_read_errors
should_retry = true should_retry = true
exception_count = 0
begin begin
yield yield
rescue EOFError rescue EOFError
@ -565,6 +566,11 @@ class Pry
# anything about it. # anything about it.
rescue RescuableException => e rescue RescuableException => e
puts "Error: #{e.message}" puts "Error: #{e.message}"
output.puts e.backtrace
exception_count += 1
if exception_count < 5
retry
end
puts "FATAL: Pry failed to get user input using `#{input}`." puts "FATAL: Pry failed to get user input using `#{input}`."
puts "To fix this you may be able to pass input and output file descriptors to pry directly. e.g." puts "To fix this you may be able to pass input and output file descriptors to pry directly. e.g."
puts " Pry.config.input = STDIN" puts " Pry.config.input = STDIN"