Search code examples
perleval

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?


Solution

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