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?
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.)
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!" }
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.