brakeman 5.0.2 → 5.0.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +6 -0
  3. data/bundle/load.rb +0 -1
  4. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby20_parser.rb +278 -273
  5. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby20_parser.y +3 -0
  6. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby21_parser.rb +291 -286
  7. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby21_parser.y +3 -0
  8. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby22_parser.rb +297 -292
  9. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby22_parser.y +3 -0
  10. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby23_parser.rb +295 -290
  11. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby23_parser.y +3 -0
  12. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby24_parser.rb +296 -291
  13. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby24_parser.y +3 -0
  14. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby25_parser.rb +297 -292
  15. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby25_parser.y +3 -0
  16. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby26_parser.rb +301 -296
  17. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby26_parser.y +3 -0
  18. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby27_parser.rb +2528 -2480
  19. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby27_parser.y +26 -0
  20. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby30_parser.rb +2528 -2480
  21. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby30_parser.y +26 -0
  22. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/lib/ruby_parser.yy +30 -0
  23. data/bundle/ruby/2.7.0/gems/ruby_parser-3.16.0/tools/ripper.rb +1 -1
  24. data/lib/brakeman.rb +0 -4
  25. data/lib/brakeman/checks/check_detailed_exceptions.rb +1 -1
  26. data/lib/brakeman/checks/check_evaluation.rb +1 -1
  27. data/lib/brakeman/checks/check_sql.rb +2 -15
  28. data/lib/brakeman/checks/check_verb_confusion.rb +1 -1
  29. data/lib/brakeman/file_parser.rb +14 -36
  30. data/lib/brakeman/options.rb +1 -1
  31. data/lib/brakeman/processors/alias_processor.rb +7 -52
  32. data/lib/brakeman/processors/controller_alias_processor.rb +43 -6
  33. data/lib/brakeman/processors/lib/call_conversion_helper.rb +0 -10
  34. data/lib/brakeman/processors/library_processor.rb +0 -9
  35. data/lib/brakeman/report.rb +1 -4
  36. data/lib/brakeman/report/ignore/interactive.rb +1 -1
  37. data/lib/brakeman/scanner.rb +0 -3
  38. data/lib/brakeman/tracker.rb +4 -33
  39. data/lib/brakeman/tracker/collection.rb +5 -27
  40. data/lib/brakeman/util.rb +0 -8
  41. data/lib/brakeman/version.rb +1 -1
  42. metadata +2 -8
  43. data/bundle/ruby/2.7.0/gems/parallel-1.20.1/MIT-LICENSE.txt +0 -20
  44. data/bundle/ruby/2.7.0/gems/parallel-1.20.1/lib/parallel.rb +0 -523
  45. data/bundle/ruby/2.7.0/gems/parallel-1.20.1/lib/parallel/processor_count.rb +0 -42
  46. data/bundle/ruby/2.7.0/gems/parallel-1.20.1/lib/parallel/version.rb +0 -3
  47. data/lib/brakeman/report/report_github.rb +0 -31
  48. data/lib/brakeman/tracker/method_info.rb +0 -29
@@ -1009,6 +1009,16 @@ rule
1009
1009
  _, args, _ = val
1010
1010
  result = args
1011
1011
  }
1012
+ | tLPAREN2 args_forward rparen
1013
+ {
1014
+ if (!self.lexer.is_local_id(:"*") ||
1015
+ !self.lexer.is_local_id(:"**") ||
1016
+ !self.lexer.is_local_id(:"&")) then
1017
+
1018
+ yyerror("Invalid argument forwarding")
1019
+ end
1020
+ result = call_args [s(:forward_args).line(lexer.lineno)]
1021
+ }
1012
1022
 
1013
1023
  opt_paren_args: none
1014
1024
  | paren_args
@@ -2289,6 +2299,19 @@ keyword_variable: kNIL { result = s(:nil).line lexer.lineno }
2289
2299
  self.lexer.lex_state = EXPR_BEG
2290
2300
  self.lexer.command_start = true
2291
2301
  }
2302
+ | tLPAREN2 args_forward rparen
2303
+ {
2304
+ args_rest = :"*"
2305
+ kwargs_rest = :"**"
2306
+ block_fwd = :"&"
2307
+ self.env[args_rest] = :lvar
2308
+ self.env[kwargs_rest] = :lvar
2309
+ self.env[block_fwd] = :lvar
2310
+
2311
+ result = s(:args, s(:forward_args)).line lexer.lineno
2312
+ self.lexer.lex_state = EXPR_BEG
2313
+ self.lexer.command_start = true
2314
+ }
2292
2315
  | {
2293
2316
  result = self.in_kwarg
2294
2317
  self.in_kwarg = true
@@ -2388,6 +2411,8 @@ keyword_variable: kNIL { result = s(:nil).line lexer.lineno }
2388
2411
  result = args val
2389
2412
  }
2390
2413
 
2414
+ args_forward: tBDOT3
2415
+
2391
2416
  f_bad_arg: tCONSTANT
2392
2417
  {
2393
2418
  yyerror "formal argument cannot be a constant"
@@ -2516,6 +2541,7 @@ keyword_variable: kNIL { result = s(:nil).line lexer.lineno }
2516
2541
  | kwrest_mark
2517
2542
  {
2518
2543
  result = :"**"
2544
+ self.env[result] = :lvar
2519
2545
  }
2520
2546
 
2521
2547
  f_opt: f_arg_asgn tEQL arg_value
@@ -1066,6 +1066,18 @@ rule
1066
1066
  _, args, _ = val
1067
1067
  result = args
1068
1068
  }
1069
+ #if V >= 27
1070
+ | tLPAREN2 args_forward rparen
1071
+ {
1072
+ if (!self.lexer.is_local_id(:"*") ||
1073
+ !self.lexer.is_local_id(:"**") ||
1074
+ !self.lexer.is_local_id(:"&")) then
1075
+
1076
+ yyerror("Invalid argument forwarding")
1077
+ end
1078
+ result = call_args [s(:forward_args).line(lexer.lineno)]
1079
+ }
1080
+ #endif
1069
1081
 
1070
1082
  opt_paren_args: none
1071
1083
  | paren_args
@@ -2366,6 +2378,21 @@ keyword_variable: kNIL { result = s(:nil).line lexer.lineno }
2366
2378
  self.lexer.lex_state = EXPR_BEG
2367
2379
  self.lexer.command_start = true
2368
2380
  }
2381
+ #if V >= 27
2382
+ | tLPAREN2 args_forward rparen
2383
+ {
2384
+ args_rest = :"*"
2385
+ kwargs_rest = :"**"
2386
+ block_fwd = :"&"
2387
+ self.env[args_rest] = :lvar
2388
+ self.env[kwargs_rest] = :lvar
2389
+ self.env[block_fwd] = :lvar
2390
+
2391
+ result = s(:args, s(:forward_args)).line lexer.lineno
2392
+ self.lexer.lex_state = EXPR_BEG
2393
+ self.lexer.command_start = true
2394
+ }
2395
+ #endif
2369
2396
  | {
2370
2397
  result = self.in_kwarg
2371
2398
  self.in_kwarg = true
@@ -2465,6 +2492,8 @@ keyword_variable: kNIL { result = s(:nil).line lexer.lineno }
2465
2492
  result = args val
2466
2493
  }
2467
2494
 
2495
+ args_forward: tBDOT3
2496
+
2468
2497
  f_bad_arg: tCONSTANT
2469
2498
  {
2470
2499
  yyerror "formal argument cannot be a constant"
@@ -2613,6 +2642,7 @@ keyword_variable: kNIL { result = s(:nil).line lexer.lineno }
2613
2642
  | kwrest_mark
2614
2643
  {
2615
2644
  result = :"**"
2645
+ self.env[result] = :lvar
2616
2646
  }
2617
2647
 
2618
2648
  #if V == 20
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby -ws
1
+ #!/Users/ryan/.rubies/ruby-2.7.1/bin/ruby -ws
2
2
 
3
3
  $d ||= false
4
4
  $p ||= false
data/lib/brakeman.rb CHANGED
@@ -250,8 +250,6 @@ module Brakeman
250
250
  [:to_sarif]
251
251
  when :sonar, :to_sonar
252
252
  [:to_sonar]
253
- when :github, :to_github
254
- [:to_github]
255
253
  else
256
254
  [:to_text]
257
255
  end
@@ -285,8 +283,6 @@ module Brakeman
285
283
  :to_sarif
286
284
  when /\.sonar$/i
287
285
  :to_sonar
288
- when /\.github$/i
289
- :to_github
290
286
  else
291
287
  :to_text
292
288
  end
@@ -26,7 +26,7 @@ class Brakeman::CheckDetailedExceptions < Brakeman::BaseCheck
26
26
  def check_detailed_exceptions
27
27
  tracker.controllers.each do |_name, controller|
28
28
  controller.methods_public.each do |method_name, definition|
29
- src = definition.src
29
+ src = definition[:src]
30
30
  body = src.body.last
31
31
  next unless body
32
32
 
@@ -10,7 +10,7 @@ class Brakeman::CheckEvaluation < Brakeman::BaseCheck
10
10
  #Process calls
11
11
  def run_check
12
12
  Brakeman.debug "Finding eval-like calls"
13
- calls = tracker.find_call methods: [:eval, :instance_eval, :class_eval, :module_eval], nested: true
13
+ calls = tracker.find_call :method => [:eval, :instance_eval, :class_eval, :module_eval]
14
14
 
15
15
  Brakeman.debug "Processing eval-like calls"
16
16
  calls.each do |call|
@@ -572,7 +572,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
572
572
  end
573
573
 
574
574
  IGNORE_METHODS_IN_SQL = Set[:id, :merge_conditions, :table_name, :quoted_table_name,
575
- :quoted_primary_key, :to_i, :to_f, :sanitize_sql, :sanitize_sql_array, :sanitize_sql_like,
575
+ :quoted_primary_key, :to_i, :to_f, :sanitize_sql, :sanitize_sql_array,
576
576
  :sanitize_sql_for_assignment, :sanitize_sql_for_conditions, :sanitize_sql_hash,
577
577
  :sanitize_sql_hash_for_assignment, :sanitize_sql_hash_for_conditions,
578
578
  :to_sql, :sanitize, :primary_key, :table_name_prefix, :table_name_suffix,
@@ -592,8 +592,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
592
592
  IGNORE_METHODS_IN_SQL.include? exp.method or
593
593
  quote_call? exp or
594
594
  arel? exp or
595
- exp.method.to_s.end_with? "_id" or
596
- number_target? exp
595
+ exp.method.to_s.end_with? "_id"
597
596
  end
598
597
  when :if
599
598
  safe_value? exp.then_clause and safe_value? exp.else_clause
@@ -696,16 +695,4 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
696
695
  active_record_models.include? klass
697
696
  end
698
697
  end
699
-
700
- def number_target? exp
701
- return unless call? exp
702
-
703
- if number? exp.target
704
- true
705
- elsif call? exp.target
706
- number_target? exp.target
707
- else
708
- false
709
- end
710
- end
711
698
  end
@@ -32,7 +32,7 @@ class Brakeman::CheckVerbConfusion < Brakeman::BaseCheck
32
32
  return
33
33
  end
34
34
 
35
- process method.src
35
+ process method[:src]
36
36
  end
37
37
 
38
38
  def process_if exp
@@ -1,5 +1,3 @@
1
- require 'parallel'
2
-
3
1
  module Brakeman
4
2
  ASTFile = Struct.new(:path, :ast)
5
3
 
@@ -15,46 +13,21 @@ module Brakeman
15
13
  end
16
14
 
17
15
  def parse_files list
18
- # Parse the files in parallel.
19
- # By default, the parsing will be in separate processes.
20
- # So we map the result to ASTFiles and/or Exceptions
21
- # then partition them into ASTFiles and Exceptions
22
- # and add the Exceptions to @errors
23
- #
24
- # Basically just a funky way to deal with two possible
25
- # return types that are returned from isolated processes.
26
- #
27
- # Note this method no longer uses read_files
28
- @file_list, new_errors = Parallel.map(list) do |file_name|
29
- file_path = @app_tree.file_path(file_name)
30
- contents = file_path.read
31
-
32
- begin
33
- if ast = parse_ruby(contents, file_path.relative)
34
- ASTFile.new(file_name, ast)
35
- end
36
- rescue Exception => e
37
- e
16
+ read_files list do |path, contents|
17
+ if ast = parse_ruby(contents, path.relative)
18
+ ASTFile.new(path, ast)
38
19
  end
39
- end.compact.partition do |result|
40
- result.is_a? ASTFile
41
20
  end
42
-
43
- errors.concat new_errors
44
21
  end
45
22
 
46
23
  def read_files list
47
24
  list.each do |path|
48
25
  file = @app_tree.file_path(path)
49
26
 
50
- begin
51
- result = yield file, file.read
27
+ result = yield file, file.read
52
28
 
53
- if result
54
- @file_list << result
55
- end
56
- rescue Exception => e
57
- @errors << e
29
+ if result
30
+ @file_list << result
58
31
  end
59
32
  end
60
33
  end
@@ -69,12 +42,17 @@ module Brakeman
69
42
  Brakeman.debug "Parsing #{path}"
70
43
  RubyParser.new.parse input, path, @timeout
71
44
  rescue Racc::ParseError => e
72
- raise e.exception(e.message + "\nCould not parse #{path}")
45
+ error e.exception(e.message + "\nCould not parse #{path}")
73
46
  rescue Timeout::Error => e
74
- raise Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout")
47
+ error Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout")
75
48
  rescue => e
76
- raise e.exception(e.message + "\nWhile processing #{path}")
49
+ error e.exception(e.message + "\nWhile processing #{path}")
77
50
  end
78
51
  end
52
+
53
+ def error exception
54
+ @errors << exception
55
+ nil
56
+ end
79
57
  end
80
58
  end
@@ -233,7 +233,7 @@ module Brakeman::Options
233
233
 
234
234
  opts.on "-f",
235
235
  "--format TYPE",
236
- [:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif, :sonar, :github],
236
+ [:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif, :sonar],
237
237
  "Specify output formats. Default is text" do |type|
238
238
 
239
239
  type = "s" if type == :text
@@ -220,28 +220,13 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
220
220
  exp = math_op(:+, target, first_arg, exp)
221
221
  end
222
222
  when :-, :*, :/
223
- if method == :* and array? target
224
- if string? first_arg
225
- exp = process_array_join(target, first_arg)
226
- end
227
- else
228
- exp = math_op(method, target, first_arg, exp)
229
- end
223
+ exp = math_op(method, target, first_arg, exp)
230
224
  when :[]
231
225
  if array? target
232
226
  exp = process_array_access(target, exp.args, exp)
233
227
  elsif hash? target
234
228
  exp = process_hash_access(target, first_arg, exp)
235
229
  end
236
- when :fetch
237
- if array? target
238
- # Not dealing with default value
239
- # so just pass in first argument, but process_array_access expects
240
- # an array of arguments.
241
- exp = process_array_access(target, [first_arg], exp)
242
- elsif hash? target
243
- exp = process_hash_access(target, first_arg, exp)
244
- end
245
230
  when :merge!, :update
246
231
  if hash? target and hash? first_arg
247
232
  target = process_hash_merge! target, first_arg
@@ -281,12 +266,6 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
281
266
  target = find_push_target(target_var)
282
267
  env[target] = exp unless target.nil? # Happens in TemplateAliasProcessor
283
268
  end
284
- when :push
285
- if array? target
286
- target << first_arg
287
- env[target_var] = target
288
- return target
289
- end
290
269
  when :first
291
270
  if array? target and first_arg.nil? and sexp? target[1]
292
271
  exp = target[1]
@@ -300,7 +279,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
300
279
  exp = target
301
280
  end
302
281
  when :join
303
- if array? target and (string? first_arg or first_arg.nil?)
282
+ if array? target and target.length > 2 and (string? first_arg or first_arg.nil?)
304
283
  exp = process_array_join(target, first_arg)
305
284
  end
306
285
  when :!
@@ -308,15 +287,6 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
308
287
  if call? target and target.method == :!
309
288
  exp = s(:or, s(:true).line(exp.line), s(:false).line(exp.line)).line(exp.line)
310
289
  end
311
- when :values
312
- # Hash literal
313
- if node_type? target, :hash
314
- exp = hash_values(target)
315
- end
316
- when :values_at
317
- if hash? target
318
- exp = hash_values_at target, exp.args
319
- end
320
290
  end
321
291
 
322
292
  exp
@@ -324,11 +294,6 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
324
294
 
325
295
  # Painful conversion of Array#join into string interpolation
326
296
  def process_array_join array, join_str
327
- # Empty array
328
- if array.length == 1
329
- return s(:str, '').line(array.line)
330
- end
331
-
332
297
  result = s().line(array.line)
333
298
 
334
299
  join_value = if string? join_str
@@ -337,10 +302,8 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
337
302
  nil
338
303
  end
339
304
 
340
- if array.length > 2
341
- array[1..-2].each do |e|
342
- result << join_item(e, join_value)
343
- end
305
+ array[1..-2].each do |e|
306
+ result << join_item(e, join_value)
344
307
  end
345
308
 
346
309
  result << join_item(array.last, nil)
@@ -369,7 +332,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
369
332
  result.unshift combined_first
370
333
 
371
334
  # Have to fix up strings that follow interpolation
372
- string = result.reduce(s(:dstr).line(array.line)) do |memo, e|
335
+ result.reduce(s(:dstr).line(array.line)) do |memo, e|
373
336
  if string? e and node_type? memo.last, :evstr
374
337
  e.value = "#{join_value}#{e.value}"
375
338
  elsif join_value and node_type? memo.last, :evstr and node_type? e, :evstr
@@ -378,14 +341,6 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
378
341
 
379
342
  memo << e
380
343
  end
381
-
382
- # Convert (:dstr, "hello world")
383
- # to (:str, "hello world")
384
- if string.length == 2 and string.last.is_a? String
385
- string[0] = :str
386
- end
387
-
388
- string
389
344
  end
390
345
 
391
346
  def join_item item, join_value
@@ -1058,8 +1013,8 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
1058
1013
  method_name = call.method
1059
1014
 
1060
1015
  #Look for helper methods and see if we can get a return value
1061
- if found_method = tracker.find_method(method_name, @current_class)
1062
- helper = found_method.src
1016
+ if found_method = find_method(method_name, @current_class)
1017
+ helper = found_method[:method]
1063
1018
 
1064
1019
  if sexp? helper
1065
1020
  value = process_helper_method helper, call.args
@@ -51,7 +51,7 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
51
51
  #Need to process the method like it was in a controller in order
52
52
  #to get the renders set
53
53
  processor = Brakeman::ControllerProcessor.new(@tracker, mixin.file)
54
- method = mixin.get_method(name).src.deep_clone
54
+ method = mixin.get_method(name)[:src].deep_clone
55
55
 
56
56
  if node_type? method, :defn
57
57
  method = processor.process_defn method
@@ -143,16 +143,16 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
143
143
  #Basically, adds any instance variable assignments to the environment.
144
144
  #TODO: method arguments?
145
145
  def process_before_filter name
146
- filter = tracker.find_method name, @current_class
146
+ filter = find_method name, @current_class
147
147
 
148
148
  if filter.nil?
149
149
  Brakeman.debug "[Notice] Could not find filter #{name}"
150
150
  return
151
151
  end
152
152
 
153
- method = filter.src
153
+ method = filter[:method]
154
154
 
155
- if ivars = @tracker.filter_cache[[filter.owner, name]]
155
+ if ivars = @tracker.filter_cache[[filter[:controller], name]]
156
156
  ivars.each do |variable, value|
157
157
  env[variable] = value
158
158
  end
@@ -162,7 +162,7 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
162
162
 
163
163
  ivars = processor.only_ivars(:include_request_vars).all
164
164
 
165
- @tracker.filter_cache[[filter.owner, name]] = ivars
165
+ @tracker.filter_cache[[filter[:controller], name]] = ivars
166
166
 
167
167
  ivars.each do |variable, value|
168
168
  env[variable] = value
@@ -182,7 +182,7 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
182
182
  # method as the line number
183
183
  if line.nil? and controller = @tracker.controllers[@current_class]
184
184
  if meth = controller.get_method(@current_method)
185
- if line = meth.src && meth.src.last && meth.src.last.line
185
+ if line = meth[:src] && meth[:src].last && meth[:src].last.line
186
186
  line += 1
187
187
  else
188
188
  line = 1
@@ -241,4 +241,41 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
241
241
  []
242
242
  end
243
243
  end
244
+
245
+ #Finds a method in the given class or a parent class
246
+ #
247
+ #Returns nil if the method could not be found.
248
+ #
249
+ #If found, returns hash table with controller name and method sexp.
250
+ def find_method method_name, klass
251
+ return nil if sexp? method_name
252
+ method_name = method_name.to_sym
253
+
254
+ if method = @method_cache[method_name]
255
+ return method
256
+ end
257
+
258
+ controller = @tracker.controllers[klass]
259
+ controller ||= @tracker.libs[klass]
260
+
261
+ if klass and controller
262
+ method = controller.get_method method_name
263
+
264
+ if method.nil?
265
+ controller.includes.each do |included|
266
+ method = find_method method_name, included
267
+ if method
268
+ @method_cache[method_name] = method
269
+ return method
270
+ end
271
+ end
272
+
273
+ @method_cache[method_name] = find_method method_name, controller.parent
274
+ else
275
+ @method_cache[method_name] = { :controller => controller.name, :method => method[:src] }
276
+ end
277
+ else
278
+ nil
279
+ end
280
+ end
244
281
  end