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.
- 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
|