11

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?

0

2 Answers 2

12

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.

3
  • 1
    Very nice, thanks again. :-) So, if the evaled code explicitly calls CORE::exit, I guess I won't be able to stop that. Right? Commented Aug 19, 2014 at 17:19
  • 1
    You'd have to install an opcode checker to change the ppaddr of exit opcodes, I think.
    – ikegami
    Commented Aug 25, 2014 at 1:02
  • Great solution, but seems a bit too long to be called a simple solution. Commented Jun 3, 2022 at 5:09
7

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.

1
  • Any way to treat exit like die? (So capture and keep going as normal with the rest of the program) Commented Jun 3, 2022 at 5:11

Not the answer you're looking for? Browse other questions tagged or ask your own question.