http://qs1969.pair.com?node_id=391783

Sprad has asked for the wisdom of the Perl Monks concerning the following question:

"perl -c" is useful for doing a quick syntax check, and I use it often as a pre-test test. But one common error that it doesn't catch is undefined subroutines. For example, this code gets a thumbs up from perl -c:
#!/usr/bin/perl use strict; use warnings; foo();
But of course, it fails upon execution. Is there a way to catch errors like this without executing the program?

---
A fair fight is a sign of poor planning.

Replies are listed 'Best First'.
Re: Detect undefined subroutines
by dragonchild (Archbishop) on Sep 17, 2004 at 14:46 UTC
    Not really, and there's a good reason why. Perl allows you to define subroutines on the fly, but doesn't know what those subroutines will be called until it gets there. For example, how would you have Perl behave in the following situation:
    #!perl use strict; use warnings; my $x = shift; my $sub_name = $x ? 'foo' : 'bar'; eval "*$sub_name = sub { print \"Hello!\n\" };"; foo();

    If the script is passed in a true value, then it will run correctly. If it's passed in a false value, it will not run correctly. But, you want Perl to know what to do before it's been given all the information it might need.

    Now, I wouldn't mind seeing an additional optional option added to strict that requires all subroutines, but not methods, to be defined by the time CHECK is complete. But, that's another story.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

    I shouldn't have to say this, but any code, unless otherwise stated, is untested

      Here's a sketch of what this should look like. It might even work, as-is.

      your_script.pl

      use Strict::Subs; foo(); # The subroutine main::foo might be called but it doesn't exist + yet.

      Strict::Subs.pm

      package Strict::Subs; use strict; use warnings; use Exporter; use B::Utils qw( all_roots anon_subs ); use vars qw( %Violations @ISA @EXPORT $VERSION ); BEGIN { $VERSION = '0.01'; @ISA = 'Exporter'; @EXPORT = 'strict_subs'; 1; } sub import { eval q[ CHECK { # Provide a named way to trigger this apply_strict_subs(); 1; } ]; } sub strict_subs () { 'strict_subs()'; } sub apply_strict_subs { local %Violations; # All named subroutines. { my %named_subs = all_roots(); _strict_sub( $_ ) for values %named_subs; } # All anonymous subroutines _strict_sub( $_->{'root'} ) for anon_subs(); 1; } sub _strict_sub { my $root = shift; walkoptree_filtered( $root, \ &_find_strict_sub_invocation, \ &_apply_strict_subs ); 1; } sub _find_strict_sub_invocation { my $op = shift; opgrep( { name => 'gv' }, $op ) and do { my $gv = $op->sv; ( $gv->NAME eq 'strict_subs' and $gv->STASH->NAME eq 'Strict' ) } } sub _apply_strict_subs { my $op = shift; walkoptree_filtered( $_, \ &_find_subroutine_calls, \ &_validate_subroutine_existance ) for $op->younger_siblings(); 1; } sub _find_subrountine_calls { opgrep( { name => 'gv' next => { name => 'entersub ' } } ); } sub _validate_subroutine_existance { my $op_gv = shift; my $gv = $op_gv->sv; my $name = $gv->STASH->NAME . '::' . $gv->NAME; no strict 'refs'; *{$name}{'CODE'} or warn "The subroutine $name might be called but it doesn't e +xist yet.\n" } package B::Utils; sub younger_siblings { my $op = shift; my @siblings; for ( my $sibling = $op->sibling; $sibling->oldname ne 'null'; $op = $sibling ) { push @siblings, $sibling; } @siblings; }

        Check out B::Lint. Here's an excerpt from the manual:

        undefined-subs This option warns whenever an undefined subroutine is invoked. This option will only catch explicitly invoked subroutines suc +h as "foo()" and not indirect invocations such as "&$subref()" o +r "$obj->meth()". Note that some programs or modules delay definition of subs until runtime by means of the AUTOLOAD mechanism.

        ihb

        Read argumentation in its context!

Re: Detect undefined subroutines
by Limbic~Region (Chancellor) on Sep 17, 2004 at 14:40 UTC
    Sprad,
    Since you didn't indicate you knew why you didn't receive a warning or an error, here is some code to illustrate the point:
    #!/usr/bin/perl use strict; use warnings; my $sub = 'foo'; { no strict 'refs'; *{ $sub } = sub { return 'bar' }; } print foo();
    The definition of a sub can be made at runtime so it is hard to check at compile time that it has been defined. I am not saying that there isn't a way to say "ok - well except for that", but if there is a way I am sure someone else will reply. It is likely a scary hack involving do'ing the file, parsing for things that look like sub calls, and then using Universal can to see if main->can( 'sub' ).

    Cheers - L~R

Re: Detect undefined subroutines
by jepri (Parson) on Sep 17, 2004 at 14:44 UTC
    There doesn't appear to be any way to do what you want. The trouble being that Perl's subs are dynamic, so it is completely fair to have a sub that is created on the fly, and only exists when the program is running.

    This is, after all, what happens when you import a module.

    A quick browse of CPAN didn't show up any module that would do run-time checks for functions to exist, so it sounds like you have 'an opportunity, rather than a problem'.

    ___________________
    Jeremy
    I didn't believe in evil until I dated it.

      That must have been a quick browse :)

      Class::Inspector has a bunch of stuff you could use to check for various things.

      If your classes don't do any run-time function munging, Test::ClassAPI will let you check for the existance of methods throughout an entire API, and will warn you if it finds extras.
        It was indeed a quick browse, combined with an old memory of looking quite hard for this. This time I went through, paying particular attention to the Sub:: hierachy, hence missing them tucked away in Class::. I've written objects before in perl, but I don't think of methods and functions(sub{}s), classes and namespaces as being interchangable, which is kind of implied with your naming scheme.

        I do realise that they more-or-less are, but I learned on GWBasic first, so if I can "call" it, I think "It's a sub" :)

        Anyway, cool modules and I'm gonna go take the back off them and see what makes them tick. But I'll do it later, I'm too tired now.

        ___________________
        Jeremy
        I didn't believe in evil until I dated it.

Re: Detect undefined subroutines
by tilly (Archbishop) on Sep 17, 2004 at 16:10 UTC
    Not without completely changing how you code in a way that will annoy everyone else.

    If you're willing to do that then never call routines directly, instead do them through variables...

    #!/usr/bin/perl use strict; use warnings; my $fooz = sub { print "Hello, world\n"; }; $foo->();
    And now -c catches the error.

    The solution is ugly enough that everyone I know is willing to just live with the problem instead.

      You could use

      sub foo { ... } *foo->(...);
      to receive compile-time warnings, assuming you don't use any other global datatype called foo (and doesn't try to call the same undefined subroutine twice).

      Update: boldened "warnings" and added parenthesis.

      ihb

      Read argumentation in its context!

        Interesting idea, but it doesn't seem to work.
        tilly@(none):~$ perl -ce 'use strict; *foo->()' -e syntax OK tilly@(none):~$ perl -e 'use strict; *foo->()' Undefined subroutine &main::foo called at -e line 1.
        Tested with Perl 5.8.4.

        UPDATE I see, my oversight.

Re: Detect undefined subroutines
by johnnywang (Priest) on Sep 17, 2004 at 16:36 UTC
Re: Detect undefined subroutines
by Anonymous Monk on Sep 17, 2004 at 16:55 UTC
    Many people have asked this this question and many have gotten the same useless answers. It is true that subs can be defined on the fly and that can be made hard to check things at compile time. HOWEVER, variables can also be made on the fly, so it seems like a stupid argument, because 'use strict' can enforce that variables be declared somewhere.
    The place where there is really a problem is with method calls, because you can not always know what type of object something is at compile time. For example:
    sub foo { my $thing = shift; $thing->bar(); }
    Here "$thing" could be any type of object, so it is impossible for the compiler to know what package to check for the existance of the method.
      HOWEVER, variables can also be made on the fly, so it seems like a stupid argument, because 'use strict' can enforce that variables be declared somewhere.

      Strictures enforces this because it is much less likely you will want to use a variable that has been defined on the fly than a subroutine that has. In fact, defining subroutines on the fly is the basis of several styles of programming. I haven't heard of a style that depends on defining variables on the fly. And, frankly, if you did, you'd use a hash. :-)

      ------
      We are the carpenters and bricklayers of the Information Age.

      Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

      I shouldn't have to say this, but any code, unless otherwise stated, is untested

Re: Detect undefined subroutines
by educated_foo (Vicar) on Sep 18, 2004 at 17:10 UTC
    Don't use parens:
    ~/info> perl -Mstrict -c -e 'foo()' -e syntax OK ~/info> perl -Mstrict -c -e 'foo' Bareword "foo" not allowed while "strict subs" in use at -e line 1. -e had compilation errors.

      That doesn't always work due to indirect object syntax. Here's an example of when lack of parenthesis doesn't help, and the call instead gets interpreted as a method call rather than a function call.

      $ perl -Mstrict -cwe 'my $bar; foo $bar;' -e syntax OK

      ihb

      Read argumentation in its context!