railroader 4.3.4

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 (165) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +1091 -0
  3. data/FEATURES +16 -0
  4. data/README.md +174 -0
  5. data/bin/railroader +8 -0
  6. data/lib/railroader/app_tree.rb +191 -0
  7. data/lib/railroader/call_index.rb +219 -0
  8. data/lib/railroader/checks/base_check.rb +505 -0
  9. data/lib/railroader/checks/check_basic_auth.rb +88 -0
  10. data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
  11. data/lib/railroader/checks/check_content_tag.rb +200 -0
  12. data/lib/railroader/checks/check_create_with.rb +74 -0
  13. data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
  14. data/lib/railroader/checks/check_default_routes.rb +86 -0
  15. data/lib/railroader/checks/check_deserialize.rb +56 -0
  16. data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
  17. data/lib/railroader/checks/check_digest_dos.rb +38 -0
  18. data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
  19. data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
  20. data/lib/railroader/checks/check_escape_function.rb +21 -0
  21. data/lib/railroader/checks/check_evaluation.rb +35 -0
  22. data/lib/railroader/checks/check_execute.rb +189 -0
  23. data/lib/railroader/checks/check_file_access.rb +71 -0
  24. data/lib/railroader/checks/check_file_disclosure.rb +35 -0
  25. data/lib/railroader/checks/check_filter_skipping.rb +31 -0
  26. data/lib/railroader/checks/check_forgery_setting.rb +81 -0
  27. data/lib/railroader/checks/check_header_dos.rb +31 -0
  28. data/lib/railroader/checks/check_i18n_xss.rb +48 -0
  29. data/lib/railroader/checks/check_jruby_xml.rb +36 -0
  30. data/lib/railroader/checks/check_json_encoding.rb +47 -0
  31. data/lib/railroader/checks/check_json_parsing.rb +107 -0
  32. data/lib/railroader/checks/check_link_to.rb +132 -0
  33. data/lib/railroader/checks/check_link_to_href.rb +146 -0
  34. data/lib/railroader/checks/check_mail_to.rb +49 -0
  35. data/lib/railroader/checks/check_mass_assignment.rb +196 -0
  36. data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
  37. data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
  38. data/lib/railroader/checks/check_model_attributes.rb +119 -0
  39. data/lib/railroader/checks/check_model_serialize.rb +67 -0
  40. data/lib/railroader/checks/check_nested_attributes.rb +38 -0
  41. data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
  42. data/lib/railroader/checks/check_number_to_currency.rb +74 -0
  43. data/lib/railroader/checks/check_permit_attributes.rb +43 -0
  44. data/lib/railroader/checks/check_quote_table_name.rb +40 -0
  45. data/lib/railroader/checks/check_redirect.rb +256 -0
  46. data/lib/railroader/checks/check_regex_dos.rb +68 -0
  47. data/lib/railroader/checks/check_render.rb +97 -0
  48. data/lib/railroader/checks/check_render_dos.rb +37 -0
  49. data/lib/railroader/checks/check_render_inline.rb +53 -0
  50. data/lib/railroader/checks/check_response_splitting.rb +21 -0
  51. data/lib/railroader/checks/check_route_dos.rb +42 -0
  52. data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
  53. data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
  54. data/lib/railroader/checks/check_secrets.rb +40 -0
  55. data/lib/railroader/checks/check_select_tag.rb +59 -0
  56. data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
  57. data/lib/railroader/checks/check_send.rb +47 -0
  58. data/lib/railroader/checks/check_send_file.rb +19 -0
  59. data/lib/railroader/checks/check_session_manipulation.rb +35 -0
  60. data/lib/railroader/checks/check_session_settings.rb +176 -0
  61. data/lib/railroader/checks/check_simple_format.rb +58 -0
  62. data/lib/railroader/checks/check_single_quotes.rb +101 -0
  63. data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
  64. data/lib/railroader/checks/check_sql.rb +700 -0
  65. data/lib/railroader/checks/check_sql_cves.rb +106 -0
  66. data/lib/railroader/checks/check_ssl_verify.rb +48 -0
  67. data/lib/railroader/checks/check_strip_tags.rb +89 -0
  68. data/lib/railroader/checks/check_symbol_dos.rb +71 -0
  69. data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
  70. data/lib/railroader/checks/check_translate_bug.rb +45 -0
  71. data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
  72. data/lib/railroader/checks/check_unscoped_find.rb +57 -0
  73. data/lib/railroader/checks/check_validation_regex.rb +116 -0
  74. data/lib/railroader/checks/check_weak_hash.rb +148 -0
  75. data/lib/railroader/checks/check_without_protection.rb +80 -0
  76. data/lib/railroader/checks/check_xml_dos.rb +45 -0
  77. data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
  78. data/lib/railroader/checks.rb +209 -0
  79. data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
  80. data/lib/railroader/commandline.rb +179 -0
  81. data/lib/railroader/differ.rb +66 -0
  82. data/lib/railroader/file_parser.rb +54 -0
  83. data/lib/railroader/format/style.css +133 -0
  84. data/lib/railroader/options.rb +339 -0
  85. data/lib/railroader/parsers/rails2_erubis.rb +6 -0
  86. data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
  87. data/lib/railroader/parsers/rails3_erubis.rb +81 -0
  88. data/lib/railroader/parsers/template_parser.rb +108 -0
  89. data/lib/railroader/processor.rb +102 -0
  90. data/lib/railroader/processors/alias_processor.rb +1229 -0
  91. data/lib/railroader/processors/base_processor.rb +295 -0
  92. data/lib/railroader/processors/config_processor.rb +14 -0
  93. data/lib/railroader/processors/controller_alias_processor.rb +278 -0
  94. data/lib/railroader/processors/controller_processor.rb +249 -0
  95. data/lib/railroader/processors/erb_template_processor.rb +77 -0
  96. data/lib/railroader/processors/erubis_template_processor.rb +92 -0
  97. data/lib/railroader/processors/gem_processor.rb +64 -0
  98. data/lib/railroader/processors/haml_template_processor.rb +191 -0
  99. data/lib/railroader/processors/lib/basic_processor.rb +37 -0
  100. data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
  101. data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
  102. data/lib/railroader/processors/lib/find_call.rb +183 -0
  103. data/lib/railroader/processors/lib/find_return_value.rb +166 -0
  104. data/lib/railroader/processors/lib/module_helper.rb +111 -0
  105. data/lib/railroader/processors/lib/processor_helper.rb +88 -0
  106. data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
  107. data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
  108. data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
  109. data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
  110. data/lib/railroader/processors/lib/render_helper.rb +181 -0
  111. data/lib/railroader/processors/lib/render_path.rb +107 -0
  112. data/lib/railroader/processors/lib/route_helper.rb +68 -0
  113. data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
  114. data/lib/railroader/processors/library_processor.rb +74 -0
  115. data/lib/railroader/processors/model_processor.rb +91 -0
  116. data/lib/railroader/processors/output_processor.rb +144 -0
  117. data/lib/railroader/processors/route_processor.rb +17 -0
  118. data/lib/railroader/processors/slim_template_processor.rb +111 -0
  119. data/lib/railroader/processors/template_alias_processor.rb +118 -0
  120. data/lib/railroader/processors/template_processor.rb +85 -0
  121. data/lib/railroader/report/config/remediation.yml +71 -0
  122. data/lib/railroader/report/ignore/config.rb +153 -0
  123. data/lib/railroader/report/ignore/interactive.rb +362 -0
  124. data/lib/railroader/report/pager.rb +112 -0
  125. data/lib/railroader/report/renderer.rb +24 -0
  126. data/lib/railroader/report/report_base.rb +292 -0
  127. data/lib/railroader/report/report_codeclimate.rb +79 -0
  128. data/lib/railroader/report/report_csv.rb +55 -0
  129. data/lib/railroader/report/report_hash.rb +23 -0
  130. data/lib/railroader/report/report_html.rb +216 -0
  131. data/lib/railroader/report/report_json.rb +45 -0
  132. data/lib/railroader/report/report_markdown.rb +107 -0
  133. data/lib/railroader/report/report_table.rb +117 -0
  134. data/lib/railroader/report/report_tabs.rb +17 -0
  135. data/lib/railroader/report/report_text.rb +198 -0
  136. data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
  137. data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
  138. data/lib/railroader/report/templates/error_overview.html.erb +29 -0
  139. data/lib/railroader/report/templates/header.html.erb +58 -0
  140. data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
  141. data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
  142. data/lib/railroader/report/templates/overview.html.erb +38 -0
  143. data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
  144. data/lib/railroader/report/templates/template_overview.html.erb +21 -0
  145. data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
  146. data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
  147. data/lib/railroader/report.rb +88 -0
  148. data/lib/railroader/rescanner.rb +483 -0
  149. data/lib/railroader/scanner.rb +321 -0
  150. data/lib/railroader/tracker/collection.rb +93 -0
  151. data/lib/railroader/tracker/config.rb +154 -0
  152. data/lib/railroader/tracker/constants.rb +171 -0
  153. data/lib/railroader/tracker/controller.rb +161 -0
  154. data/lib/railroader/tracker/library.rb +17 -0
  155. data/lib/railroader/tracker/model.rb +90 -0
  156. data/lib/railroader/tracker/template.rb +33 -0
  157. data/lib/railroader/tracker.rb +362 -0
  158. data/lib/railroader/util.rb +503 -0
  159. data/lib/railroader/version.rb +3 -0
  160. data/lib/railroader/warning.rb +294 -0
  161. data/lib/railroader/warning_codes.rb +117 -0
  162. data/lib/railroader.rb +544 -0
  163. data/lib/ruby_parser/bm_sexp.rb +626 -0
  164. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  165. metadata +386 -0
@@ -0,0 +1,1229 @@
1
+ require 'railroader/util'
2
+ require 'ruby_parser/bm_sexp_processor'
3
+ require 'railroader/processors/lib/processor_helper'
4
+ require 'railroader/processors/lib/safe_call_helper'
5
+ require 'railroader/processors/lib/call_conversion_helper'
6
+
7
+ #Returns an s-expression with aliases replaced with their value.
8
+ #This does not preserve semantics (due to side effects, etc.), but it makes
9
+ #processing easier when searching for various things.
10
+ class Railroader::AliasProcessor < Railroader::SexpProcessor
11
+ include Railroader::ProcessorHelper
12
+ include Railroader::SafeCallHelper
13
+ include Railroader::Util
14
+ include Railroader::CallConversionHelper
15
+
16
+ attr_reader :result, :tracker
17
+
18
+ #Returns a new AliasProcessor with an empty environment.
19
+ #
20
+ #The recommended usage is:
21
+ #
22
+ # AliasProcessor.new.process_safely src
23
+ def initialize tracker = nil, file_name = nil
24
+ super()
25
+ @env = SexpProcessor::Environment.new
26
+ @inside_if = false
27
+ @ignore_ifs = nil
28
+ @exp_context = []
29
+ @current_module = nil
30
+ @tracker = tracker #set in subclass as necessary
31
+ @helper_method_cache = {}
32
+ @helper_method_info = Hash.new({})
33
+ @or_depth_limit = (tracker && tracker.options[:branch_limit]) || 5 #arbitrary default
34
+ @meth_env = nil
35
+ @file_name = file_name
36
+ set_env_defaults
37
+ end
38
+
39
+ #This method processes the given Sexp, but copies it first so
40
+ #the original argument will not be modified.
41
+ #
42
+ #_set_env_ should be an instance of SexpProcessor::Environment. If provided,
43
+ #it will be used as the starting environment.
44
+ #
45
+ #This method returns a new Sexp with variables replaced with their values,
46
+ #where possible.
47
+ def process_safely src, set_env = nil, file_name = nil
48
+ @file_name = file_name
49
+ @env = set_env || SexpProcessor::Environment.new
50
+ @result = src.deep_clone
51
+ process @result
52
+ @result
53
+ end
54
+
55
+ #Process a Sexp. If the Sexp has a value associated with it in the
56
+ #environment, that value will be returned.
57
+ def process_default exp
58
+ @exp_context.push exp
59
+
60
+ begin
61
+ exp.map! do |e|
62
+ if sexp? e and not e.empty?
63
+ process e
64
+ else
65
+ e
66
+ end
67
+ end
68
+ rescue => err
69
+ @tracker.error err if @tracker
70
+ end
71
+
72
+ result = replace(exp)
73
+
74
+ @exp_context.pop
75
+
76
+ result
77
+ end
78
+
79
+ def replace exp, int = 0
80
+ return exp if int > 3
81
+
82
+
83
+ if replacement = env[exp] and not duplicate? replacement
84
+ replace(replacement.deep_clone(exp.line), int + 1)
85
+ elsif tracker and replacement = tracker.constant_lookup(exp) and not duplicate? replacement
86
+ replace(replacement.deep_clone(exp.line), int + 1)
87
+ else
88
+ exp
89
+ end
90
+ end
91
+
92
+ def process_bracket_call exp
93
+ r = replace(exp)
94
+
95
+ if r != exp
96
+ return r
97
+ end
98
+
99
+ exp.arglist = process_default(exp.arglist)
100
+
101
+ r = replace(exp)
102
+
103
+ if r != exp
104
+ return r
105
+ end
106
+
107
+ t = process(exp.target.deep_clone)
108
+
109
+ # sometimes t[blah] has a match in the env
110
+ # but we don't want to actually set the target
111
+ # in case the target is big...which is what this
112
+ # whole method is trying to avoid
113
+ if t != exp.target
114
+ e = exp.deep_clone
115
+ e.target = t
116
+
117
+ r = replace(e)
118
+
119
+ if r != e
120
+ return r
121
+ end
122
+ else
123
+ t = nil
124
+ end
125
+
126
+ if hash? t
127
+ if v = process_hash_access(t, exp.first_arg)
128
+ v.deep_clone(exp.line)
129
+ else
130
+ case t.node_type
131
+ when :params
132
+ exp.target = PARAMS_SEXP.deep_clone(exp.target.line)
133
+ when :session
134
+ exp.target = SESSION_SEXP.deep_clone(exp.target.line)
135
+ when :cookies
136
+ exp.target = COOKIES_SEXP.deep_clone(exp.target.line)
137
+ end
138
+
139
+ exp
140
+ end
141
+ elsif array? t
142
+ if v = process_array_access(t, exp.args)
143
+ v.deep_clone(exp.line)
144
+ else
145
+ exp
146
+ end
147
+ elsif t
148
+ exp.target = t
149
+ exp
150
+ else
151
+ if exp.target # `self` target is reported as `nil` https://github.com/seattlerb/ruby_parser/issues/250
152
+ exp.target = process_default exp.target
153
+ end
154
+
155
+ exp
156
+ end
157
+ end
158
+
159
+ ARRAY_CONST = s(:const, :Array)
160
+ HASH_CONST = s(:const, :Hash)
161
+ RAILS_TEST = s(:call, s(:call, s(:const, :Rails), :env), :test?)
162
+
163
+ #Process a method call.
164
+ def process_call exp
165
+ return exp if process_call_defn? exp
166
+ target_var = exp.target
167
+ target_var &&= target_var.deep_clone
168
+ if exp.node_type == :safe_call
169
+ exp.node_type = :call
170
+ end
171
+
172
+ if exp.method == :[]
173
+ return process_bracket_call exp
174
+ else
175
+ exp = process_default exp
176
+ end
177
+
178
+ #In case it is replaced with something else
179
+ unless call? exp
180
+ return exp
181
+ end
182
+
183
+ target = exp.target
184
+ method = exp.method
185
+ first_arg = exp.first_arg
186
+
187
+ if method == :send or method == :try
188
+ collapse_send_call exp, first_arg
189
+ end
190
+
191
+ if node_type? target, :or and [:+, :-, :*, :/].include? method
192
+ res = process_or_simple_operation(exp)
193
+ return res if res
194
+ elsif target == ARRAY_CONST and method == :new
195
+ return Sexp.new(:array, *exp.args)
196
+ elsif target == HASH_CONST and method == :new and first_arg.nil? and !node_type?(@exp_context.last, :iter)
197
+ return Sexp.new(:hash)
198
+ elsif exp == RAILS_TEST
199
+ return Sexp.new(:false)
200
+ end
201
+
202
+ #See if it is possible to simplify some basic cases
203
+ #of addition/concatenation.
204
+ case method
205
+ when :+
206
+ if array? target and array? first_arg
207
+ exp = join_arrays(target, first_arg, exp)
208
+ elsif string? first_arg
209
+ exp = join_strings(target, first_arg, exp)
210
+ elsif number? first_arg
211
+ exp = math_op(:+, target, first_arg, exp)
212
+ end
213
+ when :-, :*, :/
214
+ exp = math_op(method, target, first_arg, exp)
215
+ when :[]
216
+ if array? target
217
+ exp = process_array_access(target, exp.args, exp)
218
+ elsif hash? target
219
+ exp = process_hash_access(target, first_arg, exp)
220
+ end
221
+ when :merge!, :update
222
+ if hash? target and hash? first_arg
223
+ target = process_hash_merge! target, first_arg
224
+ env[target_var] = target
225
+ return target
226
+ end
227
+ when :merge
228
+ if hash? target and hash? first_arg
229
+ return process_hash_merge(target, first_arg)
230
+ end
231
+ when :<<
232
+ if string? target and string? first_arg
233
+ target.value << first_arg.value
234
+ env[target_var] = target
235
+ return target
236
+ elsif string? target and string_interp? first_arg
237
+ exp = Sexp.new(:dstr, target.value + first_arg[1]).concat(first_arg[2..-1])
238
+ env[target_var] = exp
239
+ elsif string? first_arg and string_interp? target
240
+ if string? target.last
241
+ target.last.value << first_arg.value
242
+ elsif target.last.is_a? String
243
+ target.last << first_arg.value
244
+ else
245
+ target << first_arg
246
+ end
247
+ env[target_var] = target
248
+ return first_arg
249
+ elsif array? target
250
+ target << first_arg
251
+ env[target_var] = target
252
+ return target
253
+ else
254
+ target = find_push_target(target_var)
255
+ env[target] = exp unless target.nil? # Happens in TemplateAliasProcessor
256
+ end
257
+ when :first
258
+ if array? target and first_arg.nil? and sexp? target[1]
259
+ exp = target[1]
260
+ end
261
+ when :freeze
262
+ unless target.nil?
263
+ exp = target
264
+ end
265
+ when :join
266
+ if array? target and target.length > 2 and (string? first_arg or first_arg.nil?)
267
+ exp = process_array_join(target, first_arg)
268
+ end
269
+ end
270
+
271
+ exp
272
+ end
273
+
274
+ # Painful conversion of Array#join into string interpolation
275
+ def process_array_join array, join_str
276
+ result = s()
277
+
278
+ join_value = if string? join_str
279
+ join_str.value
280
+ else
281
+ nil
282
+ end
283
+
284
+ array[1..-2].each do |e|
285
+ result << join_item(e, join_value)
286
+ end
287
+
288
+ result << join_item(array.last, nil)
289
+
290
+ # Combine the strings at the beginning because that's what RubyParser does
291
+ combined_first = ""
292
+ result.each do |e|
293
+ if string? e
294
+ combined_first << e.value
295
+ elsif e.is_a? String
296
+ combined_first << e
297
+ else
298
+ break
299
+ end
300
+ end
301
+
302
+ # Remove the strings at the beginning
303
+ result.reject! do |e|
304
+ if e.is_a? String or string? e
305
+ true
306
+ else
307
+ break
308
+ end
309
+ end
310
+
311
+ result.unshift combined_first
312
+
313
+ # Have to fix up strings that follow interpolation
314
+ result.reduce(s(:dstr)) do |memo, e|
315
+ if string? e and node_type? memo.last, :evstr
316
+ e.value = "#{join_value}#{e.value}"
317
+ elsif join_value and node_type? memo.last, :evstr and node_type? e, :evstr
318
+ memo << s(:str, join_value)
319
+ end
320
+
321
+ memo << e
322
+ end
323
+ end
324
+
325
+ def join_item item, join_value
326
+ if item.is_a? String
327
+ "#{item}#{join_value}"
328
+ elsif string? item or symbol? item or number? item
329
+ s(:str, "#{item.value}#{join_value}")
330
+ else
331
+ s(:evstr, item)
332
+ end
333
+ end
334
+
335
+ def process_iter exp
336
+ @exp_context.push exp
337
+ exp[1] = process exp.block_call
338
+ if array_detect_all_literals? exp[1]
339
+ return safe_literal(exp.line)
340
+ end
341
+
342
+ @exp_context.pop
343
+
344
+ env.scope do
345
+ call = exp.block_call
346
+ block_args = exp.block_args
347
+
348
+ if call? call and [:each, :map].include? call.method and all_literals? call.target and block_args.length == 2 and block_args.last.is_a? Symbol
349
+ # Iterating over an array of all literal values
350
+ local = Sexp.new(:lvar, block_args.last)
351
+ env.current[local] = safe_literal(exp.line)
352
+ else
353
+ block_args.each do |e|
354
+ #Force block arg(s) to be local
355
+ if node_type? e, :lasgn
356
+ env.current[Sexp.new(:lvar, e.lhs)] = Sexp.new(:lvar, e.lhs)
357
+ elsif node_type? e, :kwarg
358
+ env.current[Sexp.new(:lvar, e[1])] = e[2]
359
+ elsif node_type? e, :masgn, :shadow
360
+ e[1..-1].each do |var|
361
+ local = Sexp.new(:lvar, var)
362
+ env.current[local] = local
363
+ end
364
+ elsif e.is_a? Symbol
365
+ local = Sexp.new(:lvar, e)
366
+ env.current[local] = local
367
+ else
368
+ raise "Unexpected value in block args: #{e.inspect}"
369
+ end
370
+ end
371
+ end
372
+
373
+ block = exp.block
374
+
375
+ if block? block
376
+ process_all! block
377
+ else
378
+ exp[3] = process block
379
+ end
380
+ end
381
+
382
+ exp
383
+ end
384
+
385
+ #Process a new scope.
386
+ def process_scope exp
387
+ env.scope do
388
+ process exp.block
389
+ end
390
+ exp
391
+ end
392
+
393
+ #Start new scope for block.
394
+ def process_block exp
395
+ env.scope do
396
+ process_default exp
397
+ end
398
+ end
399
+
400
+ #Process a method definition.
401
+ def process_defn exp
402
+ meth_env do
403
+ exp.body = process_all! exp.body
404
+ end
405
+ exp
406
+ end
407
+
408
+ def meth_env
409
+ begin
410
+ env.scope do
411
+ set_env_defaults
412
+ @meth_env = env.current
413
+ yield
414
+ end
415
+ ensure
416
+ @meth_env = nil
417
+ end
418
+ end
419
+
420
+ #Process a method definition on self.
421
+ def process_defs exp
422
+ env.scope do
423
+ set_env_defaults
424
+ exp.body = process_all! exp.body
425
+ end
426
+ exp
427
+ end
428
+
429
+ # Handles x = y = z = 1
430
+ def get_rhs exp
431
+ if node_type? exp, :lasgn, :iasgn, :gasgn, :attrasgn, :safe_attrasgn, :cvdecl, :cdecl
432
+ get_rhs(exp.rhs)
433
+ else
434
+ exp
435
+ end
436
+ end
437
+
438
+ #Local assignment
439
+ # x = 1
440
+ def process_lasgn exp
441
+ self_assign = self_assign?(exp.lhs, exp.rhs)
442
+ exp.rhs = process exp.rhs if sexp? exp.rhs
443
+ return exp if exp.rhs.nil?
444
+
445
+ local = Sexp.new(:lvar, exp.lhs).line(exp.line || -2)
446
+
447
+ if self_assign
448
+ # Skip branching
449
+ env[local] = get_rhs(exp)
450
+ else
451
+ set_value local, get_rhs(exp)
452
+ end
453
+
454
+ exp
455
+ end
456
+
457
+ #Instance variable assignment
458
+ # @x = 1
459
+ def process_iasgn exp
460
+ self_assign = self_assign?(exp.lhs, exp.rhs)
461
+ exp.rhs = process exp.rhs
462
+ ivar = Sexp.new(:ivar, exp.lhs).line(exp.line)
463
+
464
+ if self_assign
465
+ if env[ivar].nil? and @meth_env
466
+ @meth_env[ivar] = get_rhs(exp)
467
+ else
468
+ env[ivar] = get_rhs(exp)
469
+ end
470
+ else
471
+ set_value ivar, get_rhs(exp)
472
+ end
473
+
474
+ exp
475
+ end
476
+
477
+ #Global assignment
478
+ # $x = 1
479
+ def process_gasgn exp
480
+ match = Sexp.new(:gvar, exp.lhs)
481
+ exp.rhs = process(exp.rhs)
482
+ value = get_rhs(exp)
483
+
484
+ if value
485
+ value.line = exp.line
486
+
487
+ set_value match, value
488
+ end
489
+
490
+ exp
491
+ end
492
+
493
+ #Class variable assignment
494
+ # @@x = 1
495
+ def process_cvdecl exp
496
+ match = Sexp.new(:cvar, exp.lhs)
497
+ exp.rhs = process(exp.rhs)
498
+ value = get_rhs(exp)
499
+
500
+ set_value match, value
501
+
502
+ exp
503
+ end
504
+
505
+ #'Attribute' assignment
506
+ # x.y = 1
507
+ #or
508
+ # x[:y] = 1
509
+ def process_attrasgn exp
510
+ tar_variable = exp.target
511
+ target = process(exp.target)
512
+ method = exp.method
513
+ index_arg = exp.first_arg
514
+ value_arg = exp.second_arg
515
+
516
+ if method == :[]=
517
+ index = exp.first_arg = process(index_arg)
518
+ value = exp.second_arg = process(value_arg)
519
+ match = Sexp.new(:call, target, :[], index)
520
+
521
+ set_value match, value
522
+
523
+ if hash? target
524
+ env[tar_variable] = hash_insert target.deep_clone, index, value
525
+ end
526
+
527
+ unless node_type? target, :hash
528
+ exp.target = target
529
+ end
530
+ elsif method.to_s[-1,1] == "="
531
+ exp.first_arg = process(index_arg)
532
+ value = get_rhs(exp)
533
+ #This is what we'll replace with the value
534
+ match = Sexp.new(:call, target, method.to_s[0..-2].to_sym)
535
+
536
+ set_value match, value
537
+ exp.target = target
538
+ else
539
+ raise "Unrecognized assignment: #{exp}"
540
+ end
541
+ exp
542
+ end
543
+
544
+ # Multiple/parallel assignment:
545
+ #
546
+ # x, y = z, w
547
+ def process_masgn exp
548
+ exp[2] = process exp[2] if sexp? exp[2]
549
+
550
+ if node_type? exp[2], :to_ary and array? exp[2][1]
551
+ exp[2] = exp[2][1]
552
+ end
553
+
554
+ unless array? exp[1] and array? exp[2] and exp[1].length == exp[2].length
555
+ return process_default(exp)
556
+ end
557
+
558
+ vars = exp[1].dup
559
+ vals = exp[2].dup
560
+
561
+ vars.shift
562
+ vals.shift
563
+
564
+ # Call each assignment as if it is normal
565
+ vars.each_with_index do |var, i|
566
+ val = vals[i]
567
+ if val
568
+
569
+ # This happens with nested destructuring like
570
+ # x, (a, b) = blah
571
+ if node_type? var, :masgn
572
+ # Need to add value to masgn exp
573
+ m = var.dup
574
+ m[2] = s(:to_ary, val)
575
+
576
+ process_masgn m
577
+ else
578
+ assign = var.dup
579
+ assign.rhs = val
580
+ process assign
581
+ end
582
+ end
583
+ end
584
+
585
+ exp
586
+ end
587
+
588
+ #Merge values into hash when processing
589
+ #
590
+ # h.merge! :something => "value"
591
+ def process_hash_merge! hash, args
592
+ hash = hash.deep_clone
593
+ hash_iterate args do |key, replacement|
594
+ hash_insert hash, key, replacement
595
+ match = Sexp.new(:call, hash, :[], key)
596
+ env[match] = replacement
597
+ end
598
+ hash
599
+ end
600
+
601
+ #Return a new hash Sexp with the given values merged into it.
602
+ #
603
+ #+args+ should be a hash Sexp as well.
604
+ def process_hash_merge hash, args
605
+ hash = hash.deep_clone
606
+ hash_iterate args do |key, replacement|
607
+ hash_insert hash, key, replacement
608
+ end
609
+ hash
610
+ end
611
+
612
+ #Assignments like this
613
+ # x[:y] ||= 1
614
+ def process_op_asgn1 exp
615
+ target_var = exp[1]
616
+ target_var &&= target_var.deep_clone
617
+
618
+ target = exp[1] = process(exp[1])
619
+ index = exp[2][1] = process(exp[2][1])
620
+ value = exp[4] = process(exp[4])
621
+ match = Sexp.new(:call, target, :[], index)
622
+
623
+ if exp[3] == :"||"
624
+ unless env[match]
625
+ if request_value? target
626
+ env[match] = match.combine(value)
627
+ else
628
+ env[match] = value
629
+ end
630
+ end
631
+ else
632
+ new_value = process s(:call, s(:call, target_var, :[], index), exp[3], value)
633
+
634
+ env[match] = new_value
635
+ end
636
+
637
+ exp
638
+ end
639
+
640
+ #Assignments like this
641
+ # x.y ||= 1
642
+ def process_op_asgn2 exp
643
+ return process_default(exp) if exp[3] != :"||"
644
+
645
+ target = exp[1] = process(exp[1])
646
+ value = exp[4] = process(exp[4])
647
+ method = exp[2]
648
+
649
+ match = Sexp.new(:call, target, method.to_s[0..-2].to_sym)
650
+
651
+ unless env[match]
652
+ env[match] = value
653
+ end
654
+
655
+ exp
656
+ end
657
+
658
+ #This is the right hand side value of a multiple assignment,
659
+ #like `x = y, z`
660
+ def process_svalue exp
661
+ exp.value
662
+ end
663
+
664
+ #Constant assignments like
665
+ # BIG_CONSTANT = 234810983
666
+ def process_cdecl exp
667
+ if sexp? exp.rhs
668
+ exp.rhs = process exp.rhs
669
+ end
670
+
671
+ if @tracker
672
+ @tracker.add_constant exp.lhs,
673
+ exp.rhs,
674
+ :file => current_file_name,
675
+ :module => @current_module,
676
+ :class => @current_class,
677
+ :method => @current_method
678
+ end
679
+
680
+ if exp.lhs.is_a? Symbol
681
+ match = Sexp.new(:const, exp.lhs)
682
+ else
683
+ match = exp.lhs
684
+ end
685
+
686
+ env[match] = get_rhs(exp)
687
+
688
+ exp
689
+ end
690
+
691
+ # Check if exp is a call to Array#include? on an array literal
692
+ # that contains all literal values. For example:
693
+ #
694
+ # [1, 2, "a"].include? x
695
+ #
696
+ def array_include_all_literals? exp
697
+ call? exp and
698
+ exp.method == :include? and
699
+ all_literals? exp.target
700
+ end
701
+
702
+ def array_detect_all_literals? exp
703
+ call? exp and
704
+ [:detect, :find].include? exp.method and
705
+ exp.first_arg.nil? and
706
+ all_literals? exp.target
707
+ end
708
+
709
+ #Sets @inside_if = true
710
+ def process_if exp
711
+ if @ignore_ifs.nil?
712
+ @ignore_ifs = @tracker && @tracker.options[:ignore_ifs]
713
+ end
714
+
715
+ condition = exp.condition = process exp.condition
716
+
717
+ #Check if a branch is obviously going to be taken
718
+ if true? condition
719
+ no_branch = true
720
+ exps = [exp.then_clause, nil]
721
+ elsif false? condition
722
+ no_branch = true
723
+ exps = [nil, exp.else_clause]
724
+ else
725
+ no_branch = false
726
+ exps = [exp.then_clause, exp.else_clause]
727
+ end
728
+
729
+ if @ignore_ifs or no_branch
730
+ exps.each_with_index do |branch, i|
731
+ exp[2 + i] = process_if_branch branch
732
+ end
733
+ else
734
+ was_inside = @inside_if
735
+ @inside_if = true
736
+
737
+ branch_scopes = []
738
+ exps.each_with_index do |branch, i|
739
+ scope do
740
+ @branch_env = env.current
741
+ branch_index = 2 + i # s(:if, condition, then_branch, else_branch)
742
+ if i == 0 and array_include_all_literals? condition
743
+ # If the condition is ["a", "b"].include? x
744
+ # set x to "a" inside the true branch
745
+ var = condition.first_arg
746
+ previous_value = env.current[var]
747
+ env.current[var] = safe_literal(var.line)
748
+ exp[branch_index] = process_if_branch branch
749
+ env.current[var] = previous_value
750
+ elsif i == 1 and array_include_all_literals? condition and early_return? branch
751
+ var = condition.first_arg
752
+ env.current[var] = safe_literal(var.line)
753
+ exp[branch_index] = process_if_branch branch
754
+ else
755
+ exp[branch_index] = process_if_branch branch
756
+ end
757
+ branch_scopes << env.current
758
+ @branch_env = nil
759
+ end
760
+ end
761
+
762
+ @inside_if = was_inside
763
+
764
+ branch_scopes.each do |s|
765
+ merge_if_branch s
766
+ end
767
+ end
768
+
769
+ exp
770
+ end
771
+
772
+ def early_return? exp
773
+ return true if node_type? exp, :return
774
+ return true if call? exp and [:fail, :raise].include? exp.method
775
+
776
+ if node_type? exp, :block, :rlist
777
+ node_type? exp.last, :return or
778
+ (call? exp and [:fail, :raise].include? exp.method)
779
+ else
780
+ false
781
+ end
782
+ end
783
+
784
+ def simple_when? exp
785
+ node_type? exp[1], :array and
786
+ not node_type? exp[1][1], :splat, :array and
787
+ (exp[1].length == 2 or
788
+ exp[1].all? { |e| e.is_a? Symbol or node_type? e, :lit, :str })
789
+ end
790
+
791
+ def process_case exp
792
+ if @ignore_ifs.nil?
793
+ @ignore_ifs = @tracker && @tracker.options[:ignore_ifs]
794
+ end
795
+
796
+ if @ignore_ifs
797
+ process_default exp
798
+ return exp
799
+ end
800
+
801
+ branch_scopes = []
802
+ was_inside = @inside_if
803
+ @inside_if = true
804
+
805
+ exp[1] = process exp[1] if exp[1]
806
+
807
+ case_value = if node_type? exp[1], :lvar, :ivar, :call
808
+ exp[1].deep_clone
809
+ end
810
+
811
+ exp.each_sexp do |e|
812
+ if node_type? e, :when
813
+ scope do
814
+ @branch_env = env.current
815
+
816
+ # set value of case var if possible
817
+ if case_value and simple_when? e
818
+ @branch_env[case_value] = e[1][1]
819
+ end
820
+
821
+ # when blocks aren't blocks, they are lists of expressions
822
+ process_default e
823
+
824
+ branch_scopes << env.current
825
+
826
+ @branch_env = nil
827
+ end
828
+ end
829
+ end
830
+
831
+ # else clause
832
+ if sexp? exp.last
833
+ scope do
834
+ @branch_env = env.current
835
+
836
+ process_default exp[-1]
837
+
838
+ branch_scopes << env.current
839
+
840
+ @branch_env = nil
841
+ end
842
+ end
843
+
844
+ @inside_if = was_inside
845
+
846
+ branch_scopes.each do |s|
847
+ merge_if_branch s
848
+ end
849
+
850
+ exp
851
+ end
852
+
853
+ def process_if_branch exp
854
+ if sexp? exp
855
+ if block? exp
856
+ process_default exp
857
+ else
858
+ process exp
859
+ end
860
+ end
861
+ end
862
+
863
+ def merge_if_branch branch_env
864
+ branch_env.each do |k, v|
865
+ next if v.nil?
866
+
867
+ current_val = env[k]
868
+
869
+ if current_val
870
+ unless same_value?(current_val, v)
871
+ if too_deep? current_val
872
+ # Give up branching, start over with latest value
873
+ env[k] = v
874
+ else
875
+ env[k] = current_val.combine(v, k.line)
876
+ end
877
+ end
878
+ else
879
+ env[k] = v
880
+ end
881
+ end
882
+ end
883
+
884
+ def too_deep? exp
885
+ @or_depth_limit >= 0 and
886
+ node_type? exp, :or and
887
+ exp.or_depth and
888
+ exp.or_depth >= @or_depth_limit
889
+ end
890
+
891
+ # Change x.send(:y, 1) to x.y(1)
892
+ def collapse_send_call exp, first_arg
893
+ # Handle try(&:id)
894
+ if node_type? first_arg, :block_pass
895
+ first_arg = first_arg[1]
896
+ end
897
+
898
+ return unless symbol? first_arg or string? first_arg
899
+ exp.method = first_arg.value.to_sym
900
+ args = exp.args
901
+ exp.pop # remove last arg
902
+ if args.length > 1
903
+ exp.arglist = args[1..-1]
904
+ end
905
+ end
906
+
907
+ #Returns a new SexpProcessor::Environment containing only instance variables.
908
+ #This is useful, for example, when processing views.
909
+ def only_ivars include_request_vars = false, lenv = nil
910
+ lenv ||= env
911
+ res = SexpProcessor::Environment.new
912
+
913
+ if include_request_vars
914
+ lenv.all.each do |k, v|
915
+ #TODO Why would this have nil values?
916
+ if (k.node_type == :ivar or request_value? k) and not v.nil?
917
+ res[k] = v.dup
918
+ end
919
+ end
920
+ else
921
+ lenv.all.each do |k, v|
922
+ #TODO Why would this have nil values?
923
+ if k.node_type == :ivar and not v.nil?
924
+ res[k] = v.dup
925
+ end
926
+ end
927
+ end
928
+
929
+ res
930
+ end
931
+
932
+ def only_request_vars
933
+ res = SexpProcessor::Environment.new
934
+
935
+ env.all.each do |k, v|
936
+ if request_value? k and not v.nil?
937
+ res[k] = v.dup
938
+ end
939
+ end
940
+
941
+ res
942
+ end
943
+
944
+ def get_call_value call
945
+ method_name = call.method
946
+
947
+ #Look for helper methods and see if we can get a return value
948
+ if found_method = find_method(method_name, @current_class)
949
+ helper = found_method[:method]
950
+
951
+ if sexp? helper
952
+ value = process_helper_method helper, call.args
953
+ value.line(call.line)
954
+ return value
955
+ else
956
+ raise "Unexpected value for method: #{found_method}"
957
+ end
958
+ else
959
+ call
960
+ end
961
+ end
962
+
963
+ def process_helper_method method_exp, args
964
+ method_name = method_exp.method_name
965
+ Railroader.debug "Processing method #{method_name}"
966
+
967
+ info = @helper_method_info[method_name]
968
+
969
+ #If method uses instance variables, then include those and request
970
+ #variables (params, etc) in the method environment. Otherwise,
971
+ #only include request variables.
972
+ if info[:uses_ivars]
973
+ meth_env = only_ivars(:include_request_vars)
974
+ else
975
+ meth_env = only_request_vars
976
+ end
977
+
978
+ #Add arguments to method environment
979
+ assign_args method_exp, args, meth_env
980
+
981
+
982
+ #Find return values if method does not depend on environment/args
983
+ values = @helper_method_cache[method_name]
984
+
985
+ unless values
986
+ #Serialize environment for cache key
987
+ meth_values = meth_env.instance_variable_get(:@env).to_a
988
+ meth_values.sort!
989
+ meth_values = meth_values.to_s
990
+
991
+ digest = Digest::SHA1.new.update(meth_values << method_name.to_s).to_s.to_sym
992
+
993
+ values = @helper_method_cache[digest]
994
+ end
995
+
996
+ if values
997
+ #Use values from cache
998
+ values[:ivar_values].each do |var, val|
999
+ env[var] = val
1000
+ end
1001
+
1002
+ values[:return_value]
1003
+ else
1004
+ #Find return value for method
1005
+ frv = Railroader::FindReturnValue.new
1006
+ value = frv.get_return_value(method_exp.body_list, meth_env)
1007
+
1008
+ ivars = {}
1009
+
1010
+ only_ivars(false, meth_env).all.each do |var, val|
1011
+ env[var] = val
1012
+ ivars[var] = val
1013
+ end
1014
+
1015
+ if not frv.uses_ivars? and args.length == 0
1016
+ #Store return value without ivars and args if they are not used
1017
+ @helper_method_cache[method_exp.method_name] = { :return_value => value, :ivar_values => ivars }
1018
+ else
1019
+ @helper_method_cache[digest] = { :return_value => value, :ivar_values => ivars }
1020
+ end
1021
+
1022
+ #Store information about method, just ivar usage for now
1023
+ @helper_method_info[method_name] = { :uses_ivars => frv.uses_ivars? }
1024
+
1025
+ value
1026
+ end
1027
+ end
1028
+
1029
+ def assign_args method_exp, args, meth_env = SexpProcessor::Environment.new
1030
+ formal_args = method_exp.formal_args
1031
+
1032
+ formal_args.each_with_index do |arg, index|
1033
+ next if index == 0
1034
+
1035
+ if arg.is_a? Symbol and sexp? args[index - 1]
1036
+ meth_env[Sexp.new(:lvar, arg)] = args[index - 1]
1037
+ end
1038
+ end
1039
+
1040
+ meth_env
1041
+ end
1042
+
1043
+ #Finds the inner most call target which is not the target of a call to <<
1044
+ def find_push_target exp
1045
+ if call? exp and exp.method == :<<
1046
+ find_push_target exp.target
1047
+ else
1048
+ exp
1049
+ end
1050
+ end
1051
+
1052
+ def duplicate? exp
1053
+ @exp_context[0..-2].reverse_each do |e|
1054
+ return true if exp == e
1055
+ end
1056
+
1057
+ false
1058
+ end
1059
+
1060
+ def find_method *args
1061
+ nil
1062
+ end
1063
+
1064
+ #Return true if lhs == rhs or lhs is an or expression and
1065
+ #rhs is one of its values
1066
+ def same_value? lhs, rhs
1067
+ if lhs == rhs
1068
+ true
1069
+ elsif node_type? lhs, :or
1070
+ lhs.rhs == rhs or lhs.lhs == rhs
1071
+ else
1072
+ false
1073
+ end
1074
+ end
1075
+
1076
+ def self_assign? var, value
1077
+ self_assign_var?(var, value) or self_assign_target?(var, value)
1078
+ end
1079
+
1080
+ #Return true if for x += blah or @x += blah
1081
+ def self_assign_var? var, value
1082
+ call? value and
1083
+ value.method == :+ and
1084
+ node_type? value.target, :lvar, :ivar and
1085
+ value.target.value == var
1086
+ end
1087
+
1088
+ #Return true for x = x.blah
1089
+ def self_assign_target? var, value
1090
+ target = top_target(value)
1091
+
1092
+ if node_type? target, :lvar, :ivar
1093
+ target = target.value
1094
+ end
1095
+
1096
+ var == target
1097
+ end
1098
+
1099
+ #Returns last non-nil target in a call chain
1100
+ def top_target exp, last = nil
1101
+ if call? exp
1102
+ top_target exp.target, exp
1103
+ elsif node_type? exp, :iter
1104
+ top_target exp.block_call, last
1105
+ else
1106
+ exp || last
1107
+ end
1108
+ end
1109
+
1110
+ def value_from_if exp
1111
+ if block? exp.else_clause or block? exp.then_clause
1112
+ #If either clause is more than a single expression, just use entire
1113
+ #if expression for now
1114
+ exp
1115
+ elsif exp.else_clause.nil?
1116
+ exp.then_clause
1117
+ elsif exp.then_clause.nil?
1118
+ exp.else_clause
1119
+ else
1120
+ condition = exp.condition
1121
+
1122
+ if true? condition
1123
+ exp.then_clause
1124
+ elsif false? condition
1125
+ exp.else_clause
1126
+ else
1127
+ exp.then_clause.combine(exp.else_clause, exp.line)
1128
+ end
1129
+ end
1130
+ end
1131
+
1132
+ def value_from_case exp
1133
+ result = []
1134
+
1135
+ exp.each do |e|
1136
+ if node_type? e, :when
1137
+ result << e.last
1138
+ end
1139
+ end
1140
+
1141
+ result << exp.last if exp.last # else
1142
+
1143
+ result.reduce do |c, e|
1144
+ if c.nil?
1145
+ e
1146
+ elsif node_type? e, :if
1147
+ c.combine(value_from_if e)
1148
+ elsif raise? e
1149
+ c # ignore exceptions
1150
+ elsif e
1151
+ c.combine e
1152
+ else # when e is nil
1153
+ c
1154
+ end
1155
+ end
1156
+ end
1157
+
1158
+ def raise? exp
1159
+ call? exp and exp.method == :raise
1160
+ end
1161
+
1162
+ #Set variable to given value.
1163
+ #Creates "branched" versions of values when appropriate.
1164
+ #Avoids creating multiple branched versions inside same
1165
+ #if branch.
1166
+ def set_value var, value
1167
+ if node_type? value, :if
1168
+ value = value_from_if(value)
1169
+ elsif node_type? value, :case
1170
+ value = value_from_case(value)
1171
+ end
1172
+
1173
+ if @ignore_ifs or not @inside_if
1174
+ if @meth_env and node_type? var, :ivar and env[var].nil?
1175
+ @meth_env[var] = value
1176
+ else
1177
+ env[var] = value
1178
+ end
1179
+ elsif env.current[var]
1180
+ env.current[var] = value
1181
+ elsif @branch_env and @branch_env[var]
1182
+ @branch_env[var] = value
1183
+ elsif @branch_env and @meth_env and node_type? var, :ivar
1184
+ @branch_env[var] = value
1185
+ else
1186
+ env.current[var] = value
1187
+ end
1188
+ end
1189
+
1190
+ #If possible, distribute operation over both sides of an or.
1191
+ #For example,
1192
+ #
1193
+ # (1 or 2) * 5
1194
+ #
1195
+ #Becomes
1196
+ #
1197
+ # (5 or 10)
1198
+ #
1199
+ #Only works for strings and numbers right now.
1200
+ def process_or_simple_operation exp
1201
+ arg = exp.first_arg
1202
+ return nil unless string? arg or number? arg
1203
+
1204
+ target = exp.target
1205
+ lhs = process_or_target(target.lhs, exp.dup)
1206
+ rhs = process_or_target(target.rhs, exp.dup)
1207
+
1208
+ if lhs and rhs
1209
+ if same_value? lhs, rhs
1210
+ lhs
1211
+ else
1212
+ exp.target.lhs = lhs
1213
+ exp.target.rhs = rhs
1214
+ exp.target
1215
+ end
1216
+ else
1217
+ nil
1218
+ end
1219
+ end
1220
+
1221
+ def process_or_target value, copy
1222
+ if string? value or number? value
1223
+ copy.target = value
1224
+ process copy
1225
+ else
1226
+ false
1227
+ end
1228
+ end
1229
+ end