[Catalyst] Catalyst::Controller bug when parsing attributes (with patch)

Seth Daniel catalyst at sethdaniel.org
Thu Feb 3 17:15:03 GMT 2011


Hi,

I believe I've discovered a bug in Catalyst::Controller.  I triggered it 
while using Catalyst::Controller::ActionRole so my examples below will
refer to that module. 

If I have a Catalyst controller with a configuration like the following:

__PACKAGE->config(
  action => {
    some_action => { Does => [ '+Some:ActionRole' ] }
  }
);


This will break *if* the Action module that processes the 'Does' attribute
changes the value that is passed to it.  For a real world example of this see
Catalyst::Controller::ActionRole which changes the value passed to its
_parse_Does_attr method.

The _parse_attrs method in Catalyst::Controller does the following:

 [...]
    foreach my $key (keys %raw_attributes) {

        my $raw = $raw_attributes{$key};

        foreach my $value (ref($raw) eq 'ARRAY' ? @$raw : $raw) {

            my $meth = "_parse_${key}_attr";
            if ( my $code = $self->can($meth) ) {
                ( $key, $value ) = $self->$code( $c, $name, $value );
            }
            push( @{ $final_attributes{$key} }, $value );
        }
    }
 [...]

The problem lies in the inner foreach.  When $row is an array reference (and
only when $row is an array reference) $value will be an *alias* to each element
of that array.  So if $value is changed within the attribute parsing method
this means that the next time this attribute parsing method is called the
$value passed to it will be the changed value...but this only occurs when
using a configuration like what I pasted above because the actual configuration
hash gets changed.  I don't recall which perl introduced aliasing, but I use
perl 5.10.1.

Here is a simple patch which resolves the issue:

--- Controller.pm  2010-07-28 15:03:32.000000000 -0700
+++ Controller.pm.bak  2011-02-02 16:55:47.000000000 -0800
@@ -322,11 +322,12 @@
 
         foreach my $value (ref($raw) eq 'ARRAY' ? @$raw : $raw) {
 
+            my $t_val = $value;
             my $meth = "_parse_${key}_attr";
             if ( my $code = $self->can($meth) ) {
-                ( $key, $value ) = $self->$code( $c, $name, $value );
+                ( $key, $t_val ) = $self->$code( $c, $name, $t_val );
             }
-            push( @{ $final_attributes{$key} }, $value );
+            push( @{ $final_attributes{$key} }, $t_val );
         }
     }


A workaround, at least when using Catalyst::Controller::ActionRole, is 
to not use the array ref syntax:

__PACKAGE->config(
  action => {
    some_action => { Does => '+Some:ActionRole' }
  }
);


Of course this workaround isn't useful if you want to apply multiple roles to
an action. 

I can probably provide a program that demonstrates this, but I've found
that writing a 'simple' Catalyst application requires a lot of setup. :o/

Catalyst::Runtime 5.80031
Catalyst::Controller::ActionRole 0.15

-- 
seth /\ sethdaniel.org



More information about the Catalyst mailing list