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

I was working on a program that needed to check the syntax of a short perl script prior to accepting it as input. To do this test the script was saved to a file and "perl -c -w script.pl" was called. (via a system call to the shell) The -c flag is supposed to cause the script to be compiled, but not executed.

Well this worked ok, but I had a problem where a user had made a call to a function that did not exist. This wasn't caught until the script was executed, not good!

So I started trying to figure out how this slipped past, and I couldn't. Perl does not notice a non-existent function (or subroutine or whatever you want to call it) at compile time. Does anyone know why this is?

I was thinking it might be because you could somehow dynamically create the function or something... but I don't know how to do that either, not including the use of eval.

Replies are listed 'Best First'.
Re: -c
by BBQ (Curate) on May 09, 2000 at 03:50 UTC
    I think what you want is to eval the entire file right? I'm supposing that you're trying to catch bad script automagically from another script. This is what I've tested:

    Consider you have a file called testee.pl with the following code:
    #!/usr/bin/perl sub Foo { print "foo\n"; } Bar(); 1;
    And that you have your tester file called tester.pl:
    #!/usr/bin/perl use strict; eval { require 'testee.pl'; }; if ($@) { warn(); } print "\nDone!\n";
    And your result when running tester.pl is:
    20:54:50.70@admin:> perl tester.pl Undefined subroutine &main::Bar called at testee.pl line 7. ...caught at tester.pl line 9. Done! 20:54:56.11@admin:>
    The only problem I see with this method is that you're testee.pl must return a true statement (hence the 1; at the end). But other than that it seems to do what you want!

    UPDATED:

    The above doesn't work. Adam has pointed out that the code cannot be executed. It this case, the eval would make everything a big mess...


    #!/home/bbq/bin/perl
    # Trust no1!
      Yeah, I suppose I should have made that more clear.
      The scripts can not be run on the developer's machines, for a wide variety of reasons. We catch the error later during tests of the product, but we aught to be able to catch simple errors earlier then that.

      Hey vroom, why can't I 'maintain' that post? I would have liked to just added a clarifier at the end there. Oh well.

Re: -c
by btrott (Parson) on May 09, 2000 at 01:53 UTC
    I think there are actually several reasons why. But I only really understand one, so that's the only one I'll comment on. :)

    You're right when you say that you could dynamically create the function. You can do this using a special subroutine called AUTOLOAD--if you call a function in a particular package, and that function doesn't exist, Perl looks for an AUTOLOAD function in your package. Among other things, you can use that AUTOLOAD function to create other functions on the fly and run them. From perltoot, in case you want a clearer explanation:

    When Perl tries to call an undefined function in a particular package and that function is not defined, it looks for a function in that same package called AUTOLOAD. If one exists, it's called with the same arguments as the original function would have had.
    Another way to dynamically create functions is eval. Things that you wrap in an eval aren't executed until runtime (ie. not at compile time), so if you're using an eval to dynamically create a subroutine that you'll later call, there's no possible way for Perl to know about this function. It's perfectly valid Perl, though, so you won't get an error.

    See, I could write this:

    #!/usr/local/bin/perl -w use strict; my $code = <<CODE; sub foo { print "in foo\n"; } CODE eval $code; foo();
    At compile time, Perl doesn't know that a subroutine called foo is going to exist. But it doesn't complain. And I can run it:
    % foo.pl in foo
    and everything's fine.

    In terms of your particular problem... I'm not sure what to suggest. Would it be possible for you to use eval to check the syntax of the script? Then you could catch any errors, but your own script wouldn't die.

RE: -c
by merlyn (Sage) on May 09, 2000 at 17:09 UTC
    If your program contains:
    require "foo.pl"; &do_this("with that");
    and foo.pl contains:
    sub do_this { print @_; } 1;
    Then this code must pass -c, because there's syntactically nothing wrong with it, and it cannot detect that foo.pl defines do_this, because that's not pulled in during compile time (it doesn't even need to exist!).

    Finding a subroutine doesn't start until it gets invoked. This has long been the way, necessarily.

    -- Randal L. Schwartz, Perl hacker

      That's what I was afraid of. Thanks!
RE: -c
by Maqs (Deacon) on May 09, 2000 at 13:40 UTC
    In my humble opinion you can check the existance of a subroutine
    or anything else verifying the symbol table of the given routine
    in a way it is described in Programming Perl book (5.1.1. Symbol
    tables)
    .
    i.e.:
    #!/usr/bin/perl
    &greet;

    sub greet () {#some stuff here
    };
    foreach $symname (sort keys %main::) {
    local *sym = $main::{$symname};
    print "\&$symname is defined\n" if defined &sym;
    }
    ----
    This code will print out: "&greet is defined". You see, you can modufy that code to meet your taste. But the main
    problem is that I didn't tested it with the incapsulated packages.

    --
    With best regards
    Maqs.