railroader 4.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES.md +1091 -0
- data/FEATURES +16 -0
- data/README.md +174 -0
- data/bin/railroader +8 -0
- data/lib/railroader/app_tree.rb +191 -0
- data/lib/railroader/call_index.rb +219 -0
- data/lib/railroader/checks/base_check.rb +505 -0
- data/lib/railroader/checks/check_basic_auth.rb +88 -0
- data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
- data/lib/railroader/checks/check_content_tag.rb +200 -0
- data/lib/railroader/checks/check_create_with.rb +74 -0
- data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
- data/lib/railroader/checks/check_default_routes.rb +86 -0
- data/lib/railroader/checks/check_deserialize.rb +56 -0
- data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
- data/lib/railroader/checks/check_digest_dos.rb +38 -0
- data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
- data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
- data/lib/railroader/checks/check_escape_function.rb +21 -0
- data/lib/railroader/checks/check_evaluation.rb +35 -0
- data/lib/railroader/checks/check_execute.rb +189 -0
- data/lib/railroader/checks/check_file_access.rb +71 -0
- data/lib/railroader/checks/check_file_disclosure.rb +35 -0
- data/lib/railroader/checks/check_filter_skipping.rb +31 -0
- data/lib/railroader/checks/check_forgery_setting.rb +81 -0
- data/lib/railroader/checks/check_header_dos.rb +31 -0
- data/lib/railroader/checks/check_i18n_xss.rb +48 -0
- data/lib/railroader/checks/check_jruby_xml.rb +36 -0
- data/lib/railroader/checks/check_json_encoding.rb +47 -0
- data/lib/railroader/checks/check_json_parsing.rb +107 -0
- data/lib/railroader/checks/check_link_to.rb +132 -0
- data/lib/railroader/checks/check_link_to_href.rb +146 -0
- data/lib/railroader/checks/check_mail_to.rb +49 -0
- data/lib/railroader/checks/check_mass_assignment.rb +196 -0
- data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
- data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
- data/lib/railroader/checks/check_model_attributes.rb +119 -0
- data/lib/railroader/checks/check_model_serialize.rb +67 -0
- data/lib/railroader/checks/check_nested_attributes.rb +38 -0
- data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
- data/lib/railroader/checks/check_number_to_currency.rb +74 -0
- data/lib/railroader/checks/check_permit_attributes.rb +43 -0
- data/lib/railroader/checks/check_quote_table_name.rb +40 -0
- data/lib/railroader/checks/check_redirect.rb +256 -0
- data/lib/railroader/checks/check_regex_dos.rb +68 -0
- data/lib/railroader/checks/check_render.rb +97 -0
- data/lib/railroader/checks/check_render_dos.rb +37 -0
- data/lib/railroader/checks/check_render_inline.rb +53 -0
- data/lib/railroader/checks/check_response_splitting.rb +21 -0
- data/lib/railroader/checks/check_route_dos.rb +42 -0
- data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
- data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
- data/lib/railroader/checks/check_secrets.rb +40 -0
- data/lib/railroader/checks/check_select_tag.rb +59 -0
- data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
- data/lib/railroader/checks/check_send.rb +47 -0
- data/lib/railroader/checks/check_send_file.rb +19 -0
- data/lib/railroader/checks/check_session_manipulation.rb +35 -0
- data/lib/railroader/checks/check_session_settings.rb +176 -0
- data/lib/railroader/checks/check_simple_format.rb +58 -0
- data/lib/railroader/checks/check_single_quotes.rb +101 -0
- data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
- data/lib/railroader/checks/check_sql.rb +700 -0
- data/lib/railroader/checks/check_sql_cves.rb +106 -0
- data/lib/railroader/checks/check_ssl_verify.rb +48 -0
- data/lib/railroader/checks/check_strip_tags.rb +89 -0
- data/lib/railroader/checks/check_symbol_dos.rb +71 -0
- data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
- data/lib/railroader/checks/check_translate_bug.rb +45 -0
- data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
- data/lib/railroader/checks/check_unscoped_find.rb +57 -0
- data/lib/railroader/checks/check_validation_regex.rb +116 -0
- data/lib/railroader/checks/check_weak_hash.rb +148 -0
- data/lib/railroader/checks/check_without_protection.rb +80 -0
- data/lib/railroader/checks/check_xml_dos.rb +45 -0
- data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
- data/lib/railroader/checks.rb +209 -0
- data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
- data/lib/railroader/commandline.rb +179 -0
- data/lib/railroader/differ.rb +66 -0
- data/lib/railroader/file_parser.rb +54 -0
- data/lib/railroader/format/style.css +133 -0
- data/lib/railroader/options.rb +339 -0
- data/lib/railroader/parsers/rails2_erubis.rb +6 -0
- data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
- data/lib/railroader/parsers/rails3_erubis.rb +81 -0
- data/lib/railroader/parsers/template_parser.rb +108 -0
- data/lib/railroader/processor.rb +102 -0
- data/lib/railroader/processors/alias_processor.rb +1229 -0
- data/lib/railroader/processors/base_processor.rb +295 -0
- data/lib/railroader/processors/config_processor.rb +14 -0
- data/lib/railroader/processors/controller_alias_processor.rb +278 -0
- data/lib/railroader/processors/controller_processor.rb +249 -0
- data/lib/railroader/processors/erb_template_processor.rb +77 -0
- data/lib/railroader/processors/erubis_template_processor.rb +92 -0
- data/lib/railroader/processors/gem_processor.rb +64 -0
- data/lib/railroader/processors/haml_template_processor.rb +191 -0
- data/lib/railroader/processors/lib/basic_processor.rb +37 -0
- data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
- data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
- data/lib/railroader/processors/lib/find_call.rb +183 -0
- data/lib/railroader/processors/lib/find_return_value.rb +166 -0
- data/lib/railroader/processors/lib/module_helper.rb +111 -0
- data/lib/railroader/processors/lib/processor_helper.rb +88 -0
- data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
- data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
- data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
- data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
- data/lib/railroader/processors/lib/render_helper.rb +181 -0
- data/lib/railroader/processors/lib/render_path.rb +107 -0
- data/lib/railroader/processors/lib/route_helper.rb +68 -0
- data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
- data/lib/railroader/processors/library_processor.rb +74 -0
- data/lib/railroader/processors/model_processor.rb +91 -0
- data/lib/railroader/processors/output_processor.rb +144 -0
- data/lib/railroader/processors/route_processor.rb +17 -0
- data/lib/railroader/processors/slim_template_processor.rb +111 -0
- data/lib/railroader/processors/template_alias_processor.rb +118 -0
- data/lib/railroader/processors/template_processor.rb +85 -0
- data/lib/railroader/report/config/remediation.yml +71 -0
- data/lib/railroader/report/ignore/config.rb +153 -0
- data/lib/railroader/report/ignore/interactive.rb +362 -0
- data/lib/railroader/report/pager.rb +112 -0
- data/lib/railroader/report/renderer.rb +24 -0
- data/lib/railroader/report/report_base.rb +292 -0
- data/lib/railroader/report/report_codeclimate.rb +79 -0
- data/lib/railroader/report/report_csv.rb +55 -0
- data/lib/railroader/report/report_hash.rb +23 -0
- data/lib/railroader/report/report_html.rb +216 -0
- data/lib/railroader/report/report_json.rb +45 -0
- data/lib/railroader/report/report_markdown.rb +107 -0
- data/lib/railroader/report/report_table.rb +117 -0
- data/lib/railroader/report/report_tabs.rb +17 -0
- data/lib/railroader/report/report_text.rb +198 -0
- data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
- data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
- data/lib/railroader/report/templates/error_overview.html.erb +29 -0
- data/lib/railroader/report/templates/header.html.erb +58 -0
- data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
- data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
- data/lib/railroader/report/templates/overview.html.erb +38 -0
- data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
- data/lib/railroader/report/templates/template_overview.html.erb +21 -0
- data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
- data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
- data/lib/railroader/report.rb +88 -0
- data/lib/railroader/rescanner.rb +483 -0
- data/lib/railroader/scanner.rb +321 -0
- data/lib/railroader/tracker/collection.rb +93 -0
- data/lib/railroader/tracker/config.rb +154 -0
- data/lib/railroader/tracker/constants.rb +171 -0
- data/lib/railroader/tracker/controller.rb +161 -0
- data/lib/railroader/tracker/library.rb +17 -0
- data/lib/railroader/tracker/model.rb +90 -0
- data/lib/railroader/tracker/template.rb +33 -0
- data/lib/railroader/tracker.rb +362 -0
- data/lib/railroader/util.rb +503 -0
- data/lib/railroader/version.rb +3 -0
- data/lib/railroader/warning.rb +294 -0
- data/lib/railroader/warning_codes.rb +117 -0
- data/lib/railroader.rb +544 -0
- data/lib/ruby_parser/bm_sexp.rb +626 -0
- data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
- 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
|