Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to override exit() call in Perl eval block

Tags:

perl

eval

I need to eval some code in Perl that might some times contain an exit() call in it. A very simplified example of this would be:

use strict;
use warnings;


eval "some_function()";
die $@ if $@;

print "Still alive!\n";


sub some_function {
    print "Hello from some_function\n";
    exit;
}

I never get to "Still alive!" because of the exit() call.

I tried setting some keys in %SIG (QUIT, STOP, TERM, BREAK, etc) but that didn't work. I also attempted to redefine CORE::exit with no success.

How can I prevent an exit() call from being effective when being evaled?

like image 557
Francisco Zarabozo Avatar asked Aug 19 '14 04:08

Francisco Zarabozo


People also ask

What is $@ in Perl?

$@ The Perl syntax error or routine error message from the last eval, do-FILE, or require command. If set, either the compilation failed, or the die function was executed within the code of the eval.

What does eval return in Perl?

In both forms, the value returned is the value of the last expression evaluated inside the mini-program; a return statement may also be used, just as with subroutines. The expression providing the return value is evaluated in void, scalar, or list context, depending on the context of the eval itself.


2 Answers

You can override exit, but you must do so at compile-time. So use a flag to signal whether the override is active or not.

our $override_exit = 0;
BEGIN { 
   *CORE::GLOBAL::exit = sub(;$) {
      CORE::exit( $_[0] // 0 ) if !$override_exit;

      die "EXIT_OVERRIDE\n";
   };
}

eval {
   local $override_exit = 1;
   some_function();
};

if ( !$@ ) {
   say "Normal return";
} elsif ( $@ eq "EXIT_OVERRIDE\n" ) {
   say "Exit called";
} else {
   print "Exception: $@";
}

But that creates an exception that might be caught unintentionally. So let's use last instead.

our $override_exit = 0;
BEGIN { 
   *CORE::GLOBAL::exit = sub(;$) {
      CORE::exit( $_[0] // 0 ) if !$override_exit;

      no warnings qw( exiting );
      last EXIT_OVERRIDE;
   };
}

my $exit_called = 1;
EXIT_OVERRIDE: {
   local $override_exit = 1;
   eval { some_function() };
   $exit_called = 0;
}

if ( $exit_called ) {
   say "Exit called";
} elsif ( $@ ) {
   print "Exception: $@";
} else {
   say "Normal return";
}

Note that eval BLOCK is used to catch exceptions. eval EXPR is used to compile code.

like image 118
ikegami Avatar answered Oct 18 '22 02:10

ikegami


exit isn't meant to be trapped, so eval isn't the solution here. You could put the remaining code you need to run in an END block:

some_function();
END { print "Still alive! For now...\n"; }

sub some_function {
    print "Hello from some_function\n";
    exit;
}

But if you absolutely, positively need to prevent exit from killing the script, you'll have to redefine exit() at compile time:

BEGIN { *CORE::GLOBAL::exit = sub (;$) { } } # Make exit() do nothing
some_function();
print "Still alive!\n"; # Gets printed

*CORE::GLOBAL::exit = *CORE::exit; # Restore exit()
exit;
print "I'm dead here.\n"; # Doesn't get printed

sub some_function { exit }

The Test::Trap module from the CPAN can be used to encapsulate this bit of ugliness for you, if you're interested in a more robust solution. Personally I would locally patch the exiting some_function() to use croak instead, and maybe file a bug report with the patch if it's a module.

If you're just evaling user input and you don't want them to be able to call exit, verify that the string contains no calls to exit or to a subroutine that would indirectly exit, then eval it. Personally I'd be more afraid of unlink and fork than exit if the user is inputting arbitrary code to be evaluated.

like image 7
Slade Avatar answered Oct 18 '22 01:10

Slade