Bug in Perl Causes a Small Class::Sniff Issue

Ovid on 2009-02-16T10:16:15

Jesse Vincent sent a bug report about Class::Sniff detecting non-existent packages.

Seems Jesse has a lot of code like this:

eval "require RT::Ticket_Overlay";
if ($@ && $@ !~ qr{^Can't locate RT/Ticket_Overlay.pm}) {
    die $@;
};

Well, that seems quite reasonable. Except for this:

#!/usr/bin/env perl -l

use strict;
use warnings;

print $::{'Foo::'} || 'Not found';
eval "use Foo";
print $::{'Foo::'} || 'Not found';
eval "require Foo";
print $::{'Foo::'} || 'Not found';
__END__
Not found
Not found
*main::Foo::

That's right. Attempting the require a non-existent module via a string eval creates a symbol-table entry. Aristotle told me he was astonished that no one had caught this before. Frankly, I just think that not enough people are trying to do introspection in Perl.

This one will be tricky to work around. I thought "if the module doesn't actually exist, can I check to see if @ISA is there?" It gets automatically created for every package, but since the module representing that package doesn't exist, maybe it won't? No such luck:

print defined *NoSuchModule::ISA{ARRAY} ? 'Yes' : 'No';
print defined *NoSuchModule::xxx{ARRAY} ? 'Yes' : 'No';

That always prints "Yes" and then "No". @ISA is always created for every package if you try to access it. Darn.

I thought I could check for the module's existence in %INC, but inlined packages don't show up there, either (unless the author explicitly puts them there).

The only thing I can think of is this curious line:

print scalar keys %Foo::;

If you do that with a non-existent package which nonetheless has a symbol table entry, it still has no keys in its symbol table. However, if you do that with a module which exists but failed to load, you will probably have a few symbol table entries. This still doesn't quite solve the problem.

So how do I detect if a module in a symbol table failed to load? I'm not sure if I can. If I simply check to see if there are any keys in the symbol table, that should be enough, right? If someone evals "require $badmodule" and that require fails due to compilation errors, they'll exit or die, right? (too optimistic, I know)

Update: Alias, I've not forgotten about your request for ambiguous method detection. I've just not added it yet :)


its been caught

jdv on 2009-02-16T14:45:48

I tripped on this a couple years ago. It wasn't a show-stopper and I wasn't sure it was actually a bug at the time.

autovivifying symbol tables

hanekomu on 2009-02-16T22:59:43

Actually, the line

print scalar keys %Foo::;

will autovivify the symbol table, so if you use that anywhere in your test program, it will print "*main::Foo::" all three times.

base.pm

jjore on 2009-02-23T21:59:54

This is another reason why base.pm is deprecated. It has to solve the same problem and there's no way to know if a class is partial.

You'll also see this if the class exists but it failed during loading. You can still find the parts taht succeeded in the symbol table but you'll just be missing "the rest" and you don't have any way to know how much else "the rest" is.

Re:base.pm

Ovid on 2009-02-23T22:09:59

Yeah, partial compilation has bitten me hard at times. Very unfortunate. I wonder if it's useful to have a module which attempts to clean up after bad loads?

Re:base.pm

jjore on 2009-02-24T01:17:21

Snapshots the symbol table and reverts it on failure? I wish git worked /inside/ perl.