railroader 4.3.4

Sign up to get free protection for your applications and to get access to all the features.
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