How can I smoke out undefined subroutines?

929 views Asked by At

I want to scan a code base to identify all instances of undefined subroutines that are not presently reachable.

As an example:

use strict;
use warnings;

my $flag = 0;
if ( $flag ) {
  undefined_sub();
}

Observations

  • When $flag evaluates to true, the following warning is emitted:

    Undefined subroutine &main::undefined_sub called at - line 6
    

    I don't want to rely on warnings issued at run-time to identify undefined subroutines

  • The strict and warnings pragmas don't help here. use strict 'subs' has no effect.

  • Even the following code snippet is silent

    $ perl -Mstrict -we 'exit 0; undefined_sub()'
    
3

There are 3 answers

2
zdim On BEST ANSWER

Perhaps Subroutines::ProhibitCallsToUndeclaredSubs policy from Perl::Critic can help

This Policy checks that every unqualified subroutine call has a matching subroutine declaration in the current file, or that it explicitly appears in the import list for one of the included modules.

This "policy" is a part of Perl::Critic::StricterSubs, which needs to be installed. There are a few more policies there. This is considered a severity 4 violation, so you can do

perlcritic -4 script.pl

and parse the output for neither declared nor explicitly imported, or use

perlcritic -4 --single-policy ProhibitCallsToUndeclaredSubs script.pl

Some legitimate uses are still flagged, since it requires all subs to be imported explicitly.

This is a static analyzer, which I think should fit your purpose.

1
Calle Dybedahl On

What you're asking for is in at least some sense impossible. Consider the following code snippet:

( rand()<0.5 ? *foo : *bar } = sub { say "Hello World!" };

foo();

There is a 50% chance that this will run OK, and a 50% chance that it will give an "Undefined subroutine" error. The decision is made at runtime, so it's not possible to tell before that what it will be. This is of course a contrived case to demonstrate a point, but runtime (or compile-time) generation of subroutines is not that uncommon in real code. For an example, look at how Moose adds functions that create methods. Static source code analysis will never be able to fully analyze such code.

B::Lint is probably about as good as something pre-runtime can get.

3
ikegami On

To find calls to subs that aren't defined at compile time, you can use B::Lint as follows:

a.pl:

use List::Util qw( min );

sub defined_sub { }
sub defined_later;
sub undefined_sub;

defined_sub();
defined_later();
undefined_sub();
undeclared_sub();
min();
max();              # XXX Didn't import
List::Util::max();
List::Util::mac();  # XXX Typo!

sub defined_later { }

Test:

$ perl -MO=Lint,undefined-subs a.pl
Undefined subroutine 'undefined_sub' called at a.pl line 9
Nonexistent subroutine 'undeclared_sub' called at a.pl line 10
Nonexistent subroutine 'max' called at a.pl line 12
Nonexistent subroutine 'List::Util::mac' called at a.pl line 14
a.pl syntax OK

Note that this is just for sub calls. Method calls (such as Class->method and method Class) aren't checked. But you are asking about sub calls.

Note that foo $x is a valid method call (using the indirect method call syntax) meaning $x->foo if foo isn't a valid function or sub, so B::Lint won't catch that. But it will catch foo($x).