Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Ovid (2709)

Ovid
  (email not shown publicly)
http://publius-ovidius.livejournal.com/
AOL IM: ovidperl (Add Buddy, Send Message)

Stuff with the Perl Foundation. A couple of patches in the Perl core. A few CPAN modules. That about sums it up.

Journal of Ovid (2709)

Thursday June 26, 2008
10:57 AM

Overriding Methods with Non-Existent Methods

[ #36789 ]

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;
}

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • This "strange behavior" is so that you can take a reference to an as-yet-undefined sub and call it later through the reference.