13

次のような Perl コードがあります (大幅に簡略化されています): ネストされたサブルーチン呼び出し (実際にはメソッド) にはいくつかのレベルがあり、内部のいくつかは独自の例外処理を行います:

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!" }

次に、そのコードを次のように変更します。

  • 最も外側のレベルまで「バブルアップ」するすべての例外の完全なスタック トレースを出力します ( sub outer)。具体的には、スタック トレースが " " の最初のレベルで停止しないeval { }ようにする必要があります。

  • 内部レベルの実装を変更する必要はありません。

現在、これを行う方法は、サブ__DIE__内にローカライズされたハンドラーをインストールすることです。outer

use Devel::StackTrace;

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

[編集: 間違いを犯しました。上記のコードは実際には思い通りに動作しませんmiddle。実際には、サブルーチンの例外処理をバイパスします。だから私は質問が本当にあるべきだと思います:私が望む動作は可能ですか?]

これは完全に機能します、唯一の問題は、私がドキュメントを正しく理解していれば、明示的に廃止された動作に依存している__DIE__ことdieですeval { }。と の両方perlvarperlsub、この動作は Perl の将来のバージョンで削除される可能性があると述べています。

非推奨の動作に頼らずにこれを達成できる別の方法はありますか、それともドキュメントが別のことを言っていても頼りにできますか?

4

3 に答える 3

11

更新:die他のパッケージからの例外もキャッチできるように、コードをグローバルにオーバーライドするように変更しました。

以下はあなたが望むことをしますか?

#!/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!" }
于 2009-06-09T17:36:51.680 に答える
9

ドキュメントに非推奨と記載されているものに依存するのは安全ではありませんこの動作は、将来のリリースで変更される可能性があります (変更される可能性があります)。非推奨の動作に依存すると、現在実行している Perl のバージョンに縛られます。

残念ながら、あなたの基準を満たすこれを回避する方法はありません。「正しい」解決策は、カスタムハンドラーを削除するCarp::confess代わりに呼び出すように内部メソッドを変更することです。die$SIG{__DIE__}

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

とにかく死んでいるので、への呼び出しをトラップする必要はないかもしれませんinner()。(あなたの例ではそうではありません。実際のコードは異なる場合があります。)

あなたの例では、を介してデータを返そうとしています$@。そんなことはできません。使用する

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

代わりは。(これは、ここに投稿するのに十分なほどコードを単純化する際の単なるエラーであると想定しています。)

于 2009-06-09T17:27:10.523 に答える
4

オーバーライドは、参照解除のような Perl エラーではなくdieへの実際の呼び出しのみをキャッチすることに注意してください。dieundef

一般的なケースはありえないと思います。全体のポイントは、evalエラーを消費することです。まさにこの理由で、非推奨の動作に依存できる場合があります。現時点では、これを行う他の方法はありません。しかし、スタックのはるか下にすでに存在するエラー処理コードを壊すことなく、すべてのケースでスタック トレースを取得する合理的な方法を見つけることができません。

于 2009-06-09T18:17:04.067 に答える