代码之家  ›  专栏  ›  技术社区  ›  trendels

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

  •  14
  • trendels  · 技术社区  · 17 年前

    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 “在里面” eval{} perlvar perlsub

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

    3 回复  |  直到 17 年前
        1
  •  11
  •   Sinan Ünür    17 年前

    更新: 我更改了代码以覆盖 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!" }
    
        2
  •  9
  •   Michael Carman    17 年前

    它是 可以安全地依赖文档中所说的已弃用的任何内容。在未来的版本中,这种行为可能会发生变化。依赖于已弃用的行为会将您锁定在您今天运行的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(@_) };
    

    相反。(我假设这只是简化代码到足以将其发布在这里的错误。)

        3
  •  4
  •   Eevee    17 年前

    请注意,覆盖 die 只会接听实际来电 , undef .

    我认为一般情况是不可能的;整个要点 eval 就是消费错误。您可能能够依赖已弃用的行为,原因正是如此:目前没有其他方法可以做到这一点。但我无法找到任何合理的方法来在每种情况下都获得堆栈跟踪,而不会破坏堆栈中已经存在的任何错误处理代码。