semi-working implementation of eval-based input validation

This commit is contained in:
Ryan Fitzgerald 2011-11-25 23:24:50 -08:00
parent 7109cebadd
commit 8d9890f876
2 changed files with 51 additions and 34 deletions

View File

@ -534,45 +534,54 @@ class Pry
prompt_stack.size > 1 ? prompt_stack.pop : prompt
end
if RUBY_VERSION =~ /1.9/ && RUBY_ENGINE == "ruby"
require 'ripper'
# Determine if a string of code is a valid Ruby expression.
# Ruby 1.9 uses Ripper, Ruby 1.8 uses RubyParser.
# @param [String] code The code to validate.
# @return [Boolean] Whether or not the code is a valid Ruby expression.
# @example
# valid_expression?("class Hello") #=> false
# valid_expression?("class Hello; end") #=> true
def valid_expression?(code)
!!Ripper::SexpBuilder.new(code).parse
# Determine if a string of code is a valid Ruby expression.
# @param [String] code The code to validate.
# @return [Boolean] Whether or not the code is a valid Ruby expression.
# @example
# valid_expression?("class Hello") #=> false
# valid_expression?("class Hello; end") #=> true
def valid_expression?(str)
if defined?(Rubinius::Melbourne19) && RUBY_VERSION =~ /^1\.9/
Rubinius::Melbourne19.parse_string(str, Pry.eval_path)
elsif defined?(Rubinius::Melbourne)
Rubinius::Melbourne.parse_string(str, Pry.eval_path)
else
catch(:valid) {
eval("BEGIN{throw :valid, true}\n#{str}", binding, Pry.eval_path)
}
end
elsif RUBY_VERSION =~ /1.9/ && RUBY_ENGINE == 'jruby'
# JRuby doesn't have Ripper, so use its native parser for 1.9 mode.
def valid_expression?(code)
JRuby.parse(code)
true
rescue SyntaxError
rescue SyntaxError => e
if incomplete_user_input_exception?(e, caller)
false
else
raise e
end
end
else
require 'ruby_parser'
# Check whether the exception indicates that the user should input more.
#
# The first part of the check verifies that the exception was raised from
# the input to the eval, taking care not to be confused if the input contains
# an eval() with incomplete syntax.
#
# @param [SyntaxError] the exception object that was raised.
# @param [Array<String>] The stack frame of the function that executed eval.
# @return [Boolean]
#
def incomplete_user_input_exception?(ex, stack)
# deliberate use of ^ instead of \A, error messages can be two lines long.
from_pry_input = /^#{Regexp.escape(Pry.eval_path)}/
# Determine if a string of code is a valid Ruby expression.
# Ruby 1.9 uses Ripper, Ruby 1.8 uses RubyParser.
# @param [String] code The code to validate.
# @return [Boolean] Whether or not the code is a valid Ruby expression.
# @example
# valid_expression?("class Hello") #=> false
# valid_expression?("class Hello; end") #=> true
def valid_expression?(code)
# NOTE: we're using .dup because RubyParser mutates the input
RubyParser.new.parse(code.dup)
true
rescue Racc::ParseError, SyntaxError
return false unless SyntaxError === ex && ex.message =~ from_pry_input
case ex.message
when /unexpected (\$end|end-of-file|END_OF_FILE)/, # mri, jruby, ironruby
/unterminated (quoted string|string|regexp) meets end of file/, # "quoted string" is ironruby
/missing 'end' for/, /: expecting '[})\]]'$/, /can't find string ".*" anywhere before EOF/, /expecting keyword_end/ # rbx
backtrace = ex.backtrace
backtrace = backtrace.drop(1) if RUBY_VERSION =~ /^1\.8/ && RUBY_PLATFORM !~ /java/
backtrace.grep(from_pry_input).count <= stack.grep(from_pry_input).count
else
false
end
end

View File

@ -180,3 +180,11 @@ CommandTester = Pry::CommandSet.new do
output.puts arg
end
end
# to help with tracking down bugs that cause an infinite loop in the test suite
if ENV["SET_TRACE_FUNC"]
require 'set_trace' if defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ # gem install 'rbx-tracer'
set_trace_func proc { |event, file, line, id, binding, classname|
STDERR.printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
}
end