brakeman-lib 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +872 -0
  3. data/FEATURES +16 -0
  4. data/README.md +169 -0
  5. data/WARNING_TYPES +95 -0
  6. data/bin/brakeman +89 -0
  7. data/lib/brakeman.rb +495 -0
  8. data/lib/brakeman/app_tree.rb +161 -0
  9. data/lib/brakeman/brakeman.rake +17 -0
  10. data/lib/brakeman/call_index.rb +219 -0
  11. data/lib/brakeman/checks.rb +191 -0
  12. data/lib/brakeman/checks/base_check.rb +518 -0
  13. data/lib/brakeman/checks/check_basic_auth.rb +88 -0
  14. data/lib/brakeman/checks/check_basic_auth_timing_attack.rb +33 -0
  15. data/lib/brakeman/checks/check_content_tag.rb +160 -0
  16. data/lib/brakeman/checks/check_create_with.rb +75 -0
  17. data/lib/brakeman/checks/check_cross_site_scripting.rb +385 -0
  18. data/lib/brakeman/checks/check_default_routes.rb +86 -0
  19. data/lib/brakeman/checks/check_deserialize.rb +57 -0
  20. data/lib/brakeman/checks/check_detailed_exceptions.rb +55 -0
  21. data/lib/brakeman/checks/check_digest_dos.rb +38 -0
  22. data/lib/brakeman/checks/check_dynamic_finders.rb +49 -0
  23. data/lib/brakeman/checks/check_escape_function.rb +21 -0
  24. data/lib/brakeman/checks/check_evaluation.rb +36 -0
  25. data/lib/brakeman/checks/check_execute.rb +167 -0
  26. data/lib/brakeman/checks/check_file_access.rb +63 -0
  27. data/lib/brakeman/checks/check_file_disclosure.rb +35 -0
  28. data/lib/brakeman/checks/check_filter_skipping.rb +31 -0
  29. data/lib/brakeman/checks/check_forgery_setting.rb +74 -0
  30. data/lib/brakeman/checks/check_header_dos.rb +31 -0
  31. data/lib/brakeman/checks/check_i18n_xss.rb +48 -0
  32. data/lib/brakeman/checks/check_jruby_xml.rb +38 -0
  33. data/lib/brakeman/checks/check_json_encoding.rb +47 -0
  34. data/lib/brakeman/checks/check_json_parsing.rb +107 -0
  35. data/lib/brakeman/checks/check_link_to.rb +132 -0
  36. data/lib/brakeman/checks/check_link_to_href.rb +115 -0
  37. data/lib/brakeman/checks/check_mail_to.rb +49 -0
  38. data/lib/brakeman/checks/check_mass_assignment.rb +198 -0
  39. data/lib/brakeman/checks/check_mime_type_dos.rb +39 -0
  40. data/lib/brakeman/checks/check_model_attr_accessible.rb +55 -0
  41. data/lib/brakeman/checks/check_model_attributes.rb +119 -0
  42. data/lib/brakeman/checks/check_model_serialize.rb +67 -0
  43. data/lib/brakeman/checks/check_nested_attributes.rb +38 -0
  44. data/lib/brakeman/checks/check_nested_attributes_bypass.rb +58 -0
  45. data/lib/brakeman/checks/check_number_to_currency.rb +74 -0
  46. data/lib/brakeman/checks/check_quote_table_name.rb +40 -0
  47. data/lib/brakeman/checks/check_redirect.rb +215 -0
  48. data/lib/brakeman/checks/check_regex_dos.rb +69 -0
  49. data/lib/brakeman/checks/check_render.rb +92 -0
  50. data/lib/brakeman/checks/check_render_dos.rb +37 -0
  51. data/lib/brakeman/checks/check_render_inline.rb +54 -0
  52. data/lib/brakeman/checks/check_response_splitting.rb +21 -0
  53. data/lib/brakeman/checks/check_route_dos.rb +42 -0
  54. data/lib/brakeman/checks/check_safe_buffer_manipulation.rb +31 -0
  55. data/lib/brakeman/checks/check_sanitize_methods.rb +79 -0
  56. data/lib/brakeman/checks/check_secrets.rb +40 -0
  57. data/lib/brakeman/checks/check_select_tag.rb +60 -0
  58. data/lib/brakeman/checks/check_select_vulnerability.rb +60 -0
  59. data/lib/brakeman/checks/check_send.rb +48 -0
  60. data/lib/brakeman/checks/check_send_file.rb +19 -0
  61. data/lib/brakeman/checks/check_session_manipulation.rb +36 -0
  62. data/lib/brakeman/checks/check_session_settings.rb +170 -0
  63. data/lib/brakeman/checks/check_simple_format.rb +59 -0
  64. data/lib/brakeman/checks/check_single_quotes.rb +101 -0
  65. data/lib/brakeman/checks/check_skip_before_filter.rb +60 -0
  66. data/lib/brakeman/checks/check_sql.rb +660 -0
  67. data/lib/brakeman/checks/check_sql_cves.rb +101 -0
  68. data/lib/brakeman/checks/check_ssl_verify.rb +49 -0
  69. data/lib/brakeman/checks/check_strip_tags.rb +89 -0
  70. data/lib/brakeman/checks/check_symbol_dos.rb +64 -0
  71. data/lib/brakeman/checks/check_symbol_dos_cve.rb +30 -0
  72. data/lib/brakeman/checks/check_translate_bug.rb +45 -0
  73. data/lib/brakeman/checks/check_unsafe_reflection.rb +51 -0
  74. data/lib/brakeman/checks/check_unscoped_find.rb +41 -0
  75. data/lib/brakeman/checks/check_validation_regex.rb +116 -0
  76. data/lib/brakeman/checks/check_weak_hash.rb +151 -0
  77. data/lib/brakeman/checks/check_without_protection.rb +80 -0
  78. data/lib/brakeman/checks/check_xml_dos.rb +51 -0
  79. data/lib/brakeman/checks/check_yaml_parsing.rb +121 -0
  80. data/lib/brakeman/differ.rb +66 -0
  81. data/lib/brakeman/file_parser.rb +50 -0
  82. data/lib/brakeman/format/style.css +133 -0
  83. data/lib/brakeman/options.rb +301 -0
  84. data/lib/brakeman/parsers/rails2_erubis.rb +6 -0
  85. data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +48 -0
  86. data/lib/brakeman/parsers/rails3_erubis.rb +74 -0
  87. data/lib/brakeman/parsers/template_parser.rb +89 -0
  88. data/lib/brakeman/processor.rb +102 -0
  89. data/lib/brakeman/processors/alias_processor.rb +1013 -0
  90. data/lib/brakeman/processors/base_processor.rb +277 -0
  91. data/lib/brakeman/processors/config_processor.rb +14 -0
  92. data/lib/brakeman/processors/controller_alias_processor.rb +273 -0
  93. data/lib/brakeman/processors/controller_processor.rb +326 -0
  94. data/lib/brakeman/processors/erb_template_processor.rb +80 -0
  95. data/lib/brakeman/processors/erubis_template_processor.rb +104 -0
  96. data/lib/brakeman/processors/gem_processor.rb +57 -0
  97. data/lib/brakeman/processors/haml_template_processor.rb +190 -0
  98. data/lib/brakeman/processors/lib/basic_processor.rb +37 -0
  99. data/lib/brakeman/processors/lib/find_all_calls.rb +223 -0
  100. data/lib/brakeman/processors/lib/find_call.rb +183 -0
  101. data/lib/brakeman/processors/lib/find_return_value.rb +134 -0
  102. data/lib/brakeman/processors/lib/processor_helper.rb +75 -0
  103. data/lib/brakeman/processors/lib/rails2_config_processor.rb +145 -0
  104. data/lib/brakeman/processors/lib/rails2_route_processor.rb +313 -0
  105. data/lib/brakeman/processors/lib/rails3_config_processor.rb +132 -0
  106. data/lib/brakeman/processors/lib/rails3_route_processor.rb +308 -0
  107. data/lib/brakeman/processors/lib/render_helper.rb +181 -0
  108. data/lib/brakeman/processors/lib/render_path.rb +107 -0
  109. data/lib/brakeman/processors/lib/route_helper.rb +68 -0
  110. data/lib/brakeman/processors/lib/safe_call_helper.rb +16 -0
  111. data/lib/brakeman/processors/library_processor.rb +119 -0
  112. data/lib/brakeman/processors/model_processor.rb +191 -0
  113. data/lib/brakeman/processors/output_processor.rb +171 -0
  114. data/lib/brakeman/processors/route_processor.rb +17 -0
  115. data/lib/brakeman/processors/slim_template_processor.rb +107 -0
  116. data/lib/brakeman/processors/template_alias_processor.rb +116 -0
  117. data/lib/brakeman/processors/template_processor.rb +74 -0
  118. data/lib/brakeman/report.rb +78 -0
  119. data/lib/brakeman/report/config/remediation.yml +71 -0
  120. data/lib/brakeman/report/ignore/config.rb +135 -0
  121. data/lib/brakeman/report/ignore/interactive.rb +311 -0
  122. data/lib/brakeman/report/renderer.rb +24 -0
  123. data/lib/brakeman/report/report_base.rb +286 -0
  124. data/lib/brakeman/report/report_codeclimate.rb +70 -0
  125. data/lib/brakeman/report/report_csv.rb +55 -0
  126. data/lib/brakeman/report/report_hash.rb +23 -0
  127. data/lib/brakeman/report/report_html.rb +216 -0
  128. data/lib/brakeman/report/report_json.rb +42 -0
  129. data/lib/brakeman/report/report_markdown.rb +156 -0
  130. data/lib/brakeman/report/report_table.rb +107 -0
  131. data/lib/brakeman/report/report_tabs.rb +17 -0
  132. data/lib/brakeman/report/templates/controller_overview.html.erb +22 -0
  133. data/lib/brakeman/report/templates/controller_warnings.html.erb +21 -0
  134. data/lib/brakeman/report/templates/error_overview.html.erb +29 -0
  135. data/lib/brakeman/report/templates/header.html.erb +58 -0
  136. data/lib/brakeman/report/templates/ignored_warnings.html.erb +25 -0
  137. data/lib/brakeman/report/templates/model_warnings.html.erb +21 -0
  138. data/lib/brakeman/report/templates/overview.html.erb +38 -0
  139. data/lib/brakeman/report/templates/security_warnings.html.erb +23 -0
  140. data/lib/brakeman/report/templates/template_overview.html.erb +21 -0
  141. data/lib/brakeman/report/templates/view_warnings.html.erb +34 -0
  142. data/lib/brakeman/report/templates/warning_overview.html.erb +17 -0
  143. data/lib/brakeman/rescanner.rb +483 -0
  144. data/lib/brakeman/scanner.rb +317 -0
  145. data/lib/brakeman/tracker.rb +347 -0
  146. data/lib/brakeman/tracker/collection.rb +93 -0
  147. data/lib/brakeman/tracker/config.rb +101 -0
  148. data/lib/brakeman/tracker/constants.rb +101 -0
  149. data/lib/brakeman/tracker/controller.rb +161 -0
  150. data/lib/brakeman/tracker/library.rb +17 -0
  151. data/lib/brakeman/tracker/model.rb +90 -0
  152. data/lib/brakeman/tracker/template.rb +33 -0
  153. data/lib/brakeman/util.rb +481 -0
  154. data/lib/brakeman/version.rb +3 -0
  155. data/lib/brakeman/warning.rb +255 -0
  156. data/lib/brakeman/warning_codes.rb +111 -0
  157. data/lib/ruby_parser/bm_sexp.rb +610 -0
  158. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  159. metadata +362 -0
@@ -0,0 +1,6 @@
1
+ Brakeman.load_brakeman_dependency 'erubis'
2
+
3
+ #Erubis processor which ignores any output which is plain text.
4
+ class Brakeman::ScannerErubis < Erubis::Eruby
5
+ include Erubis::NoTextEnhancer
6
+ end
@@ -0,0 +1,48 @@
1
+ Brakeman.load_brakeman_dependency 'erubis'
2
+
3
+ #This is from the rails_xss plugin for Rails 2
4
+ class Brakeman::Rails2XSSPluginErubis < ::Erubis::Eruby
5
+ def add_preamble(src)
6
+ #src << "@output_buffer = ActiveSupport::SafeBuffer.new;"
7
+ end
8
+
9
+ #This is different from rails_xss - fixes some line number issues
10
+ def add_text(src, text)
11
+ if text == "\n"
12
+ src << "\n"
13
+ elsif text.include? "\n"
14
+ lines = text.split("\n")
15
+ if text.match(/\n\z/)
16
+ lines.each do |line|
17
+ src << "@output_buffer.safe_concat('" << escape_text(line) << "');\n"
18
+ end
19
+ else
20
+ lines[0..-2].each do |line|
21
+ src << "@output_buffer.safe_concat('" << escape_text(line) << "');\n"
22
+ end
23
+
24
+ src << "@output_buffer.safe_concat('" << escape_text(lines.last) << "');"
25
+ end
26
+ else
27
+ src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
28
+ end
29
+ end
30
+
31
+ BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
32
+
33
+ def add_expr_literal(src, code)
34
+ if code =~ BLOCK_EXPR
35
+ src << "@output_buffer.safe_concat((" << $1 << ").to_s);"
36
+ else
37
+ src << '@output_buffer << ((' << code << ').to_s);'
38
+ end
39
+ end
40
+
41
+ def add_expr_escaped(src, code)
42
+ src << '@output_buffer << ' << escaped_expr(code) << ';'
43
+ end
44
+
45
+ def add_postamble(src)
46
+ #src << '@output_buffer.to_s'
47
+ end
48
+ end
@@ -0,0 +1,74 @@
1
+ Brakeman.load_brakeman_dependency 'erubis'
2
+
3
+ # This is from Rails 5 version of the Erubis handler
4
+ # https://github.com/rails/rails/blob/ec608107801b1e505db03ba76bae4a326a5804ca/actionview/lib/action_view/template/handlers/erb.rb#L7-L73
5
+ class Brakeman::Rails3Erubis < ::Erubis::Eruby
6
+
7
+ def add_preamble(src)
8
+ @newline_pending = 0
9
+ src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
10
+ end
11
+
12
+ def add_text(src, text)
13
+ return if text.empty?
14
+
15
+ if text == "\n"
16
+ @newline_pending += 1
17
+ else
18
+ src << "@output_buffer.safe_append='"
19
+ src << "\n" * @newline_pending if @newline_pending > 0
20
+ src << escape_text(text)
21
+ src << "'.freeze;"
22
+
23
+ @newline_pending = 0
24
+ end
25
+ end
26
+
27
+ # Erubis toggles <%= and <%== behavior when escaping is enabled.
28
+ # We override to always treat <%== as escaped.
29
+ def add_expr(src, code, indicator)
30
+ case indicator
31
+ when '=='
32
+ add_expr_escaped(src, code)
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
39
+
40
+ def add_expr_literal(src, code)
41
+ flush_newline_if_pending(src)
42
+ if code =~ BLOCK_EXPR
43
+ src << '@output_buffer.append= ' << code
44
+ else
45
+ src << '@output_buffer.append=(' << code << ');'
46
+ end
47
+ end
48
+
49
+ def add_expr_escaped(src, code)
50
+ flush_newline_if_pending(src)
51
+ if code =~ BLOCK_EXPR
52
+ src << "@output_buffer.safe_expr_append= " << code
53
+ else
54
+ src << "@output_buffer.safe_expr_append=(" << code << ");"
55
+ end
56
+ end
57
+
58
+ def add_stmt(src, code)
59
+ flush_newline_if_pending(src)
60
+ super
61
+ end
62
+
63
+ def add_postamble(src)
64
+ flush_newline_if_pending(src)
65
+ src << '@output_buffer.to_s'
66
+ end
67
+
68
+ def flush_newline_if_pending(src)
69
+ if @newline_pending > 0
70
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
71
+ @newline_pending = 0
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,89 @@
1
+ module Brakeman
2
+ class TemplateParser
3
+ include Brakeman::Util
4
+ attr_reader :tracker
5
+ KNOWN_TEMPLATE_EXTENSIONS = /.*\.(erb|haml|rhtml|slim)$/
6
+
7
+ TemplateFile = Struct.new(:path, :ast, :name, :type)
8
+
9
+ def initialize tracker, file_parser
10
+ @tracker = tracker
11
+ @file_parser = file_parser
12
+ @file_parser.file_list[:templates] ||= []
13
+ end
14
+
15
+ def parse_template path, text
16
+ type = path.match(KNOWN_TEMPLATE_EXTENSIONS)[1].to_sym
17
+ type = :erb if type == :rhtml
18
+ name = template_path_to_name path
19
+ Brakeman.debug "Parsing #{path}"
20
+
21
+ begin
22
+ src = case type
23
+ when :erb
24
+ type = :erubis if erubis?
25
+ parse_erb text
26
+ when :haml
27
+ parse_haml text
28
+ when :slim
29
+ parse_slim text
30
+ else
31
+ tracker.error "Unknown template type in #{path}"
32
+ nil
33
+ end
34
+
35
+ if src and ast = @file_parser.parse_ruby(src, path)
36
+ @file_parser.file_list[:templates] << TemplateFile.new(path, ast, name, type)
37
+ end
38
+ rescue Racc::ParseError => e
39
+ tracker.error e, "could not parse #{path}"
40
+ rescue Haml::Error => e
41
+ tracker.error e, ["While compiling HAML in #{path}"] << e.backtrace
42
+ rescue StandardError, LoadError => e
43
+ tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
44
+ end
45
+
46
+ nil
47
+ end
48
+
49
+ def parse_erb text
50
+ if tracker.config.escape_html?
51
+ if tracker.options[:rails3]
52
+ require 'brakeman/parsers/rails3_erubis'
53
+ Brakeman::Rails3Erubis.new(text).src
54
+ else
55
+ require 'brakeman/parsers/rails2_xss_plugin_erubis'
56
+ Brakeman::Rails2XSSPluginErubis.new(text).src
57
+ end
58
+ elsif tracker.config.erubis?
59
+ require 'brakeman/parsers/rails2_erubis'
60
+ Brakeman::ScannerErubis.new(text).src
61
+ else
62
+ require 'erb'
63
+ src = ERB.new(text, nil, "-").src
64
+ src.sub!(/^#.*\n/, '') if Brakeman::Scanner::RUBY_1_9
65
+ src
66
+ end
67
+ end
68
+
69
+ def erubis?
70
+ tracker.config.escape_html? or
71
+ tracker.config.erubis?
72
+ end
73
+
74
+ def parse_haml text
75
+ Brakeman.load_brakeman_dependency 'haml'
76
+ Brakeman.load_brakeman_dependency 'sass'
77
+
78
+ Haml::Engine.new(text,
79
+ :escape_html => tracker.config.escape_html?).precompiled.gsub(/([^\\])\\n/, '\1')
80
+ end
81
+
82
+ def parse_slim text
83
+ Brakeman.load_brakeman_dependency 'slim'
84
+
85
+ Slim::Template.new(:disable_capture => true,
86
+ :generator => Temple::Generators::RailsOutputBuffer) { text }.precompiled_template
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,102 @@
1
+ #Load all files in processors/
2
+ Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/processors/*.rb").each { |f| require f.match(/brakeman\/processors.*/)[0] }
3
+ require 'brakeman/tracker'
4
+ require 'set'
5
+ require 'pathname'
6
+
7
+ module Brakeman
8
+ #Makes calls to the appropriate processor.
9
+ #
10
+ #The ControllerProcessor, TemplateProcessor, and ModelProcessor will
11
+ #update the Tracker with information about what is parsed.
12
+ class Processor
13
+ include Util
14
+
15
+ def initialize(app_tree, options)
16
+ @app_tree = app_tree
17
+ @tracker = Tracker.new(@app_tree, self, options)
18
+ end
19
+
20
+ def tracked_events
21
+ @tracker
22
+ end
23
+
24
+ #Process configuration file source
25
+ def process_config src, file_name
26
+ ConfigProcessor.new(@tracker).process_config src, file_name
27
+ end
28
+
29
+ #Process Gemfile
30
+ def process_gems gem_files
31
+ GemProcessor.new(@tracker).process_gems gem_files
32
+ end
33
+
34
+ #Process route file source
35
+ def process_routes src
36
+ RoutesProcessor.new(@tracker).process_routes src
37
+ end
38
+
39
+ #Process controller source. +file_name+ is used for reporting
40
+ def process_controller src, file_name
41
+ if contains_class? src
42
+ ControllerProcessor.new(@app_tree, @tracker).process_controller src, file_name
43
+ else
44
+ LibraryProcessor.new(@tracker).process_library src, file_name
45
+ end
46
+ end
47
+
48
+ #Process variable aliasing in controller source and save it in the
49
+ #tracker.
50
+ def process_controller_alias name, src, only_method = nil, file = nil
51
+ ControllerAliasProcessor.new(@app_tree, @tracker, only_method).process_controller name, src, file
52
+ end
53
+
54
+ #Process a model source
55
+ def process_model src, file_name
56
+ result = ModelProcessor.new(@tracker).process_model src, file_name
57
+ AliasProcessor.new(@tracker).process_all result if result
58
+ end
59
+
60
+ #Process either an ERB or HAML template
61
+ def process_template name, src, type, called_from = nil, file_name = nil
62
+ case type
63
+ when :erb
64
+ result = ErbTemplateProcessor.new(@tracker, name, called_from, file_name).process src
65
+ when :haml
66
+ result = HamlTemplateProcessor.new(@tracker, name, called_from, file_name).process src
67
+ when :erubis
68
+ result = ErubisTemplateProcessor.new(@tracker, name, called_from, file_name).process src
69
+ when :slim
70
+ result = SlimTemplateProcessor.new(@tracker, name, called_from, file_name).process src
71
+ else
72
+ abort "Unknown template type: #{type} (#{name})"
73
+ end
74
+
75
+ #Each template which is rendered is stored separately
76
+ #with a new name.
77
+ if called_from
78
+ name = ("#{name}.#{called_from}").to_sym
79
+ end
80
+
81
+ @tracker.templates[name].src = result
82
+ @tracker.templates[name].type = type
83
+ end
84
+
85
+ #Process any calls to render() within a template
86
+ def process_template_alias template
87
+ TemplateAliasProcessor.new(@tracker, template).process_safely template.src
88
+ end
89
+
90
+ #Process source for initializing files
91
+ def process_initializer file_name, src
92
+ res = BaseProcessor.new(@tracker).process_file src, file_name
93
+ res = AliasProcessor.new(@tracker).process_safely res, nil, file_name
94
+ @tracker.initializers[Pathname.new(file_name).basename.to_s] = res
95
+ end
96
+
97
+ #Process source for a library file
98
+ def process_lib src, file_name
99
+ LibraryProcessor.new(@tracker).process_library src, file_name
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,1013 @@
1
+ require 'brakeman/util'
2
+ require 'ruby_parser/bm_sexp_processor'
3
+ require 'brakeman/processors/lib/processor_helper'
4
+ require 'brakeman/processors/lib/safe_call_helper'
5
+
6
+ #Returns an s-expression with aliases replaced with their value.
7
+ #This does not preserve semantics (due to side effects, etc.), but it makes
8
+ #processing easier when searching for various things.
9
+ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
10
+ include Brakeman::ProcessorHelper
11
+ include Brakeman::SafeCallHelper
12
+ include Brakeman::Util
13
+
14
+ attr_reader :result, :tracker
15
+
16
+ #Returns a new AliasProcessor with an empty environment.
17
+ #
18
+ #The recommended usage is:
19
+ #
20
+ # AliasProcessor.new.process_safely src
21
+ def initialize tracker = nil, file_name = nil
22
+ super()
23
+ @env = SexpProcessor::Environment.new
24
+ @inside_if = false
25
+ @ignore_ifs = nil
26
+ @exp_context = []
27
+ @current_module = nil
28
+ @tracker = tracker #set in subclass as necessary
29
+ @helper_method_cache = {}
30
+ @helper_method_info = Hash.new({})
31
+ @or_depth_limit = (tracker && tracker.options[:branch_limit]) || 5 #arbitrary default
32
+ @meth_env = nil
33
+ @file_name = file_name
34
+ set_env_defaults
35
+ end
36
+
37
+ #This method processes the given Sexp, but copies it first so
38
+ #the original argument will not be modified.
39
+ #
40
+ #_set_env_ should be an instance of SexpProcessor::Environment. If provided,
41
+ #it will be used as the starting environment.
42
+ #
43
+ #This method returns a new Sexp with variables replaced with their values,
44
+ #where possible.
45
+ def process_safely src, set_env = nil, file_name = nil
46
+ @file_name = file_name
47
+ @env = set_env || SexpProcessor::Environment.new
48
+ @result = src.deep_clone
49
+ process @result
50
+ @result
51
+ end
52
+
53
+ #Process a Sexp. If the Sexp has a value associated with it in the
54
+ #environment, that value will be returned.
55
+ def process_default exp
56
+ @exp_context.push exp
57
+
58
+ begin
59
+ exp.map! do |e|
60
+ if sexp? e and not e.empty?
61
+ process e
62
+ else
63
+ e
64
+ end
65
+ end
66
+ rescue => err
67
+ @tracker.error err if @tracker
68
+ end
69
+
70
+ result = replace(exp)
71
+
72
+ @exp_context.pop
73
+
74
+ result
75
+ end
76
+
77
+ def replace exp, int = 0
78
+ return exp if int > 3
79
+
80
+
81
+ if replacement = env[exp] and not duplicate? replacement
82
+ replace(replacement.deep_clone(exp.line), int + 1)
83
+ elsif tracker and replacement = tracker.constant_lookup(exp) and not duplicate? replacement
84
+ replace(replacement.deep_clone(exp.line), int + 1)
85
+ else
86
+ exp
87
+ end
88
+ end
89
+
90
+ ARRAY_CONST = s(:const, :Array)
91
+ HASH_CONST = s(:const, :Hash)
92
+ RAILS_TEST = s(:call, s(:call, s(:const, :Rails), :env), :test?)
93
+
94
+ #Process a method call.
95
+ def process_call exp
96
+ return exp if process_call_defn? exp
97
+ target_var = exp.target
98
+ target_var &&= target_var.deep_clone
99
+ if exp.node_type == :safe_call
100
+ exp.node_type = :call
101
+ end
102
+ exp = process_default exp
103
+
104
+ #In case it is replaced with something else
105
+ unless call? exp
106
+ return exp
107
+ end
108
+
109
+ target = exp.target
110
+ method = exp.method
111
+ first_arg = exp.first_arg
112
+
113
+ if method == :send or method == :try
114
+ collapse_send_call exp, first_arg
115
+ end
116
+
117
+ if node_type? target, :or and [:+, :-, :*, :/].include? method
118
+ res = process_or_simple_operation(exp)
119
+ return res if res
120
+ elsif target == ARRAY_CONST and method == :new
121
+ return Sexp.new(:array, *exp.args)
122
+ elsif target == HASH_CONST and method == :new and first_arg.nil? and !node_type?(@exp_context.last, :iter)
123
+ return Sexp.new(:hash)
124
+ elsif exp == RAILS_TEST
125
+ return Sexp.new(:false)
126
+ end
127
+
128
+
129
+ #See if it is possible to simplify some basic cases
130
+ #of addition/concatenation.
131
+ case method
132
+ when :+
133
+ if array? target and array? first_arg
134
+ joined = join_arrays target, first_arg
135
+ joined.line(exp.line)
136
+ exp = joined
137
+ elsif string? first_arg
138
+ if string? target # "blah" + "blah"
139
+ joined = join_strings target, first_arg
140
+ joined.line(exp.line)
141
+ exp = joined
142
+ elsif call? target and target.method == :+ and string? target.first_arg
143
+ joined = join_strings target.first_arg, first_arg
144
+ joined.line(exp.line)
145
+ target.first_arg = joined
146
+ exp = target
147
+ end
148
+ elsif number? first_arg
149
+ if number? target
150
+ exp = Sexp.new(:lit, target.value + first_arg.value)
151
+ elsif call? target and target.method == :+ and number? target.first_arg
152
+ target.first_arg = Sexp.new(:lit, target.first_arg.value + first_arg.value)
153
+ exp = target
154
+ end
155
+ end
156
+ when :-
157
+ if number? target and number? first_arg
158
+ exp = Sexp.new(:lit, target.value - first_arg.value)
159
+ end
160
+ when :*
161
+ if number? target and number? first_arg
162
+ exp = Sexp.new(:lit, target.value * first_arg.value)
163
+ end
164
+ when :/
165
+ if number? target and number? first_arg
166
+ if first_arg.value == 0 and not target.value.is_a? Float
167
+ if @tracker
168
+ location = [@current_class, @current_method, "line #{first_arg.line}"].compact.join(' ')
169
+ require 'brakeman/processors/output_processor'
170
+ code = Brakeman::OutputProcessor.new.format(exp)
171
+ @tracker.error Exception.new("Potential divide by zero: #{code} (#{location})")
172
+ end
173
+ else
174
+ exp = Sexp.new(:lit, target.value / first_arg.value)
175
+ end
176
+ end
177
+ when :[]
178
+ if array? target
179
+ temp_exp = process_array_access target, exp.args
180
+ exp = temp_exp if temp_exp
181
+ elsif hash? target
182
+ temp_exp = process_hash_access target, first_arg
183
+ exp = temp_exp if temp_exp
184
+ end
185
+ when :merge!, :update
186
+ if hash? target and hash? first_arg
187
+ target = process_hash_merge! target, first_arg
188
+ env[target_var] = target
189
+ return target
190
+ end
191
+ when :merge
192
+ if hash? target and hash? first_arg
193
+ return process_hash_merge(target, first_arg)
194
+ end
195
+ when :<<
196
+ if string? target and string? first_arg
197
+ target.value << first_arg.value
198
+ env[target_var] = target
199
+ return target
200
+ elsif string? target and string_interp? first_arg
201
+ exp = Sexp.new(:dstr, target.value + first_arg[1]).concat(first_arg[2..-1])
202
+ env[target_var] = exp
203
+ elsif string? first_arg and string_interp? target
204
+ if string? target.last
205
+ target.last.value << first_arg.value
206
+ elsif target.last.is_a? String
207
+ target.last << first_arg.value
208
+ else
209
+ target << first_arg
210
+ end
211
+ env[target_var] = target
212
+ return first_arg
213
+ elsif array? target
214
+ target << first_arg
215
+ env[target_var] = target
216
+ return target
217
+ else
218
+ target = find_push_target(target_var)
219
+ env[target] = exp unless target.nil? # Happens in TemplateAliasProcessor
220
+ end
221
+ when :first
222
+ if array? target and first_arg.nil? and sexp? target[1]
223
+ exp = target[1]
224
+ end
225
+ end
226
+
227
+ exp
228
+ end
229
+
230
+ def process_iter exp
231
+ @exp_context.push exp
232
+ exp[1] = process exp.block_call
233
+ if array_detect_all_literals? exp[1]
234
+ return exp.block_call.target[1]
235
+ end
236
+
237
+ @exp_context.pop
238
+
239
+ env.scope do
240
+ exp.block_args.each do |e|
241
+ #Force block arg(s) to be local
242
+ if node_type? e, :lasgn
243
+ env.current[Sexp.new(:lvar, e.lhs)] = Sexp.new(:lvar, e.lhs)
244
+ elsif node_type? e, :kwarg
245
+ env.current[Sexp.new(:lvar, e[1])] = e[2]
246
+ elsif node_type? e, :masgn, :shadow
247
+ e[1..-1].each do |var|
248
+ local = Sexp.new(:lvar, var)
249
+ env.current[local] = local
250
+ end
251
+ elsif e.is_a? Symbol
252
+ local = Sexp.new(:lvar, e)
253
+ env.current[local] = local
254
+ else
255
+ raise "Unexpected value in block args: #{e.inspect}"
256
+ end
257
+ end
258
+
259
+ block = exp.block
260
+
261
+ if block? block
262
+ process_all! block
263
+ else
264
+ exp[3] = process block
265
+ end
266
+ end
267
+
268
+ exp
269
+ end
270
+
271
+ #Process a new scope.
272
+ def process_scope exp
273
+ env.scope do
274
+ process exp.block
275
+ end
276
+ exp
277
+ end
278
+
279
+ #Start new scope for block.
280
+ def process_block exp
281
+ env.scope do
282
+ process_default exp
283
+ end
284
+ end
285
+
286
+ #Process a method definition.
287
+ def process_defn exp
288
+ meth_env do
289
+ exp.body = process_all! exp.body
290
+ end
291
+ exp
292
+ end
293
+
294
+ def meth_env
295
+ begin
296
+ env.scope do
297
+ set_env_defaults
298
+ @meth_env = env.current
299
+ yield
300
+ end
301
+ ensure
302
+ @meth_env = nil
303
+ end
304
+ end
305
+
306
+ #Process a method definition on self.
307
+ def process_defs exp
308
+ env.scope do
309
+ set_env_defaults
310
+ exp.body = process_all! exp.body
311
+ end
312
+ exp
313
+ end
314
+
315
+ # Handles x = y = z = 1
316
+ def get_rhs exp
317
+ if node_type? exp, :lasgn, :iasgn, :gasgn, :attrasgn, :safe_attrasgn, :cvdecl, :cdecl
318
+ get_rhs(exp.rhs)
319
+ else
320
+ exp
321
+ end
322
+ end
323
+
324
+ #Local assignment
325
+ # x = 1
326
+ def process_lasgn exp
327
+ self_assign = self_assign?(exp.lhs, exp.rhs)
328
+ exp.rhs = process exp.rhs if sexp? exp.rhs
329
+ return exp if exp.rhs.nil?
330
+
331
+ local = Sexp.new(:lvar, exp.lhs).line(exp.line || -2)
332
+
333
+ if self_assign
334
+ # Skip branching
335
+ env[local] = get_rhs(exp)
336
+ else
337
+ set_value local, get_rhs(exp)
338
+ end
339
+
340
+ exp
341
+ end
342
+
343
+ #Instance variable assignment
344
+ # @x = 1
345
+ def process_iasgn exp
346
+ self_assign = self_assign?(exp.lhs, exp.rhs)
347
+ exp.rhs = process exp.rhs
348
+ ivar = Sexp.new(:ivar, exp.lhs).line(exp.line)
349
+
350
+ if self_assign
351
+ if env[ivar].nil? and @meth_env
352
+ @meth_env[ivar] = get_rhs(exp)
353
+ else
354
+ env[ivar] = get_rhs(exp)
355
+ end
356
+ else
357
+ set_value ivar, get_rhs(exp)
358
+ end
359
+
360
+ exp
361
+ end
362
+
363
+ #Global assignment
364
+ # $x = 1
365
+ def process_gasgn exp
366
+ match = Sexp.new(:gvar, exp.lhs)
367
+ exp.rhs = process(exp.rhs)
368
+ value = get_rhs(exp)
369
+ value.line = exp.line
370
+
371
+ set_value match, value
372
+
373
+ exp
374
+ end
375
+
376
+ #Class variable assignment
377
+ # @@x = 1
378
+ def process_cvdecl exp
379
+ match = Sexp.new(:cvar, exp.lhs)
380
+ exp.rhs = process(exp.rhs)
381
+ value = get_rhs(exp)
382
+
383
+ set_value match, value
384
+
385
+ exp
386
+ end
387
+
388
+ #'Attribute' assignment
389
+ # x.y = 1
390
+ #or
391
+ # x[:y] = 1
392
+ def process_attrasgn exp
393
+ tar_variable = exp.target
394
+ target = exp.target = process(exp.target)
395
+ method = exp.method
396
+ index_arg = exp.first_arg
397
+ value_arg = exp.second_arg
398
+
399
+ if method == :[]=
400
+ index = exp.first_arg = process(index_arg)
401
+ value = exp.second_arg = process(value_arg)
402
+ match = Sexp.new(:call, target, :[], index)
403
+
404
+ set_value match, value
405
+
406
+ if hash? target
407
+ env[tar_variable] = hash_insert target.deep_clone, index, value
408
+ end
409
+ elsif method.to_s[-1,1] == "="
410
+ exp.first_arg = process(index_arg)
411
+ value = get_rhs(exp)
412
+ #This is what we'll replace with the value
413
+ match = Sexp.new(:call, target, method.to_s[0..-2].to_sym)
414
+
415
+ set_value match, value
416
+ else
417
+ raise "Unrecognized assignment: #{exp}"
418
+ end
419
+ exp
420
+ end
421
+
422
+ # Multiple/parallel assignment:
423
+ #
424
+ # x, y = z, w
425
+ def process_masgn exp
426
+ unless array? exp[1] and array? exp[2] and exp[1].length == exp[2].length
427
+ return process_default(exp)
428
+ end
429
+
430
+ vars = exp[1].dup
431
+ vals = exp[2].dup
432
+
433
+ vars.shift
434
+ vals.shift
435
+
436
+ # Call each assignment as if it is normal
437
+ vars.each_with_index do |var, i|
438
+ val = vals[i]
439
+ if val
440
+ assign = var.dup
441
+ assign.rhs = val
442
+ process assign
443
+ end
444
+ end
445
+
446
+ exp
447
+ end
448
+
449
+ #Merge values into hash when processing
450
+ #
451
+ # h.merge! :something => "value"
452
+ def process_hash_merge! hash, args
453
+ hash = hash.deep_clone
454
+ hash_iterate args do |key, replacement|
455
+ hash_insert hash, key, replacement
456
+ match = Sexp.new(:call, hash, :[], key)
457
+ env[match] = replacement
458
+ end
459
+ hash
460
+ end
461
+
462
+ #Return a new hash Sexp with the given values merged into it.
463
+ #
464
+ #+args+ should be a hash Sexp as well.
465
+ def process_hash_merge hash, args
466
+ hash = hash.deep_clone
467
+ hash_iterate args do |key, replacement|
468
+ hash_insert hash, key, replacement
469
+ end
470
+ hash
471
+ end
472
+
473
+ #Assignments like this
474
+ # x[:y] ||= 1
475
+ def process_op_asgn1 exp
476
+ return process_default(exp) if exp[3] != :"||"
477
+
478
+ target = exp[1] = process(exp[1])
479
+ index = exp[2][1] = process(exp[2][1])
480
+ value = exp[4] = process(exp[4])
481
+ match = Sexp.new(:call, target, :[], index)
482
+
483
+ unless env[match]
484
+ if request_value? target
485
+ env[match] = match.combine(value)
486
+ else
487
+ env[match] = value
488
+ end
489
+ end
490
+
491
+ exp
492
+ end
493
+
494
+ #Assignments like this
495
+ # x.y ||= 1
496
+ def process_op_asgn2 exp
497
+ return process_default(exp) if exp[3] != :"||"
498
+
499
+ target = exp[1] = process(exp[1])
500
+ value = exp[4] = process(exp[4])
501
+ method = exp[2]
502
+
503
+ match = Sexp.new(:call, target, method.to_s[0..-2].to_sym)
504
+
505
+ unless env[match]
506
+ env[match] = value
507
+ end
508
+
509
+ exp
510
+ end
511
+
512
+ #This is the right hand side value of a multiple assignment,
513
+ #like `x = y, z`
514
+ def process_svalue exp
515
+ exp.value
516
+ end
517
+
518
+ #Constant assignments like
519
+ # BIG_CONSTANT = 234810983
520
+ def process_cdecl exp
521
+ if sexp? exp.rhs
522
+ exp.rhs = process exp.rhs
523
+ end
524
+
525
+ file = case
526
+ when @file_name
527
+ @file_name
528
+ when @current_class.is_a?(Brakeman::Collection)
529
+ @current_class.file
530
+ when @current_module.is_a?(Brakeman::Collection)
531
+ @current_module.file
532
+ else
533
+ nil
534
+ end
535
+
536
+ @tracker.add_constant exp.lhs, exp.rhs, :file => file if @tracker
537
+
538
+ if exp.lhs.is_a? Symbol
539
+ match = Sexp.new(:const, exp.lhs)
540
+ else
541
+ match = exp.lhs
542
+ end
543
+
544
+ env[match] = get_rhs(exp)
545
+
546
+ exp
547
+ end
548
+
549
+ # Check if exp is a call to Array#include? on an array literal
550
+ # that contains all literal values. For example:
551
+ #
552
+ # [1, 2, "a"].include? x
553
+ #
554
+ def array_include_all_literals? exp
555
+ call? exp and
556
+ exp.method == :include? and
557
+ node_type? exp.target, :array and
558
+ exp.target.length > 1 and
559
+ exp.target.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
560
+ end
561
+
562
+ def array_detect_all_literals? exp
563
+ call? exp and
564
+ [:detect, :find].include? exp.method and
565
+ node_type? exp.target, :array and
566
+ exp.target.length > 1 and
567
+ exp.first_arg.nil? and
568
+ exp.target.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
569
+ end
570
+
571
+ #Sets @inside_if = true
572
+ def process_if exp
573
+ if @ignore_ifs.nil?
574
+ @ignore_ifs = @tracker && @tracker.options[:ignore_ifs]
575
+ end
576
+
577
+ condition = exp.condition = process exp.condition
578
+
579
+ #Check if a branch is obviously going to be taken
580
+ if true? condition
581
+ no_branch = true
582
+ exps = [exp.then_clause, nil]
583
+ elsif false? condition
584
+ no_branch = true
585
+ exps = [nil, exp.else_clause]
586
+ else
587
+ no_branch = false
588
+ exps = [exp.then_clause, exp.else_clause]
589
+ end
590
+
591
+ if @ignore_ifs or no_branch
592
+ exps.each_with_index do |branch, i|
593
+ exp[2 + i] = process_if_branch branch
594
+ end
595
+ else
596
+ was_inside = @inside_if
597
+ @inside_if = true
598
+
599
+ branch_scopes = []
600
+ exps.each_with_index do |branch, i|
601
+ scope do
602
+ @branch_env = env.current
603
+ branch_index = 2 + i # s(:if, condition, then_branch, else_branch)
604
+ if i == 0 and array_include_all_literals? condition
605
+ # If the condition is ["a", "b"].include? x
606
+ # set x to "a" inside the true branch
607
+ var = condition.first_arg
608
+ previous_value = env.current[var]
609
+ env.current[var] = condition.target[1]
610
+ exp[branch_index] = process_if_branch branch
611
+ env.current[var] = previous_value
612
+ else
613
+ exp[branch_index] = process_if_branch branch
614
+ end
615
+ branch_scopes << env.current
616
+ @branch_env = nil
617
+ end
618
+ end
619
+
620
+ @inside_if = was_inside
621
+
622
+ branch_scopes.each do |s|
623
+ merge_if_branch s
624
+ end
625
+ end
626
+
627
+ exp
628
+ end
629
+
630
+ def process_if_branch exp
631
+ if sexp? exp
632
+ if block? exp
633
+ process_default exp
634
+ else
635
+ process exp
636
+ end
637
+ end
638
+ end
639
+
640
+ def merge_if_branch branch_env
641
+ branch_env.each do |k, v|
642
+ next if v.nil?
643
+
644
+ current_val = env[k]
645
+
646
+ if current_val
647
+ unless same_value?(current_val, v)
648
+ if too_deep? current_val
649
+ # Give up branching, start over with latest value
650
+ env[k] = v
651
+ else
652
+ env[k] = current_val.combine(v, k.line)
653
+ end
654
+ end
655
+ else
656
+ env[k] = v
657
+ end
658
+ end
659
+ end
660
+
661
+ def too_deep? exp
662
+ @or_depth_limit >= 0 and
663
+ node_type? exp, :or and
664
+ exp.or_depth and
665
+ exp.or_depth >= @or_depth_limit
666
+ end
667
+
668
+ #Process single integer access to an array.
669
+ #
670
+ #Returns the value inside the array, if possible.
671
+ def process_array_access target, args
672
+ if args.length == 1 and integer? args.first
673
+ index = args.first.value
674
+
675
+ #Have to do this because first element is :array and we have to skip it
676
+ target[1..-1][index]
677
+ else
678
+ nil
679
+ end
680
+ end
681
+
682
+ #Process hash access by returning the value associated
683
+ #with the given argument.
684
+ def process_hash_access target, index
685
+ hash_access(target, index)
686
+ end
687
+
688
+ #Join two array literals into one.
689
+ def join_arrays array1, array2
690
+ result = Sexp.new(:array)
691
+ result.concat array1[1..-1]
692
+ result.concat array2[1..-1]
693
+ end
694
+
695
+ #Join two string literals into one.
696
+ def join_strings string1, string2
697
+ result = Sexp.new(:str)
698
+ result.value = string1.value + string2.value
699
+
700
+ if result.value.length > 50
701
+ string1
702
+ else
703
+ result
704
+ end
705
+ end
706
+
707
+ # Change x.send(:y, 1) to x.y(1)
708
+ def collapse_send_call exp, first_arg
709
+ # Handle try(&:id)
710
+ if node_type? first_arg, :block_pass
711
+ first_arg = first_arg[1]
712
+ end
713
+
714
+ return unless symbol? first_arg or string? first_arg
715
+ exp.method = first_arg.value.to_sym
716
+ args = exp.args
717
+ exp.pop # remove last arg
718
+ if args.length > 1
719
+ exp.arglist = args[1..-1]
720
+ end
721
+ end
722
+
723
+ #Returns a new SexpProcessor::Environment containing only instance variables.
724
+ #This is useful, for example, when processing views.
725
+ def only_ivars include_request_vars = false, lenv = nil
726
+ lenv ||= env
727
+ res = SexpProcessor::Environment.new
728
+
729
+ if include_request_vars
730
+ lenv.all.each do |k, v|
731
+ #TODO Why would this have nil values?
732
+ if (k.node_type == :ivar or request_value? k) and not v.nil?
733
+ res[k] = v.dup
734
+ end
735
+ end
736
+ else
737
+ lenv.all.each do |k, v|
738
+ #TODO Why would this have nil values?
739
+ if k.node_type == :ivar and not v.nil?
740
+ res[k] = v.dup
741
+ end
742
+ end
743
+ end
744
+
745
+ res
746
+ end
747
+
748
+ def only_request_vars
749
+ res = SexpProcessor::Environment.new
750
+
751
+ env.all.each do |k, v|
752
+ if request_value? k and not v.nil?
753
+ res[k] = v.dup
754
+ end
755
+ end
756
+
757
+ res
758
+ end
759
+
760
+ def get_call_value call
761
+ method_name = call.method
762
+
763
+ #Look for helper methods and see if we can get a return value
764
+ if found_method = find_method(method_name, @current_class)
765
+ helper = found_method[:method]
766
+
767
+ if sexp? helper
768
+ value = process_helper_method helper, call.args
769
+ value.line(call.line)
770
+ return value
771
+ else
772
+ raise "Unexpected value for method: #{found_method}"
773
+ end
774
+ else
775
+ call
776
+ end
777
+ end
778
+
779
+ def process_helper_method method_exp, args
780
+ method_name = method_exp.method_name
781
+ Brakeman.debug "Processing method #{method_name}"
782
+
783
+ info = @helper_method_info[method_name]
784
+
785
+ #If method uses instance variables, then include those and request
786
+ #variables (params, etc) in the method environment. Otherwise,
787
+ #only include request variables.
788
+ if info[:uses_ivars]
789
+ meth_env = only_ivars(:include_request_vars)
790
+ else
791
+ meth_env = only_request_vars
792
+ end
793
+
794
+ #Add arguments to method environment
795
+ assign_args method_exp, args, meth_env
796
+
797
+
798
+ #Find return values if method does not depend on environment/args
799
+ values = @helper_method_cache[method_name]
800
+
801
+ unless values
802
+ #Serialize environment for cache key
803
+ meth_values = meth_env.instance_variable_get(:@env).to_a
804
+ meth_values.sort!
805
+ meth_values = meth_values.to_s
806
+
807
+ digest = Digest::SHA1.new.update(meth_values << method_name.to_s).to_s.to_sym
808
+
809
+ values = @helper_method_cache[digest]
810
+ end
811
+
812
+ if values
813
+ #Use values from cache
814
+ values[:ivar_values].each do |var, val|
815
+ env[var] = val
816
+ end
817
+
818
+ values[:return_value]
819
+ else
820
+ #Find return value for method
821
+ frv = Brakeman::FindReturnValue.new
822
+ value = frv.get_return_value(method_exp.body_list, meth_env)
823
+
824
+ ivars = {}
825
+
826
+ only_ivars(false, meth_env).all.each do |var, val|
827
+ env[var] = val
828
+ ivars[var] = val
829
+ end
830
+
831
+ if not frv.uses_ivars? and args.length == 0
832
+ #Store return value without ivars and args if they are not used
833
+ @helper_method_cache[method_exp.method_name] = { :return_value => value, :ivar_values => ivars }
834
+ else
835
+ @helper_method_cache[digest] = { :return_value => value, :ivar_values => ivars }
836
+ end
837
+
838
+ #Store information about method, just ivar usage for now
839
+ @helper_method_info[method_name] = { :uses_ivars => frv.uses_ivars? }
840
+
841
+ value
842
+ end
843
+ end
844
+
845
+ def assign_args method_exp, args, meth_env = SexpProcessor::Environment.new
846
+ formal_args = method_exp.formal_args
847
+
848
+ formal_args.each_with_index do |arg, index|
849
+ next if index == 0
850
+
851
+ if arg.is_a? Symbol and sexp? args[index - 1]
852
+ meth_env[Sexp.new(:lvar, arg)] = args[index - 1]
853
+ end
854
+ end
855
+
856
+ meth_env
857
+ end
858
+
859
+ #Finds the inner most call target which is not the target of a call to <<
860
+ def find_push_target exp
861
+ if call? exp and exp.method == :<<
862
+ find_push_target exp.target
863
+ else
864
+ exp
865
+ end
866
+ end
867
+
868
+ def duplicate? exp
869
+ @exp_context[0..-2].reverse_each do |e|
870
+ return true if exp == e
871
+ end
872
+
873
+ false
874
+ end
875
+
876
+ def find_method *args
877
+ nil
878
+ end
879
+
880
+ #Return true if lhs == rhs or lhs is an or expression and
881
+ #rhs is one of its values
882
+ def same_value? lhs, rhs
883
+ if lhs == rhs
884
+ true
885
+ elsif node_type? lhs, :or
886
+ lhs.rhs == rhs or lhs.lhs == rhs
887
+ else
888
+ false
889
+ end
890
+ end
891
+
892
+ def self_assign? var, value
893
+ self_assign_var?(var, value) or self_assign_target?(var, value)
894
+ end
895
+
896
+ #Return true if for x += blah or @x += blah
897
+ def self_assign_var? var, value
898
+ call? value and
899
+ value.method == :+ and
900
+ node_type? value.target, :lvar, :ivar and
901
+ value.target.value == var
902
+ end
903
+
904
+ #Return true for x = x.blah
905
+ def self_assign_target? var, value
906
+ target = top_target(value)
907
+
908
+ if node_type? target, :lvar, :ivar
909
+ target = target.value
910
+ end
911
+
912
+ var == target
913
+ end
914
+
915
+ #Returns last non-nil target in a call chain
916
+ def top_target exp, last = nil
917
+ if call? exp
918
+ top_target exp.target, exp
919
+ elsif node_type? exp, :iter
920
+ top_target exp.block_call, last
921
+ else
922
+ exp || last
923
+ end
924
+ end
925
+
926
+ def value_from_if exp
927
+ if block? exp.else_clause or block? exp.then_clause
928
+ #If either clause is more than a single expression, just use entire
929
+ #if expression for now
930
+ exp
931
+ elsif exp.else_clause.nil?
932
+ exp.then_clause
933
+ elsif exp.then_clause.nil?
934
+ exp.else_clause
935
+ else
936
+ condition = exp.condition
937
+
938
+ if true? condition
939
+ exp.then_clause
940
+ elsif false? condition
941
+ exp.else_clause
942
+ else
943
+ exp.then_clause.combine(exp.else_clause, exp.line)
944
+ end
945
+ end
946
+ end
947
+
948
+ #Set variable to given value.
949
+ #Creates "branched" versions of values when appropriate.
950
+ #Avoids creating multiple branched versions inside same
951
+ #if branch.
952
+ def set_value var, value
953
+ if node_type? value, :if
954
+ value = value_from_if(value)
955
+ end
956
+
957
+ if @ignore_ifs or not @inside_if
958
+ if @meth_env and node_type? var, :ivar and env[var].nil?
959
+ @meth_env[var] = value
960
+ else
961
+ env[var] = value
962
+ end
963
+ elsif env.current[var]
964
+ env.current[var] = value
965
+ elsif @branch_env and @branch_env[var]
966
+ @branch_env[var] = value
967
+ elsif @branch_env and @meth_env and node_type? var, :ivar
968
+ @branch_env[var] = value
969
+ else
970
+ env.current[var] = value
971
+ end
972
+ end
973
+
974
+ #If possible, distribute operation over both sides of an or.
975
+ #For example,
976
+ #
977
+ # (1 or 2) * 5
978
+ #
979
+ #Becomes
980
+ #
981
+ # (5 or 10)
982
+ #
983
+ #Only works for strings and numbers right now.
984
+ def process_or_simple_operation exp
985
+ arg = exp.first_arg
986
+ return nil unless string? arg or number? arg
987
+
988
+ target = exp.target
989
+ lhs = process_or_target(target.lhs, exp.dup)
990
+ rhs = process_or_target(target.rhs, exp.dup)
991
+
992
+ if lhs and rhs
993
+ if same_value? lhs, rhs
994
+ lhs
995
+ else
996
+ exp.target.lhs = lhs
997
+ exp.target.rhs = rhs
998
+ exp.target
999
+ end
1000
+ else
1001
+ nil
1002
+ end
1003
+ end
1004
+
1005
+ def process_or_target value, copy
1006
+ if string? value or number? value
1007
+ copy.target = value
1008
+ process copy
1009
+ else
1010
+ false
1011
+ end
1012
+ end
1013
+ end