Perl: $SIG{DIE, eval {} et stack trace

j'ai un morceau de code Perl un peu comme le suivant (fortement simplifié): il y a quelques niveaux d'appels de sous-programmes imbriqués (en fait, méthodes), et certains des appels internes font leur propre Manipulation d'exception:

sub outer { middle() }

sub middle {
    eval { inner() };
    if ( my $x = $@ ) { # caught exception
        if (ref $x eq 'ARRAY') {
            print "we can handle this ...";
        }
        else {
            die $x; # rethrow
        }
    }
}

sub inner { die "OH NOES!" }

maintenant je veux changer ce code pour qu'il fasse ce qui suit:

  • imprimer une trace complète de la pile pour chaque exception qui "remonte" jusqu'au niveau le plus éloigné (). Plus précisément, la trace de la pile devrait arrêter au premier niveau de "eval { }".

  • ne pas avoir à changer l'implémentation de l'un des niveaux internes.

en ce moment, la façon dont je fais ceci est d'installer un__DIE__ gestionnaire à l'intérieur de l' outer sujet:

use Devel::StackTrace;

sub outer {
    local $SIG{__DIE__} = sub {
        my $error = shift;
        my $trace = Devel::StackTrace->new;
        print "Error: $errorn",
              "Stack Trace:n",
              $trace->as_string;
    };
    middle();
}

[ EDIT: j'ai fait une erreur, le code ci-dessus fait ne pas travailler de la façon que je veux, il contourne en fait la manipulation d'exception de la middle sub. Alors je suppose que la question devrait vraiment être: est-ce que le comportement que je veux est même possible?]

Cela fonctionne parfaitement, le seul problème est que, si je comprends bien le docs, il s'appuie sur un comportement qui est explicitement déprécié, à savoir le fait que __DIE__ les gestionnaires sont déclenchées même pour "die"s à l'intérieur de "eval { }"s, qu'ils ne devraient vraiment pas. Les deux perlvar et perlsub indiquer que ce comportement pourrait être supprimé dans les versions futures de Perl.

y a-t-il une autre façon d'y parvenir sans compter sur un comportement déprécié, ou est-ce une économie sur laquelle compter même si les docs disent le contraire?

12
demandé sur trendels 2009-06-09 20:54:34

3 réponses

C'est sûr de se fier à tout ce que la documentation dit est déprécié. Le comportement pourrait (et va probablement) changer dans une version future. S'appuyer sur un comportement déprécié vous enferme dans la version de Perl que vous utilisez aujourd'hui.

Malheureusement, je ne vois pas un moyen de contourner ce qui répond à vos critères. La" bonne " solution est de modifier les méthodes internes pour appeler Carp::confess au lieu de die déposer le custom $SIG{__DIE__} manipulateur.

use strict;
use warnings;
use Carp qw'confess';

outer();

sub outer { middle(@_) }

sub middle { eval { inner() }; die $@ if $@ }

sub inner { confess("OH NOES!") }
__END__
OH NOES! at c:\temp\foo.pl line 11
    main::inner() called at c:\temp\foo.pl line 9
    eval {...} called at c:\temp\foo.pl line 9
    main::middle() called at c:\temp\foo.pl line 7
    main::outer() called at c:\temp\foo.pl line 5

puisque vous êtes en train de mourir de toute façon, vous pouvez ne pas avoir besoin de piéger l'appel à inner(). (Vous ne le faites pas dans votre exemple, votre code réel peut différer.)

dans votre exemple, vous essayez de retourner des données via $@. Vous ne pouvez pas le faire. Utilisez

my $x = eval { inner(@_) };

à la place. (Je suppose que c'est juste une erreur de simplifier le code assez pour le poster ici.)

8
répondu Michael Carman 2009-06-09 17:39:39

mise à jour: j'ai changé le code pour remplacer die globalement pour que les exceptions des autres paquets puissent aussi être détectées.

la suite à faire ce que vous voulez?

#!/usr/bin/perl

use strict;
use warnings;

use Devel::StackTrace;

use ex::override GLOBAL_die => sub {
    local *__ANON__ = "custom_die";
    warn (
        'Error: ', @_, "\n",
        "Stack trace:\n",
        Devel::StackTrace->new(no_refs => 1)->as_string, "\n",
    );
    exit 1;
};

use M; # dummy module to functions dying in other modules

outer();

sub outer {
    middle( @_ );
    M::n(); # M::n dies
}

sub middle {
    eval { inner(@_) };
    if ( my $x = $@ ) { # caught exception
        if (ref $x eq 'ARRAY') {
            print "we can handle this ...";
        }
        else {
            die $x; # rethrow
        }
    }
}

sub inner { die "OH NOES!" }
10
répondu Sinan Ünür 2009-06-09 18:18:21

Notez que primordial die ne captera que les appels réels vers die, erreurs Perl comme dereferencing undef.

je ne pense pas que le cas général est possible, toute l'équipe de point de eval est de consommer des erreurs. Vous pourriez être en mesure de compter sur le comportement déprécié pour exactement cette raison: il n'y a pas d'autre moyen de le faire pour le moment. Mais je ne peux trouver aucun moyen raisonnable d'obtenir une trace de pile dans chaque cas sans casser quoi que ce soit le code de traitement des erreurs existe déjà aussi loin que possible dans la pile.

4
répondu Eevee 2009-06-09 18:17:04