diff --git a/lib/brakeman/checks/base_check.rb b/lib/brakeman/checks/base_check.rb index 11c5cd3f6253d5f75bc57c7924432616754dd1dc..6b0120b3ad7e669f5b285a979b59f68e1e791f21 100644 --- a/lib/brakeman/checks/base_check.rb +++ b/lib/brakeman/checks/base_check.rb @@ -1,11 +1,10 @@ -require 'sexp_processor' require 'brakeman/processors/output_processor' require 'brakeman/processors/lib/processor_helper' require 'brakeman/warning' require 'brakeman/util' #Basis of vulnerability checks. -class Brakeman::BaseCheck < SexpProcessor +class Brakeman::BaseCheck < Brakeman::SexpProcessor include Brakeman::ProcessorHelper include Brakeman::Util attr_reader :tracker, :warnings @@ -24,11 +23,6 @@ class Brakeman::BaseCheck < SexpProcessor @current_set = nil @current_template = @current_module = @current_class = @current_method = nil @mass_assign_disabled = nil - self.strict = false - self.auto_shift_type = false - self.require_empty = false - self.default_method = :process_default - self.warn_on_default = false end #Add result to result list, which is used to check for duplicates diff --git a/lib/brakeman/processors/alias_processor.rb b/lib/brakeman/processors/alias_processor.rb index e1fdef05cca66e318281c3dac89c464bb5bc040d..c9b4291dd9bf085001f8191c30ff4d281766dc98 100644 --- a/lib/brakeman/processors/alias_processor.rb +++ b/lib/brakeman/processors/alias_processor.rb @@ -1,12 +1,11 @@ -require 'rubygems' -require 'sexp_processor' require 'brakeman/util' +require 'ruby_parser/bm_sexp_processor' require 'brakeman/processors/lib/processor_helper' #Returns an s-expression with aliases replaced with their value. #This does not preserve semantics (due to side effects, etc.), but it makes #processing easier when searching for various things. -class Brakeman::AliasProcessor < SexpProcessor +class Brakeman::AliasProcessor < Brakeman::SexpProcessor include Brakeman::ProcessorHelper include Brakeman::Util @@ -19,11 +18,6 @@ class Brakeman::AliasProcessor < SexpProcessor # AliasProcessor.new.process_safely src def initialize tracker = nil super() - self.strict = false - self.auto_shift_type = false - self.require_empty = false - self.default_method = :process_default - self.warn_on_default = false @env = SexpProcessor::Environment.new @inside_if = false @ignore_ifs = false diff --git a/lib/brakeman/processors/base_processor.rb b/lib/brakeman/processors/base_processor.rb index c67c0b43768cc3ba01db29c18302ca90a108288c..53fc26533b03a8ac4577539c7882390fbe332e5d 100644 --- a/lib/brakeman/processors/base_processor.rb +++ b/lib/brakeman/processors/base_processor.rb @@ -1,10 +1,8 @@ -require 'rubygems' -require 'sexp_processor' require 'brakeman/processors/lib/processor_helper' require 'brakeman/util' #Base processor for most processors. -class Brakeman::BaseProcessor < SexpProcessor +class Brakeman::BaseProcessor < Brakeman::SexpProcessor include Brakeman::ProcessorHelper include Brakeman::Util @@ -13,11 +11,6 @@ class Brakeman::BaseProcessor < SexpProcessor #Return a new Processor. def initialize tracker super() - self.strict = false - self.auto_shift_type = false - self.require_empty = false - self.default_method = :process_default - self.warn_on_default = false @last = nil @tracker = tracker @ignore = Sexp.new :ignore @@ -50,7 +43,7 @@ class Brakeman::BaseProcessor < SexpProcessor #Default processing. def process_default exp exp = exp.dup - type = exp.shift + exp.each_with_index do |e, i| if sexp? e and not e.empty? exp[i] = process e @@ -58,8 +51,8 @@ class Brakeman::BaseProcessor < SexpProcessor e end end - ensure - exp.unshift type + + exp end #Process an if statement. diff --git a/lib/brakeman/processors/template_processor.rb b/lib/brakeman/processors/template_processor.rb index bda89ed794294135f63649b6d0b4729f9181b70b..0e95fb7a3c1a34df6eaad51a07e601cea4078702 100644 --- a/lib/brakeman/processors/template_processor.rb +++ b/lib/brakeman/processors/template_processor.rb @@ -20,7 +20,6 @@ class Brakeman::TemplateProcessor < Brakeman::BaseProcessor tracker.templates[template_name] = @current_template @inside_concat = false - self.warn_on_default = false end #Process the template Sexp. diff --git a/lib/brakeman/scanner.rb b/lib/brakeman/scanner.rb index b62d3b0e26234728ef4bf6845e2f53ffa18fbae6..b3910eb1ba055674103cd344e8866ddb03b8fc14 100644 --- a/lib/brakeman/scanner.rb +++ b/lib/brakeman/scanner.rb @@ -8,6 +8,8 @@ begin require 'ruby_parser/bm_sexp.rb' end + require 'ruby_parser/bm_sexp_processor.rb' + require 'haml' require 'sass' require 'erb' diff --git a/lib/brakeman/util.rb b/lib/brakeman/util.rb index 63e96b6b04e45a41cd3b7f2bbd428c9734b84022..ddc93a2b2610be5005a80999c4f25eeb5bf140a3 100644 --- a/lib/brakeman/util.rb +++ b/lib/brakeman/util.rb @@ -1,4 +1,3 @@ -require 'sexp_processor' require 'set' require 'active_support/inflector' diff --git a/lib/ruby_parser/bm_sexp_processor.rb b/lib/ruby_parser/bm_sexp_processor.rb new file mode 100644 index 0000000000000000000000000000000000000000..db1588d2cfadb873c88540415619d9a720e468aa --- /dev/null +++ b/lib/ruby_parser/bm_sexp_processor.rb @@ -0,0 +1,231 @@ +## +# SexpProcessor provides a uniform interface to process Sexps. +# +# In order to create your own SexpProcessor subclass you'll need +# to call super in the initialize method, then set any of the +# Sexp flags you want to be different from the defaults. +# +# SexpProcessor uses a Sexp's type to determine which process method +# to call in the subclass. For Sexp s(:lit, 1) +# SexpProcessor will call #process_lit, if it is defined. +# + +class Brakeman::SexpProcessor + + VERSION = 'CUSTOM' + + ## + # Return a stack of contexts. Most recent node is first. + + attr_reader :context + + ## + # Expected result class + + attr_accessor :expected + + ## + # A scoped environment to make you happy. + + attr_reader :env + + ## + # Creates a new SexpProcessor. Use super to invoke this + # initializer from SexpProcessor subclasses, then use the + # attributes above to customize the functionality of the + # SexpProcessor + + def initialize + @expected = Sexp + + # we do this on an instance basis so we can subclass it for + # different processors. + @processors = {} + @context = [] + + public_methods.each do |name| + if name.to_s.start_with? "process_" then + @processors[name[8..-1].to_sym] = name.to_sym + end + end + end + + ## + # Default Sexp processor. Invokes process_ methods matching + # the Sexp type given. Performs additional checks as specified by + # the initializer. + + def process(exp) + return nil if exp.nil? + + result = nil + + type = exp.first + raise "type should be a Symbol, not: #{exp.first.inspect}" unless + Symbol === type + + in_context type do + # now do a pass with the real processor (or generic) + meth = @processors[type] + if meth then + if $DEBUG + result = error_handler(type) do + self.send(meth, exp) + end + else + result = self.send(meth, exp) + end + + else + result = self.process_default(exp) + end + end + + raise SexpTypeError, "Result must be a #{@expected}, was #{result.class}:#{result.inspect}" unless @expected === result + + result + end + + def error_handler(type, exp=nil) # :nodoc: + begin + return yield + rescue StandardError => err + warn "#{err.class} Exception thrown while processing #{type} for sexp #{exp.inspect} #{caller.inspect}" if $DEBUG + raise + end + end + + ## + # A fairly generic processor for a dummy node. Dummy nodes are used + # when your processor is doing a complicated rewrite that replaces + # the current sexp with multiple sexps. + # + # Bogus Example: + # + # def process_something(exp) + # return s(:dummy, process(exp), s(:extra, 42)) + # end + + def process_dummy(exp) + result = @expected.new(:dummy) rescue @expected.new + + until exp.empty? do + result << self.process(exp.shift) + end + + result + end + + ## + # Add a scope level to the current env. Eg: + # + # def process_defn exp + # name = exp.shift + # args = process(exp.shift) + # scope do + # body = process(exp.shift) + # # ... + # end + # end + # + # env[:x] = 42 + # scope do + # env[:x] # => 42 + # env[:y] = 24 + # end + # env[:y] # => nil + + def scope &block + env.scope(&block) + end + + def in_context type + self.context.unshift type + + yield + + self.context.shift + end + + ## + # I really hate this here, but I hate subdirs in my lib dir more... + # I guess it is kinda like shaving... I'll split this out when it + # itches too much... + + class Environment + def initialize + @env = [] + @env.unshift({}) + end + + def all + @env.reverse.inject { |env, scope| env.merge scope } + end + + def depth + @env.length + end + + # TODO: depth_of + + def [] name + hash = @env.find { |closure| closure.has_key? name } + hash[name] if hash + end + + def []= name, val + hash = @env.find { |closure| closure.has_key? name } || @env.first + hash[name] = val + end + + def scope + @env.unshift({}) + begin + yield + ensure + @env.shift + raise "You went too far unextending env" if @env.empty? + end + end + end +end + +class Object + + ## + # deep_clone is the usual Marshalling hack to make a deep copy. + # It is rather slow, so use it sparingly. Helps with debugging + # SexpProcessors since you usually shift off sexps. + + def deep_clone + Marshal.load(Marshal.dump(self)) + end +end + +## +# SexpProcessor base exception class. + +class SexpProcessorError < StandardError; end + +## +# Raised by SexpProcessor if it sees a node type listed in its +# unsupported list. + +class UnsupportedNodeError < SexpProcessorError; end + +## +# Raised by SexpProcessor if it is in strict mode and sees a node for +# which there is no processor available. + +class UnknownNodeError < SexpProcessorError; end + +## +# Raised by SexpProcessor if a processor did not process every node in +# a sexp and @require_empty is true. + +class NotEmptyError < SexpProcessorError; end + +## +# Raised if assert_type encounters an unexpected sexp type. + +class SexpTypeError < SexpProcessorError; end