Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

eval error stack

by shmem (Chancellor)
on Nov 11, 2015 at 21:13 UTC ( [id://1147503]=perlmeditation: print w/replies, xml ) Need Help??

There's the perl special variable $@ which is set at eval errors. It is a global variable, and it is a SCALAR type variable. It gets easily clobbered if you don't proceed with utmost care (tye wrote some nodes about that) and fails to report the really interesting thing, e.g. in nested evals. Consider:

my $foo = eval { my $bar = baz(); if ($@) { die "baz failed!\n"; } }; die $@ if $@; sub baz { eval "1 = 2"; } __END__ baz failed!

With that code, there's currently no way to get at the real problem, which is in the baz code, where $@ is set to

Can't modify constant item in scalar assignment at (eval 1) line 1, at + EOF

and gets clobbered by my $foo = eval {...} Of course, the above program would have reported the baz() error, if baz()behaved well:

sub baz { my $result = eval "1 = 2"; die $@ if $@; $result; }

Nonsensical assignment reported, but where is the error from my $foo = eval { };? Looks like I have to set up a die signal handler, or some such. This is all very confusing and doesn't DWIM.

And you cannot rely on $@ being propagated properly, unless you revise any included code that uses eval, which is pretty much anything, because eval is the heart of any perl code. All perl code is somehow evaled.

Back to $@ - a SCALAR. Why don't we have @@ as an error stack, and a builtin %@ hash keyed upon the names of the stack frames (e.g. Package::sub or 'eva123') or such?

The variables @@ and %@ currently (well, I have perl v5.18.2) work as a regular array/hash. But *@{SCALAR} isn't related to $@ at all.

Comments?

perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'

Replies are listed 'Best First'.
Re: eval error stack (noise)
by tye (Sage) on Nov 11, 2015 at 22:06 UTC
    my $bar = baz(); if ($@) { die "baz failed!\n"; }

    Yeah, don't do that. That isn't a feature of Perl that is discarding the error reason. That is bad coding that is explicitly discarding the error reason. Such code should look at least more like:

    my $bar = baz(); if ($@) { die "baz failed: $@\n"; } # ^^^^

    The feature of (modern) Perl that could discard an error reason goes more like this:

    #!/usr/bin/perl use strict; { package Deal; sub new { bless {} } sub make { "No collateral" } sub free_resources { unlink $_[0]->{tmp} } sub DESTROY { my( $self ) = @_; $self->free_resources() or die "Failed to free resources: $!\n"; } } eval { my $deal = Deal->new(); die "Can't make a deal: $_\n" for $deal->make(); 1 } or do { warn "Deal failed: $@\n"; settle(); }; sub settle { # ... }

    Which produces:

    Deal failed: Can't make a deal: No collateral

    But all you have to do is add "-w" (such as to the "#!" line) to get:

    Use of uninitialized value in unlink at - line 7. (in cleanup) Failed to free resources: No such file or directory Deal failed: Can't make a deal: No collateral

    But, yes, you lose programmatic access to inner error(s).

    But if you want programmatic access to errors, then you should be using structured exceptions and you get access to the inner error by having the outer exception store a reference to the inner exception.

    If you ever use eval, when it fails, you need to do at least one of the following things with the resulting value of $@:

    1. Log it (such as via warn)
    2. Rethrow it (via die), usually decorated with more context info
    3. Precisely match it and handle it (and probably still log something)

    Note that (3) is usually better done using structured exceptions or something like Errno.

    We actually have a __DIE__ handler (at $work) in order to log why the program is about to exit (we don't just redirect STDERR straight to the log file as we prefer log lines to be in a parsable format and to include a precise timestamp and some context information). Unfortunately, our old version of Perl has a bug with $^S so we can't reliably distinguish between exceptions that are about to be caught from exceptions that are about to cause the program to exit.

    So, for now, we have a flawed heuristic where exceptions that happen within a short time span of the program exiting are logged. I can tell you that it is easily very annoying to have caught exceptions being logged. Having a bunch of fatal-looking error messages that don't actually indicate any kind of problem sucks.

    I don't see any way for your idea of collecting all exceptions to produce more signal than noise.

    Actually, I don't even see how such an idea ever allows for an exception to be freed. So catching an exception just becomes a memory leak.

    - tye        

      Yeah, don't do that.

      Well, I certainly don't. This is a contrived example of course. Such code, if present, usually is buried inside a module chain (probably of third party modules), and if so, difficult to debug.

      But, yes, you lose programmatic access to inner error(s). But if you want programmatic access to errors, then you should be using structured exceptions and you get access to the inner error by having the outer exception store a reference to the inner exception.

      With @@, there would be no need to store references to the inner exception, and any code that doesn't, would do.

      I agree with all your points of proper error handling, and in fact I do follow them in my coding. But the code I write just makes up for 2% of the whole code perl exercises in any program or application running on my behalf.

      I don't see any way for your idea of collecting all exceptions to produce more signal than noise.

      This is the case also with e.g. Carp::confess and perls backtrace facility. It is in place, but you don't need it. Except when you do.

      Actually, I don't even see how such an idea ever allows for an exception to be freed. So catching an exception just becomes a memory leak.

      That depends on the semantics of @@. The current value of $@ could be pushed onto it, if $@ hasn't been accessed before resetting. And it could be handled via a pragma:

      use evalerrorstack; # @@ initialized ... no evalerrorstack; # @@ cleared

      But you didn't get my point, which is: $@ is related to eval errors. Why isn't @@ ? Probably the answer is: you don't need it, it can be done otherwise, and there is a canonical way to handle (eval) errors.

      Actually, the idea of using @@ comes from wanting to be able to say:

      sub expand { my $macros = shift; my $result; @{$result}{keys %$macros} = map { eval $_ } values %$macros; return if @@; $result; }

      If a subroutine fails, I usually set $@ and just return. In the above case, there may be a bunch of failures which are all related to macro expansion, which I would like to be able to report. The above is for now written as

      sub expand { my $macros = shift; my $result; my @errors; for ( keys %$macros ) { $result->{$_} = eval $macros->{$_}; push @errors, $@ if $@; } if ( @errors ) { $@ = join "\n", @errors; return; } $result; }

      but having @@ acummulate $@ would just be handy if failures occur in list context and/or $@ hasn't been accessed before it is being set again.

      perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
        With @@, there would be no need to store references to the inner exception

        Of course there would be. Because I want the inner exception to be stored when it is actually part of the cause of the outer exception and not because it was an incidental exception that was thrown and handled inside of the code that later detected an error and threw the outer exception.

        The current value of $@ could be pushed onto it, if $@ hasn't been accessed before resetting.

        But your original example was, in part:

        if ($@) { die "baz failed!\n"; }

        The first line I quoted accessed $@. So your own solution doesn't work in your own example. So you have an implementation hair to split there.

        - tye        

Re: eval error stack
by Athanasius (Archbishop) on Nov 12, 2015 at 08:10 UTC

    Hello shmem,

    Why don't we have @@ as an error stack, ...?

    FWIW, Exception::Class::TryCatch:

    ...provides for a method to push errors onto a hidden error stack immediately after an eval so that cleanup code or other error handling may also call eval without the original error in $@ being lost.

    For example:

    #! perl use strict; use warnings; use Exception::Class::TryCatch; try eval { my $bar = baz(); die 'baz failed!' if $@; }; if (my ($second, $first) = (catch, catch)) { print "(1) $first(2) $second"; } sub baz { try eval "1 = 2"; }

    Output:

    18:09 >perl 1447_SoPW.pl (1) Can't modify constant item in scalar assignment at (eval 4) line 1 +, at EOF (2) baz failed! at 1447_SoPW.pl line 9. 18:09 >

    Hope that’s of interest,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://1147503]
Approved by kcott
Front-paged by kcott
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (8)
As of 2024-04-19 15:34 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found