Today I spent a few hours trying to nail down a nasty bug. Once I could replicate a small test case, what finally clued me in was an error message that looked impossible, but I remembered seeing it somewhere before. I just couldn't remember where. Consider this:
if ($c->user || $c->authenticate(undef, 'Pips Realm')) {
That was failing with "Undefined subroutine &Pips3::user called at ..."
Wait! What? I didn't call a subroutine. I called a method! What the hell is going on?
For our tests, we sometimes override the authenticated user in our Pips3 application. The core of this code looks like this:
sub new { my ( $class, $user ) = @_; unless (defined wantarray) { croak('PIPTest::User->new($user) must not be called in void context'); } my $self = bless { pips_user => \&Pips3::user, cat_user => \&Catalyst::Request::user, user => $user, } => $class; $self->_set_user($user); return $self; } sub _set_user { my $self = shift; # Get reference to sub returning the user object my $coderef = $self->_return_user_coderef; # Override Pips3::user { no warnings 'redefine'; *Pips3::user = $coderef; *Catalyst::Request::user = $coderef; } # Return the user object for convenience return $coderef->(); } sub DESTROY { my $self = shift; no warnings 'redefine'; *Pips3::user = $self->{pips_user}; *Catalyst::Request::user = $self->{cat_user}; }
When you call 'new' with a valid user name, you get a token back and while it is in scope, you have overridden the user for the application. I was quite happy with this code, but there's a nasty, nasty bug in it. Once I remembered what it was, the fix was painfully obvious.
In Perl, when you try to pull a coderef out of a typeglob, you sometimes get some strange behavior:
sub Foo::bar {'foo'} my $code1 = *Foo::bar{CODE}; my $code2 = *Foo::no_such_subroutine{CODE}; print "$code1 $code2"; __END__ # output is something like: foo CODE(0x8063d94) CODE(0x8063d70)
As you can see, you apparently get a code reference. If you try to call it, though, it will give the following error message "Undefined subroutine &main::no_such_subroutine called at ...". It even remembers the name of the non-existent subroutine! Even stranger:
#!/usr/bin/perl -l sub Foo::bar {'foo'} *Foo::baz = \&::baz; print Foo->bar; print Foo->baz; __END__ foo Undefined subroutine &main::baz called at test.pl line 7.
Wait! What? I didn't call a subroutine. I called a method! What the hell is going on?
Ooooooohhhhhhhh.
In Catalyst, when you use a plugin, your application gets the methods because it inherits them. It doesn't install them into your namespace. Thus, when Pips3 used the authentication plugin, my constructor above, trying to grab a reference to &Pips3::user, was clearly stupid. What's worse, when I tried to restore it, I had a non-existent method overriding an existing one! How's that for a blunder? The fix is simple:
sub new { my ( $class, $user ) = @_; unless (defined wantarray) { croak('PIPTest::User->new($user) must not be called in void context'); } my $self = bless { pips_user => Pips3->can('user'), cat_user => Catalyst::Request->can('user'), user => $user, } => $class; $self->_set_user($user); return $self; }
This "strange behavior" is so that you can take a reference to an as-yet-undefined sub and call it later through the reference.