mikkoi has asked for the wisdom of the Perl Monks concerning the following question:

I am trying to keep my distro Env::Dot backwards compatible to Perl 5.10.1. In order to do better error messaging, I have a nested evals which capture croaks from deeper in the program. Normal exception throw/catch stuff. I noticed this strange behavior in Perl 5.10.1:
#!/usr/bin/env perl
use strict;
use warnings;

use Carp;

local $@ = undef;
eval {
    local $@ = undef;
    eval {
        # croak __LINE__ . ':DEEPEST ERROR'; 1;
        croak 'DEEPEST ERROR'; 1;
    } or do {
        my $e = $@;
        warn __LINE__ . ":e=$e";
        croak $e;
    }; 1;
} or do {
    my $e = $@;
    warn __LINE__ . ":e=$e";
    croak $e;
};

Output:
16:e=DEEPEST ERROR at ./test-recursive-evals.pl line 13.
        eval {...} called at ./test-recursive-evals.pl line 14
        eval {...} called at ./test-recursive-evals.pl line 19
21:e= at ./test-recursive-evals.pl line 21.
 at ./test-recursive-evals.pl line 22.

In later Perl 5.14.4 this works as it should.

Is there a way around this bug, besides removing the nested evals? Or perhaps it is time to stop supporting older than 5.14 or 5.16?

Replies are listed 'Best First'.
Re: Nested Evals in Old Perl 5.10
by eyepopslikeamosquito (Archbishop) on Aug 24, 2024 at 22:49 UTC

      I want to stick to using only Perl distro modules, so Try::Tiny is not "allowed". Otherwise, Try::Tiny is excellent and I use it in many other projects.

Re: Nested Evals in Old Perl 5.10
by hv (Prior) on Aug 25, 2024 at 04:03 UTC

    It's going back quite a while, but I think I remember a bug something along the lines of the error getting assigned to a localized $@ before it gets de-localized. Looking through the changelogs I found the section Exception handling in pod/perl5140delta.pod which states:

    When an exception is thrown inside an eval, the exception is no longer at risk of being clobbered by destructor code running during unwinding. Previously, the exception was written into $@ early in the throwing process, and would be overwritten if eval was used internally in the destructor for an object that had to be freed while exiting from the outer eval. Now the exception is written into $@ last thing before exiting the outer eval, so the code running immediately thereafter can rely on the value in $@ correctly corresponding to that eval. ($@ is still also set before exiting the eval, for the sake of destructors that rely on this.)

    (There's more, you should probably read the whole section to see what's relevant to your case.)

    Assuming the bug fixed here is what you're seeing, it may be that you could avoid it by redesigning the inner eval to avoid local $@. But it's not clear to me if that would be enough.

Re: Nested Evals in Old Perl 5.10
by perlfan (Parson) on Aug 25, 2024 at 07:54 UTC
    Out of curiosity, does it work if you use die over croak? You are using warn rather than carp; I figure you stick with the pair provided by Carp.

    Anyway, I just noticed the inconsistency. Maybe it's nothing.

      No difference if I switch croak to die.

      warn is only used to print to stderr.

        Cool, thank you for checking for that.
Re: Nested Evals in Old Perl 5.10
by perlfan (Parson) on Aug 25, 2024 at 20:01 UTC
    Regarding Perl version support, based on some cursory research 5.14 or greater has been the default on the likes of RHEL since 2014 and on Debian distros since 2012 or 2013. I am not suggesting you drop compatibility, but it seems pushing the oldest supported version to 5.14 is extremely reasonable.