Francisco Zarabozo
Francisco Zarabozo

Reputation: 3748

How to override exit() call in Perl eval block

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?

Upvotes: 11

Views: 2319

Answers (2)

ikegami
ikegami

Reputation: 386331

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.

Upvotes: 12

Slade
Slade

Reputation: 1364

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.

Upvotes: 7

Related Questions