railroader 4.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +1091 -0
  3. data/FEATURES +16 -0
  4. data/README.md +174 -0
  5. data/bin/railroader +8 -0
  6. data/lib/railroader/app_tree.rb +191 -0
  7. data/lib/railroader/call_index.rb +219 -0
  8. data/lib/railroader/checks/base_check.rb +505 -0
  9. data/lib/railroader/checks/check_basic_auth.rb +88 -0
  10. data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
  11. data/lib/railroader/checks/check_content_tag.rb +200 -0
  12. data/lib/railroader/checks/check_create_with.rb +74 -0
  13. data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
  14. data/lib/railroader/checks/check_default_routes.rb +86 -0
  15. data/lib/railroader/checks/check_deserialize.rb +56 -0
  16. data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
  17. data/lib/railroader/checks/check_digest_dos.rb +38 -0
  18. data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
  19. data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
  20. data/lib/railroader/checks/check_escape_function.rb +21 -0
  21. data/lib/railroader/checks/check_evaluation.rb +35 -0
  22. data/lib/railroader/checks/check_execute.rb +189 -0
  23. data/lib/railroader/checks/check_file_access.rb +71 -0
  24. data/lib/railroader/checks/check_file_disclosure.rb +35 -0
  25. data/lib/railroader/checks/check_filter_skipping.rb +31 -0
  26. data/lib/railroader/checks/check_forgery_setting.rb +81 -0
  27. data/lib/railroader/checks/check_header_dos.rb +31 -0
  28. data/lib/railroader/checks/check_i18n_xss.rb +48 -0
  29. data/lib/railroader/checks/check_jruby_xml.rb +36 -0
  30. data/lib/railroader/checks/check_json_encoding.rb +47 -0
  31. data/lib/railroader/checks/check_json_parsing.rb +107 -0
  32. data/lib/railroader/checks/check_link_to.rb +132 -0
  33. data/lib/railroader/checks/check_link_to_href.rb +146 -0
  34. data/lib/railroader/checks/check_mail_to.rb +49 -0
  35. data/lib/railroader/checks/check_mass_assignment.rb +196 -0
  36. data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
  37. data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
  38. data/lib/railroader/checks/check_model_attributes.rb +119 -0
  39. data/lib/railroader/checks/check_model_serialize.rb +67 -0
  40. data/lib/railroader/checks/check_nested_attributes.rb +38 -0
  41. data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
  42. data/lib/railroader/checks/check_number_to_currency.rb +74 -0
  43. data/lib/railroader/checks/check_permit_attributes.rb +43 -0
  44. data/lib/railroader/checks/check_quote_table_name.rb +40 -0
  45. data/lib/railroader/checks/check_redirect.rb +256 -0
  46. data/lib/railroader/checks/check_regex_dos.rb +68 -0
  47. data/lib/railroader/checks/check_render.rb +97 -0
  48. data/lib/railroader/checks/check_render_dos.rb +37 -0
  49. data/lib/railroader/checks/check_render_inline.rb +53 -0
  50. data/lib/railroader/checks/check_response_splitting.rb +21 -0
  51. data/lib/railroader/checks/check_route_dos.rb +42 -0
  52. data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
  53. data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
  54. data/lib/railroader/checks/check_secrets.rb +40 -0
  55. data/lib/railroader/checks/check_select_tag.rb +59 -0
  56. data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
  57. data/lib/railroader/checks/check_send.rb +47 -0
  58. data/lib/railroader/checks/check_send_file.rb +19 -0
  59. data/lib/railroader/checks/check_session_manipulation.rb +35 -0
  60. data/lib/railroader/checks/check_session_settings.rb +176 -0
  61. data/lib/railroader/checks/check_simple_format.rb +58 -0
  62. data/lib/railroader/checks/check_single_quotes.rb +101 -0
  63. data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
  64. data/lib/railroader/checks/check_sql.rb +700 -0
  65. data/lib/railroader/checks/check_sql_cves.rb +106 -0
  66. data/lib/railroader/checks/check_ssl_verify.rb +48 -0
  67. data/lib/railroader/checks/check_strip_tags.rb +89 -0
  68. data/lib/railroader/checks/check_symbol_dos.rb +71 -0
  69. data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
  70. data/lib/railroader/checks/check_translate_bug.rb +45 -0
  71. data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
  72. data/lib/railroader/checks/check_unscoped_find.rb +57 -0
  73. data/lib/railroader/checks/check_validation_regex.rb +116 -0
  74. data/lib/railroader/checks/check_weak_hash.rb +148 -0
  75. data/lib/railroader/checks/check_without_protection.rb +80 -0
  76. data/lib/railroader/checks/check_xml_dos.rb +45 -0
  77. data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
  78. data/lib/railroader/checks.rb +209 -0
  79. data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
  80. data/lib/railroader/commandline.rb +179 -0
  81. data/lib/railroader/differ.rb +66 -0
  82. data/lib/railroader/file_parser.rb +54 -0
  83. data/lib/railroader/format/style.css +133 -0
  84. data/lib/railroader/options.rb +339 -0
  85. data/lib/railroader/parsers/rails2_erubis.rb +6 -0
  86. data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
  87. data/lib/railroader/parsers/rails3_erubis.rb +81 -0
  88. data/lib/railroader/parsers/template_parser.rb +108 -0
  89. data/lib/railroader/processor.rb +102 -0
  90. data/lib/railroader/processors/alias_processor.rb +1229 -0
  91. data/lib/railroader/processors/base_processor.rb +295 -0
  92. data/lib/railroader/processors/config_processor.rb +14 -0
  93. data/lib/railroader/processors/controller_alias_processor.rb +278 -0
  94. data/lib/railroader/processors/controller_processor.rb +249 -0
  95. data/lib/railroader/processors/erb_template_processor.rb +77 -0
  96. data/lib/railroader/processors/erubis_template_processor.rb +92 -0
  97. data/lib/railroader/processors/gem_processor.rb +64 -0
  98. data/lib/railroader/processors/haml_template_processor.rb +191 -0
  99. data/lib/railroader/processors/lib/basic_processor.rb +37 -0
  100. data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
  101. data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
  102. data/lib/railroader/processors/lib/find_call.rb +183 -0
  103. data/lib/railroader/processors/lib/find_return_value.rb +166 -0
  104. data/lib/railroader/processors/lib/module_helper.rb +111 -0
  105. data/lib/railroader/processors/lib/processor_helper.rb +88 -0
  106. data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
  107. data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
  108. data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
  109. data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
  110. data/lib/railroader/processors/lib/render_helper.rb +181 -0
  111. data/lib/railroader/processors/lib/render_path.rb +107 -0
  112. data/lib/railroader/processors/lib/route_helper.rb +68 -0
  113. data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
  114. data/lib/railroader/processors/library_processor.rb +74 -0
  115. data/lib/railroader/processors/model_processor.rb +91 -0
  116. data/lib/railroader/processors/output_processor.rb +144 -0
  117. data/lib/railroader/processors/route_processor.rb +17 -0
  118. data/lib/railroader/processors/slim_template_processor.rb +111 -0
  119. data/lib/railroader/processors/template_alias_processor.rb +118 -0
  120. data/lib/railroader/processors/template_processor.rb +85 -0
  121. data/lib/railroader/report/config/remediation.yml +71 -0
  122. data/lib/railroader/report/ignore/config.rb +153 -0
  123. data/lib/railroader/report/ignore/interactive.rb +362 -0
  124. data/lib/railroader/report/pager.rb +112 -0
  125. data/lib/railroader/report/renderer.rb +24 -0
  126. data/lib/railroader/report/report_base.rb +292 -0
  127. data/lib/railroader/report/report_codeclimate.rb +79 -0
  128. data/lib/railroader/report/report_csv.rb +55 -0
  129. data/lib/railroader/report/report_hash.rb +23 -0
  130. data/lib/railroader/report/report_html.rb +216 -0
  131. data/lib/railroader/report/report_json.rb +45 -0
  132. data/lib/railroader/report/report_markdown.rb +107 -0
  133. data/lib/railroader/report/report_table.rb +117 -0
  134. data/lib/railroader/report/report_tabs.rb +17 -0
  135. data/lib/railroader/report/report_text.rb +198 -0
  136. data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
  137. data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
  138. data/lib/railroader/report/templates/error_overview.html.erb +29 -0
  139. data/lib/railroader/report/templates/header.html.erb +58 -0
  140. data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
  141. data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
  142. data/lib/railroader/report/templates/overview.html.erb +38 -0
  143. data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
  144. data/lib/railroader/report/templates/template_overview.html.erb +21 -0
  145. data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
  146. data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
  147. data/lib/railroader/report.rb +88 -0
  148. data/lib/railroader/rescanner.rb +483 -0
  149. data/lib/railroader/scanner.rb +321 -0
  150. data/lib/railroader/tracker/collection.rb +93 -0
  151. data/lib/railroader/tracker/config.rb +154 -0
  152. data/lib/railroader/tracker/constants.rb +171 -0
  153. data/lib/railroader/tracker/controller.rb +161 -0
  154. data/lib/railroader/tracker/library.rb +17 -0
  155. data/lib/railroader/tracker/model.rb +90 -0
  156. data/lib/railroader/tracker/template.rb +33 -0
  157. data/lib/railroader/tracker.rb +362 -0
  158. data/lib/railroader/util.rb +503 -0
  159. data/lib/railroader/version.rb +3 -0
  160. data/lib/railroader/warning.rb +294 -0
  161. data/lib/railroader/warning_codes.rb +117 -0
  162. data/lib/railroader.rb +544 -0
  163. data/lib/ruby_parser/bm_sexp.rb +626 -0
  164. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  165. metadata +386 -0
@@ -0,0 +1,90 @@
1
+ module Railroader
2
+ module CallConversionHelper
3
+ def all_literals? exp, expected_type = :array
4
+ node_type? exp, expected_type and
5
+ exp.length > 1 and
6
+ exp.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
7
+ end
8
+
9
+ # Join two array literals into one.
10
+ def join_arrays lhs, rhs, original_exp = nil
11
+ if array? lhs and array? rhs
12
+ result = Sexp.new(:array).line(lhs.line)
13
+ result.concat lhs[1..-1]
14
+ result.concat rhs[1..-1]
15
+ result
16
+ else
17
+ original_exp
18
+ end
19
+ end
20
+
21
+ # Join two string literals into one.
22
+ def join_strings lhs, rhs, original_exp = nil
23
+ if string? lhs and string? rhs
24
+ result = Sexp.new(:str).line(lhs.line)
25
+ result.value = lhs.value + rhs.value
26
+
27
+ if result.value.length > 50
28
+ # Avoid gigantic strings
29
+ lhs
30
+ else
31
+ result
32
+ end
33
+ elsif call? lhs and lhs.method == :+ and string? lhs.first_arg and string? rhs
34
+ joined = join_strings lhs.first_arg, rhs
35
+ lhs.first_arg = joined
36
+ lhs
37
+ elsif safe_literal? lhs or safe_literal? rhs
38
+ safe_literal(lhs.line)
39
+ else
40
+ original_exp
41
+ end
42
+ end
43
+
44
+ def math_op op, lhs, rhs, original_exp = nil
45
+ if number? lhs and number? rhs
46
+ if op == :/ and rhs.value == 0 and not lhs.value.is_a? Float
47
+ # Avoid division by zero
48
+ return original_exp
49
+ else
50
+ value = lhs.value.send(op, rhs.value)
51
+ Sexp.new(:lit, value).line(lhs.line)
52
+ end
53
+ elsif call? lhs and lhs.method == :+ and number? lhs.first_arg and number? rhs
54
+ # (x + 1) + 2 -> (x + 3)
55
+ lhs.first_arg = Sexp.new(:lit, lhs.first_arg.value + rhs.value).line(lhs.first_arg.line)
56
+ lhs
57
+ elsif safe_literal? lhs or safe_literal? rhs
58
+ safe_literal(lhs.line)
59
+ else
60
+ original_exp
61
+ end
62
+ end
63
+
64
+ # Process single integer access to an array.
65
+ #
66
+ # Returns the value inside the array, if possible.
67
+ def process_array_access array, args, original_exp = nil
68
+ if args.length == 1 and integer? args.first
69
+ index = args.first.value
70
+
71
+ #Have to do this because first element is :array and we have to skip it
72
+ array[1..-1][index] or original_exp
73
+ else
74
+ original_exp
75
+ end
76
+ end
77
+
78
+ # Process hash access by returning the value associated
79
+ # with the given argument.
80
+ def process_hash_access hash, index, original_exp = nil
81
+ if value = hash_access(hash, index)
82
+ value # deep_clone?
83
+ elsif all_literals? hash, :hash
84
+ safe_literal(hash.line)
85
+ else
86
+ original_exp
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,224 @@
1
+ require 'railroader/processors/lib/basic_processor'
2
+
3
+ class Railroader::FindAllCalls < Railroader::BasicProcessor
4
+ attr_reader :calls
5
+
6
+ def initialize tracker
7
+ super
8
+ @current_class = nil
9
+ @current_method = nil
10
+ @in_target = false
11
+ @calls = []
12
+ @cache = {}
13
+ end
14
+
15
+ #Process the given source. Provide either class and method being searched
16
+ #or the template. These names are used when reporting results.
17
+ def process_source exp, opts
18
+ @current_class = opts[:class]
19
+ @current_method = opts[:method]
20
+ @current_template = opts[:template]
21
+ @current_file = opts[:file]
22
+ @current_call = nil
23
+ process exp
24
+ end
25
+
26
+ #Process body of method
27
+ def process_defn exp
28
+ return exp unless @current_method
29
+ process_all exp.body
30
+ end
31
+
32
+ alias process_defs process_defn
33
+
34
+ #Process body of block
35
+ def process_rlist exp
36
+ process_all exp
37
+ end
38
+
39
+ def process_call exp
40
+ @calls << create_call_hash(exp)
41
+ exp
42
+ end
43
+
44
+ def process_iter exp
45
+ call = exp.block_call
46
+
47
+ if call.node_type == :call
48
+ call_hash = create_call_hash(call)
49
+
50
+ call_hash[:block] = exp.block
51
+ call_hash[:block_args] = exp.block_args
52
+
53
+ @calls << call_hash
54
+
55
+ process exp.block
56
+ else
57
+ #Probably a :render call with block
58
+ process call
59
+ process exp.block
60
+ end
61
+
62
+ exp
63
+ end
64
+
65
+ #Calls to render() are converted to s(:render, ...) but we would
66
+ #like them in the call cache still for speed
67
+ def process_render exp
68
+ process exp.last if sexp? exp.last
69
+
70
+ add_simple_call :render, exp
71
+
72
+ exp
73
+ end
74
+
75
+ #Technically, `` is call to Kernel#`
76
+ #But we just need them in the call cache for speed
77
+ def process_dxstr exp
78
+ process exp.last if sexp? exp.last
79
+
80
+ add_simple_call :`, exp
81
+
82
+ exp
83
+ end
84
+
85
+ #:"string" is equivalent to "string".to_sym
86
+ def process_dsym exp
87
+ exp.each { |arg| process arg if sexp? arg }
88
+
89
+ add_simple_call :literal_to_sym, exp
90
+
91
+ exp
92
+ end
93
+
94
+ # Process a dynamic regex like a call
95
+ def process_dregx exp
96
+ exp.each { |arg| process arg if sexp? arg }
97
+
98
+ add_simple_call :railroader_regex_interp, exp
99
+
100
+ exp
101
+ end
102
+
103
+ #Process an assignment like a call
104
+ def process_attrasgn exp
105
+ process_call exp
106
+ end
107
+
108
+ private
109
+
110
+ def add_simple_call method_name, exp
111
+ @calls << { :target => nil,
112
+ :method => method_name,
113
+ :call => exp,
114
+ :nested => false,
115
+ :location => make_location,
116
+ :parent => @current_call }
117
+ end
118
+
119
+ #Gets the target of a call as a Symbol
120
+ #if possible
121
+ def get_target exp, include_calls = false
122
+ if sexp? exp
123
+ case exp.node_type
124
+ when :ivar, :lvar, :const, :lit
125
+ exp.value
126
+ when :true, :false
127
+ exp[0]
128
+ when :colon2
129
+ class_name exp
130
+ when :self
131
+ @current_class || @current_module || nil
132
+ when :params, :session, :cookies
133
+ exp.node_type
134
+ when :call, :safe_call
135
+ if include_calls
136
+ if exp.target.nil?
137
+ exp.method
138
+ else
139
+ t = get_target(exp.target, :include_calls)
140
+ if t.is_a? Symbol
141
+ :"#{t}.#{exp.method}"
142
+ else
143
+ exp
144
+ end
145
+ end
146
+ else
147
+ exp
148
+ end
149
+ else
150
+ exp
151
+ end
152
+ else
153
+ exp
154
+ end
155
+ end
156
+
157
+ #Returns method chain as an array
158
+ #For example, User.human.alive.all would return [:User, :human, :alive, :all]
159
+ def get_chain call
160
+ if node_type? call, :call, :attrasgn, :safe_call, :safe_attrasgn
161
+ get_chain(call.target) + [call.method]
162
+ elsif call.nil?
163
+ []
164
+ else
165
+ [get_target(call)]
166
+ end
167
+ end
168
+
169
+ def make_location
170
+ if @current_template
171
+ key = [@current_template, @current_file]
172
+ cached = @cache[key]
173
+ return cached if cached
174
+
175
+ @cache[key] = { :type => :template,
176
+ :template => @current_template,
177
+ :file => @current_file }
178
+ else
179
+ key = [@current_class, @current_method, @current_file]
180
+ cached = @cache[key]
181
+ return cached if cached
182
+ @cache[key] = { :type => :class,
183
+ :class => @current_class,
184
+ :method => @current_method,
185
+ :file => @current_file }
186
+ end
187
+
188
+ end
189
+
190
+ #Return info hash for a call Sexp
191
+ def create_call_hash exp
192
+ target = get_target exp.target
193
+
194
+ if call? target or node_type? target, :dxstr # need to index `` even if target of a call
195
+ already_in_target = @in_target
196
+ @in_target = true
197
+ process target
198
+ @in_target = already_in_target
199
+
200
+ target = get_target(target, :include_calls)
201
+ end
202
+
203
+ method = exp.method
204
+
205
+ call_hash = {
206
+ :target => target,
207
+ :method => method,
208
+ :call => exp,
209
+ :nested => @in_target,
210
+ :chain => get_chain(exp),
211
+ :location => make_location,
212
+ :parent => @current_call
213
+ }
214
+
215
+ old_parent = @current_call
216
+ @current_call = call_hash
217
+
218
+ process_call_args exp
219
+
220
+ @current_call = old_parent
221
+
222
+ call_hash
223
+ end
224
+ end
@@ -0,0 +1,183 @@
1
+ require 'railroader/processors/lib/basic_processor'
2
+
3
+ #Finds method calls matching the given target(s).
4
+ # #-- This should be deprecated --#
5
+ # #-- Do not use for new code --#
6
+ #
7
+ #Targets/methods can be:
8
+ #
9
+ # - nil: matches anything, including nothing
10
+ # - Empty array: matches nothing
11
+ # - Symbol: matches single target/method exactly
12
+ # - Array of symbols: matches against any of the symbols
13
+ # - Regular expression: matches the expression
14
+ # - Array of regular expressions: matches any of the expressions
15
+ #
16
+ #If a target is also the name of a class, methods called on instances
17
+ #of that class will also be matched, in a very limited way.
18
+ #(Any methods called on Klass.new, basically. More useful when used
19
+ #in conjunction with AliasProcessor.)
20
+ #
21
+ #Examples:
22
+ #
23
+ # #To find any uses of this class:
24
+ # FindCall.new :FindCall, nil
25
+ #
26
+ # #Find system calls without a target
27
+ # FindCall.new [], [:system, :exec, :syscall]
28
+ #
29
+ # #Find all calls to length(), no matter the target
30
+ # FindCall.new nil, :length
31
+ #
32
+ # #Find all calls to sub, sub!, gsub, or gsub!
33
+ # FindCall.new nil, /^g?sub!?$/
34
+ class Railroader::FindCall < Railroader::BasicProcessor
35
+
36
+ def initialize targets, methods, tracker, in_depth = false
37
+ super tracker
38
+ @calls = []
39
+ @find_targets = targets
40
+ @find_methods = methods
41
+ @current_class = nil
42
+ @current_method = nil
43
+ @in_depth = in_depth
44
+ end
45
+
46
+ #Returns a list of results.
47
+ #
48
+ #A result looks like:
49
+ #
50
+ # s(:result, :ClassName, :method_name, s(:call, ...))
51
+ #
52
+ #or
53
+ #
54
+ # s(:result, :template_name, s(:call, ...))
55
+ def matches
56
+ @calls
57
+ end
58
+
59
+ #Process the given source. Provide either class and method being searched
60
+ #or the template. These names are used when reporting results.
61
+ #
62
+ #Use FindCall#matches to retrieve results.
63
+ def process_source exp, klass = nil, method = nil, template = nil
64
+ @current_class = klass
65
+ @current_method = method
66
+ @current_template = template
67
+ process exp
68
+ end
69
+
70
+ #Process body of method
71
+ def process_defn exp
72
+ process_all exp.body
73
+ end
74
+
75
+ alias :process_defs :process_defn
76
+
77
+ #Process body of block
78
+ def process_rlist exp
79
+ process_all exp
80
+ end
81
+
82
+ #Look for matching calls and add them to results
83
+ def process_call exp
84
+ target = get_target exp.target
85
+ method = exp.method
86
+
87
+ process_call_args exp
88
+
89
+ if match(@find_targets, target) and match(@find_methods, method)
90
+
91
+ if @current_template
92
+ @calls << Sexp.new(:result, @current_template, exp).line(exp.line)
93
+ else
94
+ @calls << Sexp.new(:result, @current_module, @current_class, @current_method, exp).line(exp.line)
95
+ end
96
+
97
+ end
98
+
99
+ #Normally FindCall won't match a method invocation that is the target of
100
+ #another call, such as:
101
+ #
102
+ # User.find(:first, :conditions => "user = '#{params['user']}').name
103
+ #
104
+ #A search for User.find will not match this unless @in_depth is true.
105
+ if @in_depth and call? exp.target
106
+ process exp.target
107
+ end
108
+
109
+ exp
110
+ end
111
+
112
+ #Process an assignment like a call
113
+ def process_attrasgn exp
114
+ process_call exp
115
+ end
116
+
117
+ private
118
+
119
+ #Gets the target of a call as a Symbol
120
+ #if possible
121
+ def get_target exp
122
+ if sexp? exp
123
+ case exp.node_type
124
+ when :ivar, :lvar, :const, :lit
125
+ exp.value
126
+ when :true, :false
127
+ exp.node_type
128
+ when :colon2
129
+ class_name exp
130
+ else
131
+ exp
132
+ end
133
+ else
134
+ exp
135
+ end
136
+ end
137
+
138
+ #Checks if the search terms match the given item
139
+ def match search_terms, item
140
+ case search_terms
141
+ when Symbol
142
+ if search_terms == item
143
+ true
144
+ elsif sexp? item
145
+ is_instance_of? item, search_terms
146
+ else
147
+ false
148
+ end
149
+ when Sexp
150
+ search_terms == item
151
+ when Enumerable
152
+ if search_terms.empty?
153
+ item == nil
154
+ else
155
+ search_terms.each do|term|
156
+ if match(term, item)
157
+ return true
158
+ end
159
+ end
160
+ false
161
+ end
162
+ when Regexp
163
+ search_terms.match item.to_s
164
+ when nil
165
+ true
166
+ else
167
+ raise "Cannot match #{search_terms} and #{item}"
168
+ end
169
+ end
170
+
171
+ #Checks if +item+ is an instance of +klass+ by looking for Klass.new
172
+ def is_instance_of? item, klass
173
+ if call? item
174
+ if sexp? item.target
175
+ item.method == :new and item.target.node_type == :const and item.target.value == klass
176
+ else
177
+ item.method == :new and item.target == klass
178
+ end
179
+ else
180
+ false
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,166 @@
1
+ require 'railroader/processors/alias_processor'
2
+
3
+ #Attempts to determine the return value of a method.
4
+ #
5
+ #Preferred usage:
6
+ #
7
+ # Railroader::FindReturnValue.return_value exp
8
+ class Railroader::FindReturnValue
9
+ include Railroader::Util
10
+
11
+ #Returns a guess at the return value of a given method or other block of code.
12
+ #
13
+ #If multiple return values are possible, returns all values in an :or Sexp.
14
+ def self.return_value exp, env = nil
15
+ self.new.get_return_value exp, env
16
+ end
17
+
18
+ def initialize
19
+ @uses_ivars = false
20
+ @return_values = []
21
+ end
22
+
23
+ def uses_ivars?
24
+ @uses_ivars
25
+ end
26
+
27
+ #Find return value of Sexp. Takes an optional starting environment.
28
+ def get_return_value exp, env = nil
29
+ process_method exp, env
30
+ value = make_return_value
31
+ value.original_line = exp.line
32
+ value
33
+ end
34
+
35
+ #Process method (or, actually, any Sexp) for return value.
36
+ def process_method exp, env = nil
37
+ exp = Railroader::AliasProcessor.new.process_safely exp, env
38
+
39
+ find_explicit_return_values exp
40
+
41
+ if node_type? exp, :defn, :defs
42
+ body = exp.body
43
+
44
+ unless body.empty?
45
+ @return_values << last_value(body)
46
+ else
47
+ Railroader.debug "FindReturnValue: Empty method? #{exp.inspect}"
48
+ end
49
+ elsif exp
50
+ @return_values << last_value(exp)
51
+ else
52
+ Railroader.debug "FindReturnValue: Given something strange? #{exp.inspect}"
53
+ end
54
+
55
+ exp
56
+ end
57
+
58
+ #Searches expression for return statements.
59
+ def find_explicit_return_values exp
60
+ todo = [exp]
61
+
62
+ until todo.empty?
63
+ current = todo.shift
64
+
65
+ @uses_ivars = true if node_type? current, :ivar
66
+
67
+ if node_type? current, :return
68
+ @return_values << last_value(current.value) if current.value
69
+ elsif sexp? current
70
+ todo = current[1..-1].concat todo
71
+ end
72
+ end
73
+ end
74
+
75
+ #Determines the "last value" of an expression.
76
+ def last_value exp
77
+ case exp.node_type
78
+ when :rlist, :block, :scope, Sexp
79
+ last_value exp.last
80
+ when :if
81
+ then_clause = exp.then_clause
82
+ else_clause = exp.else_clause
83
+
84
+ if then_clause.nil? and else_clause.nil?
85
+ nil
86
+ elsif then_clause.nil?
87
+ last_value else_clause
88
+ elsif else_clause.nil?
89
+ last_value then_clause
90
+ else
91
+ true_branch = last_value then_clause
92
+ false_branch = last_value else_clause
93
+
94
+ if true_branch and false_branch
95
+ value = make_or(true_branch, false_branch)
96
+ value.original_line = value.rhs.line
97
+ value
98
+ else #Unlikely?
99
+ true_branch or false_branch
100
+ end
101
+ end
102
+ when :lasgn, :iasgn, :op_asgn_or, :attrasgn
103
+ last_value exp.rhs
104
+ when :rescue
105
+ values = []
106
+
107
+ exp.each_sexp do |e|
108
+ if node_type? e, :resbody
109
+ if e.last
110
+ values << last_value(e.last)
111
+ end
112
+ elsif sexp? e
113
+ values << last_value(e)
114
+ end
115
+ end
116
+
117
+ values.reject! do |v|
118
+ v.nil? or node_type? v, :nil
119
+ end
120
+
121
+ if values.length > 1
122
+ values.inject do |m, v|
123
+ make_or(m, v)
124
+ end
125
+ else
126
+ values.first
127
+ end
128
+ when :return
129
+ if exp.value
130
+ last_value exp.value
131
+ else
132
+ nil
133
+ end
134
+ when :nil
135
+ nil
136
+ else
137
+ exp.original_line = exp.line unless exp.original_line
138
+ exp
139
+ end
140
+ end
141
+
142
+ def make_or lhs, rhs
143
+ #Better checks in future
144
+ if lhs == rhs
145
+ lhs
146
+ else
147
+ Sexp.new(:or, lhs, rhs)
148
+ end
149
+ end
150
+
151
+ #Turns the array of return values into an :or Sexp
152
+ def make_return_value
153
+ @return_values.compact!
154
+ @return_values.uniq!
155
+
156
+ if @return_values.empty?
157
+ Sexp.new(:nil)
158
+ elsif @return_values.length == 1
159
+ @return_values.first
160
+ else
161
+ @return_values.reduce do |value, sexp|
162
+ make_or value, sexp
163
+ end
164
+ end
165
+ end
166
+ end