brakeman 5.0.2 → 5.0.4

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