Lumi
Lumi

Reputation: 15264

Perl module loading - Safeguarding against: perhaps you forgot to load "Bla"?

When you run perl -e "Bla->new", you get this well-known error:

Can't locate object method "new" via package "Bla"
(perhaps you forgot to load "Bla"?)

Happened in a Perl server process the other day due to an oversight of mine. There are multiple scripts, and most of them have the proper use statements in place. But there was one script that was doing Bla->new in sub blub at line 123 but missing a use Bla at the top, and when it was hit by a click without any of the other scripts using Bla having been loaded by the server process before, then bang!

Testing the script in isolation would be the obvious way to safeguard against this particular mistake, but alas the code is dependent upon a humungous environment. Do you know of another way to safeguard against this oversight?

Here's one example how PPI (despite its merits) is limited in its view on Perl:

use strict;
use HTTP::Request::Common;

my $req = GET 'http://www.example.com';
$req->headers->push_header( Bla => time );

my $au=Auweia->new;

__END__
PPI::Token::Symbol          '$req'
PPI::Token::Operator        '->'
PPI::Token::Word            'headers'
PPI::Token::Operator        '->'
PPI::Token::Word            'push_header'

PPI::Token::Symbol          '$au'
PPI::Token::Operator        '='
PPI::Token::Word            'Auweia'
PPI::Token::Operator        '->'
PPI::Token::Word            'new'

Setting the header and assigning the Auweia->new parse the same. So I'm not sure how you can build upon such a shaky foundation. I think the problem is that Auweia could also be a subroutine; perl.exe cannot tell until runtime.

Further Update

Okay, from @Schwern's instructive comments below I learnt that PPI is just a tokenizer, and you can build upon it if you accept its limitations.

Upvotes: 5

Views: 3133

Answers (2)

Mark Fowler
Mark Fowler

Reputation: 1264

You don't say what server enviroment you're running under, but from what you say it sounds like you could do with preloading all your modules in advance before executing any individual pages. Not only would this prevent the problems you're describing (where every script has to remember to load all the modules it uses) but it would also save you memory.

In pre-forking servers (as is commonly used with mod_perl and Apache) you really want to load as much of your code before your server forks for the first time so that the code is stored once in copy-on-write shared memory rather than mulitple times in each child process when it is loaded on demand.

For information on pre-loading in Apache, see the section of Practical mod_perl

Upvotes: 0

Schwern
Schwern

Reputation: 164629

Testing is the only answer worth the effort. If the code contains mistakes like forgetting to load a class, it probably contains other mistakes. Whatever the obstacles, make it testable. Otherwise you're patching a sieve.

That said, you have two options. You can use Class::Autouse which will try to load a module if it isn't already loaded. It's handy, but because it affects the entire process it can have unintended effects.

Or you can use PPI to scan your code and find all the class method calls. PPI::Dumper is very handy to understand how PPI sees Perl.

use strict;
use warnings;

use PPI;
use PPI::Dumper;

my $file = shift;
my $doc = PPI::Document->new($file);

# How PPI sees a class method call.
#    PPI::Token::Word      'Class'
#    PPI::Token::Operator  '->'
#    PPI::Token::Word      'method'
$doc->find( sub {
    my($node, $class) = @_;

    # First we want a word
    return 0 unless $class->isa("PPI::Token::Word");

    # It's not a class, it's actually a method call.
    return 0 if $class->method_call;

    my $class_name = $class->literal;

    # Next to it is a -> operator
    my $op = $class->snext_sibling or return 0;
    return 0 unless $op->isa("PPI::Token::Operator") and $op->content eq '->';

    # And then another word which PPI identifies as a method call.
    my $method = $op->snext_sibling or return 0;
    return 0 unless $method->isa("PPI::Token::Word") and $method->method_call;

    my $method_name = $method->literal;

    printf "$class->$method_name seen at %s line %d.\n", $file, $class->line_number;
});

Upvotes: 10

Related Questions