Perl die() call mysteriously not dying
Asked Answered
A

1

8

I was able to get to this short code after doing some serious debugging at work with a very obscure bug in the project. A die call that was not dying.

The issue happens only when calling script.pl. If you call Class_A directly, then the die call will be successful.

We need three files:

File 1: script.pl

use strict;
use warnings;
use lib '.';
use Class_A;

# This should not execute. Class_A should die at loading time
print "We shouldn't get here. Class_A shoud not load and die.\n";

File 2: Class_A.pm

package Class_A;
use strict;
use warnings;
use Class_B;

# This code SHOULD die:
my $p = Class_B->new;
$p->do_something->die_now;


1;

File 3: Class_B.pm

package Class_B;
use strict;
use warnings;

sub new {
    my $class = shift;
    bless {}, $class;
}

sub do_something {
    my $self = shift;
}

sub die_now {
    die "No soup for you!";
}

sub DESTROY {
    eval {
        1;
    };
}

1;

Notice the chained call at Class_A.pm line 8? Well, if you unchain it, then the code dies successfully. :-|

# This works. There should be no difference.
$p->do_something;
$p->die_now;

And, the final surprise was to find out that just by removing the eval call at Class_B.pm line 19, then things work as expected and the script dies.

I had the opportunity to test this in Perl 5.22.2, Perl 5.26.1 and Perl 5.32.0. For another wonderful surprise, this issue doesn't happen on 5.32.0 only.

Seriously, WT*? Any thoughts on what is going on here?

Agape answered 14/8, 2020 at 4:46 Comment(3)
Interesting! I am not sure the root cause, but the perldoc page on eval has this little statement: "It [eval] is also Perl's exception-trapping mechanism, where the die operator is used to raise exceptions.". Seems the combination of Die+Destructor+Eval is getting confused.... like maybe something is up with the nesting of eval from Die with the eval in Destroy.Bhili
You forget mixing it with the chained call, because if not chained, then everything works well.Agape
Tip: . is not the script's dir. Use use FindBin qw( $RealBin ); use lib $RealBin;.Donofrio
D
10

The code you posted doesn't exhibit the problem since 5.28.

The problem relates to $@ getting clobbered (using eval { }) during the unwind that occurs due to an exception. Apparently, unsetting $@ fools Perl into thinking no exception occurred.

According to perl528delta, $@ is now set after everything is destroyed, which prevents the destructor from clobbering $@. You can also prevent the destructor from clobbering $@ by adding local $@; (e.g. to support older versions of Perl).

Donofrio answered 14/8, 2020 at 5:24 Comment(2)
Interesting. Any thoughts on why it happens with the chained call, but not by unchaining it? Eval is still doing the same thing at the same time.Agape
Has to do with precisely when the variables on the stack are freed.Donofrio

© 2022 - 2024 — McMap. All rights reserved.