Perl:$SIG{__DIE__}、eval { } 和堆栈跟踪

发布于 2024-07-24 01:30:16 字数 1355 浏览 4 评论 0原文

我有一段 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!" }

现在我想更改该代码,以便它执行以下操作:

  • 为每个一直“冒泡”到最外层(subouter)的异常打印完整的堆栈跟踪。 具体来说,堆栈跟踪不应停止在“eval { }”的第一级。

  • 不必更改任何内部级别的实现。

现在,我执行此操作的方法是在 outer 子内部安装本地化的 __DIE__ 处理程序:

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子的异常处理。 所以我想问题实际上应该是:我想要的行为是否可能?]

这完美地工作,唯一的问题是,如果我正确理解文档,它依赖于明确弃用的行为,即即使“eval { }”内的“die”也会触发 __DIE__ 处理程序,它们确实应该这样做” t。 perlvarperlsub 都声明此行为可能会在 Perl 的未来版本中删除。

有没有另一种方法可以在不依赖已弃用的行为的情况下实现此目的,或者即使文档另有说明,也可以依赖它吗?

I have a piece of Perl code somewhat like the following (strongly simplified): There are some levels of nested subroutine calls (actually, methods), and some of the inner ones do their own exception handling:

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

Now I want to change that code so that it does the following:

  • print a full stack trace for every exception that "bubbles up" all the way to the outermost level (sub outer). Specifically, the stack trace should not stop at the first level of "eval { }".

  • Not having to change the the implementation of any of the inner levels.

Right now, the way I do this is to install a localized __DIE__ handler inside the outer sub:

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();
}

[EDIT: I made a mistake, the code above actually doesn't work the way I want, it actually bypasses the exception handling of the middle sub. So I guess the question should really be: Is the behaviour I want even possible?]

This works perfectly, the only problem is that, if I understand the docs correctly, it relies on behaviour that is explicitly deprecated, namely the fact that __DIE__ handlers are triggered even for "die"s inside of "eval { }"s, which they really shouldn't. Both perlvar and perlsub state that this behaviour might be removed in future versions of Perl.

Is there another way I can achieve this without relying on deprecated behaviour, or is it save to rely on even if the docs say otherwise?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

梦里兽 2024-07-31 01:30:16

更新:我更改了代码以全局覆盖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!" }

UPDATE: I changed the code to override die globally so that exceptions from other packages can be caught as well.

Does the following do what you want?

#!/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!" }
比忠 2024-07-31 01:30:16

依赖文档中指出已弃用的任何内容都是安全的。 该行为可能(并且可能会)在未来版本中发生变化。 依赖已弃用的行为会将您锁定在当前运行的 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(@_) };

。 (我假设这只是将代码简化到足以将其发布到此处的错误。)

It is not safe to rely on anything that the documentation says is deprecated. The behavior could (and likely will) change in a future release. Relying on deprecated behavior locks you into the version of Perl you're running today.

Unfortunately, I don't see a way around this that meets your criteria. The "right" solution is to modify the inner methods to call Carp::confess instead of die and drop the custom $SIG{__DIE__} handler.

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

Since you're dieing anyway, you may not need to trap the call to inner(). (You don't in your example, your actual code may differ.)

In your example you're trying to return data via $@. You can't do that. Use

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

instead. (I'm assuming this is just an error in simplifying the code enough to post it here.)

So尛奶瓶 2024-07-31 01:30:16

请注意,覆盖 die 只会捕获对 die 的实际调用,不会像解引用 undef 这样的 Perl 错误。

我认为一般情况是不可能的; eval 的全部意义在于消耗错误。 正是出于这个原因,您可能能够依赖已弃用的行为:目前没有其他方法可以做到这一点。 但我找不到任何合理的方法来在每种情况下获取堆栈跟踪,而不会潜在地破坏堆栈中已经存在的任何错误处理代码。

Note that overriding die will only catch actual calls to die, not Perl errors like dereferencing undef.

I don't think the general case is possible; the entire point of eval is to consume errors. You MIGHT be able to rely on the deprecated behavior for exactly this reason: there's no other way to do this at the moment. But I can't find any reasonable way to get a stack trace in every case without potentially breaking whatever error-handling code already exists however far down the stack.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文