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

Hi, Monks. . . This seems like a FAQ but I'll be doggoned if I could find it anywhere. I have a script that will be taking text from a configuration file which will then be executed remotely (or locally) as a perl snippet. How can I syntax check it? eval would compile it and run it (though the stages that it completes depends on the syntax used - either of eval BLOCK or eval EXPR). E.g.:
my $str = <<'CODEIMPORT'; # this code block would be dynamic print "Don't run me!\n"; prnt 'Just check me for errors\n": CODEIMPORT # eval $str; # gives a run-time error eval { $str; }; # compiles (?) - but does not check syntax # I just want to check for syntax - # it will evaluated later and in a new/different context # (e.g., passed to a different script)
Here's a discussion from the Original Monks regarding eval in this context. Am I overlooking the obvious? I know I could shell out to perl -wc but it seems to be an overkill since I really just want to check that one little code block. I thought the B: or O: modules might help but I can't figure out how they might. Ideas?
-- Andy

Replies are listed 'Best First'.
•Re: Embedded Syntax Checker?
by merlyn (Sage) on Apr 21, 2004 at 15:00 UTC
    Are you willing to accept the risk that such code may contain BEGIN or use, and thus execute the code even while you're trying to compile it?

    Perhaps you should look at doing this in a Safe compartment that cannot execute anything.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

Re: Embedded Syntax Checker?
by sgifford (Prior) on Apr 21, 2004 at 15:28 UTC
    One way would be to wrap it in a sub. Then eval would compile the sub, and calling the sub would execute it:
    #!/usr/bin/perl -Tw use strict; my @code = ( 'for(my $i=0;$i<10;$i++) { print $i," "; }; print "\n";', 'blarmityschlammit', 'for(my $i=0;$i<10;$i++) { print $i,"\n";', # Missing closing brace '}; print "code4 sneaky code\n"; sub { print "code4 regular code\n";' ); for my $i (0..$#code) { print "\n** Compiling code block $i\n"; my $user_code = eval "sub { $code[$i] };"; if ($@) { warn "Bad user code block: $@\n"; next; } print "** Running code block $i\n"; $user_code->(); }

    As the last example demonstrates, it's possible to break out of this and execute code at compile-time, but it's unlikely to happen accidentally. If you don't have malicious code, something like this will probably work OK. If you do, as merlyn said, you'll need to use the Safe module.

      Thanks for your response. I didn't think of code being executed in the BEGIN block so that's a good point - an interesting, example, too. In my case, there shouldn't be any intentional maliciousness - in fact, the user will have no more (or less) access than they would under normal circumstances.

      I guess wrapping in sub was the key that I overlooked. Thanks a lot! Thanks also to merlyn and everyone else who responded so quickly!


      -- Andy

        Note, there are many ways a BEGIN block can get executed without ever having the word BEGIN in your code. For example:
        # In Test.pm package Test; sub import { print "Boo!\n"; } 1; -------------- # In your script use Test;

        Where's the BEGIN?

        There actually was a question/meditation on here about 2 years ago on this very topic. I don't remember the details, but the OP was remarking that he was running into a forking/threading/caching/whatever issue. It turned out that there was a module about 4 levels back into the uses that was doing something he didn't want. And, it was happening at compile-time, so he couldn't disable it.

        ------
        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

      Andy, I'd really like to know why you are doing this at all... seems that accepting input as perl code over the 'net (or wherever) is a very bad idea. For those that need access to install plugins (this sounds like what you are doing), allow the filesystem to enforce the protections -- that is -- require them to have an account on the box. If they must do this remotely, this is what version control systems and SSH are for. Even Net::SSH if you must. Maybe if I understood the goal a little better...

      and if you are running say, a student test program, if your students OWNZOR your box you might as well just get them expelled :)

      But still, I do agree it would be nice to not have these BEGIN block vulnerabilities, but checking syntax without BEGIN blocks would look like broken syntax for many things that used BEGIN.

Re: Embedded Syntax Checker?
by borisz (Canon) on Apr 21, 2004 at 15:02 UTC
    Perhaps you can use perl -c. Just from the manpage
    -c causes Perl to check the syntax of the program and then exit without executing it. Actually, it will execute "BEGIN", "CHECK", and "use" blocks, because these are considered as occurring outside the execu- tion of your program. "INIT" and "END" blocks, how- ever, will be skipped.
    Boris
      Thanks - I was trying to avoid shelling out. perl -c is exactly what I want, but I want to use it as a function, ala:
      perlminusc($str); # syntax check _only_ of $str

      Thanks!


      -- Andy
Re: Embedded Syntax Checker?
by bronto (Priest) on Apr 21, 2004 at 15:09 UTC
    Do a perl -cw yourscript.pl before doing anything else

    Ciao!
    --bronto


    The very nature of Perl to be like natural language--inconsistant and full of dwim and special cases--makes it impossible to know it all without simply memorizing the documentation (which is not complete or totally correct anyway).
    --John M. Dlugosz