NAME
Devel::EvalError -- Reliably detect if and why eval() failed
SYNOPSIS
use Devel::EvalError();
my $ee = Deval::EvalError->new();
$ee->ExpectOne(
eval { ...; 1 }
);
if ( $ee->Failed() ) { # if ( ! $ee->Succeeded() )
... $ee->Reason() ...;
}
DESCRIPTION
Although it is common to check $@
to determine if a call to eval
failed, it is easy to make eval
fail while leaving $@
empty.
Using Devel::EvalError
encourages you to use more reliable ways to check whether eval
failed while also giving you access to the failure reason(s) even if $@
ended up empty. (It also makes $@
ending up empty less likely for other uses of eval
.)
If you have code that looks like the following:
eval { ... };
if ( $@ ) {
log_failure( "...: $@" );
}
Then you should replace it with code more like this:
use Devel::EvalError();
# ...
my $ee = Devel::EvalError->new();
$ee->ExpectOne( eval { ...; 1 } );
if ( $ee->Failed() ) {
log_failure( "...: " . $ee->Reason() );
}
Caveats
It is important to call Devel::EvalError-
new()> before doing the eval
. Although I believe that in all existing implementations of Perl v5, the following code still works, there is no iron-clad guarantee that it will do things in the required order (such as in some future version of Perl). So you might not want to risk using it:
my $ee = Devel::EvalError->new()->ExpectOne(
eval $code . "; 1"
);
It is important that the Perl code that you evaluate ends with an expression that returns just the number one. When evaluating a string, append "; 1"
to the end of the string. When evaluating a block, add ; 1
to the end inside the block, like so:
$ee->ExpectOne()->( eval { ...; 1 } );
If the eval
'd code returns early, it is important that it does so either via return 1;
or by die
ing.
Since you can't rely on $@
to tell if eval
failed or succeeded, you need to rely on what eval
returns. eval
indicates failure by returning an empty list so it is very important to not do return;
inside the eval
(of course, return;
in some subroutine called from your eval
'd code is not a problem). You also should avoid return @list;
unless you can be certain that @list
is not empty.
ExpectOne()
requires that eval
either returns an empty list or returns just the number one (otherwise it croak
s).
Why $@
is unreliable
It is a bug in Perl that the value of $@
is not guaranteed to survive until eval
finishes returning. This bug has existed since Perl 5 was created so there are a lot of versions of Perl around where you can run into this problem. Here is a quick example demonstrating it:
my $ok = eval {
my $trigger = RunAtBlockEnd->new(
sub { warn "Exiting block!\n" },
);
die "Something important!\n";
1;
};
if( $@ ) {
warn "eval failed ($@)\n";
} elsif( $ok ) {
warn "eval succeeded\n";
} else {
warn "eval failed but \$@ was empty!\n";
}
{
package RunAtBlockEnd;
sub new { bless \$_[-1], $_[0] }
sub DESTROY {
my $self = shift @_;
eval { ${$self}->(); 1 }
or warn "RunAtBlockEnd failed: $@\n";
}
}
This code produces the following output:
Exiting block!
eval failed but $@ was empty!
The crux of the problem is the use of eval
inside of a DESTROY
method while not also doing local $@
in that method. Note that it is also a problem if any code called, however indirectly, from a DESTROY
method uses eval
without local $@
, so preventing the problem can be quite difficult (and once you have identified that this problem is happening to you, the inability to overload eval
prevents easily finding the source of the problem).
Note that the use of Devel::EvalError
also has the side-effect of localizing the changing of $@
so it not only works around this problem if used on the outer eval
, it would also prevent the problem if only used on the inner eval
. If we change our DESTROY
method to:
sub DESTROY {
use Devel::EvalError();
my $self = shift @_;
my $ee = Devel::EvalError->new();
$ee->ExpectOne( eval { ${$self}->(); 1 } );
warn "RunAtBlockEnd failed: ", $ee->Reason(), "\n"
if $ee->Failed();
}
Then our snippet produces the following results:
Exiting block!
eval failed (Something important!
)
Why use
not require
?
Note that we wrote use Devel::EvalError();
and not require Devel::EvalError;
in the above contrived example. That is because, the first time a module is require
'd, the code for the module has to be eval
'd, which also clobbers $@
just like a straight eval
would. So doing a require
inside of a DESTROY
method causes the same problem.
So all of our examples use use Devel::EvalError();
just in case somebody pastes some example code into their DESTROY
method. In most real-world code, the require
would be placed outside of the DESTROY
method and so is unlikely to cause a problem. So if you prefer require
over use
in some cases, you can usually write require Devel::EvalError;
with no problem.
Methods
new
new()
is a class method that takes no arguments and returns a new Devel::EvalError
object. You usually call new()
like so:
my $ee = Devel::EvalError->new();
new()
saves away the current value of $@
so that it can restore it when you are done using the returned Devel::EvalError
object. new()
also sets up a $SIG{__DIE__}
handler to make a note of any exceptions that get thrown (such as by calling die
). This "die handler" will also call the previous handler (if there was one) and the previous handler will be automatically restored later.
ExpectOne
$ee->ExpectOne( eval { ...; 1 } );
$ee->ExpectOne( eval $code . '; 1' );
ExpectOne()
should be passed the results of a call to eval
. The code being eval
'd should exit only by returning just the number one or by throwning an exception (such as by calling die
).
ExpectOne()
returns the object that invoked it so that you can use the following shortened form:
my $ee = Devel::EvalError->new()->ExpectOne( eval ... );
But be aware that this shortened form relies on a particular order of evaluation that is not guaranteed. So you may wish to avoid this risk or just prefer to not rely on undefined evaluation order as a matter of principle.
If ExpectOne()
gets passed just the number one, then the eval
succeeded, setting what several other methods will return.
If ExpectOne()
gets passed the empty list, then the eval
failed, setting the return values for other methods differently.
The current release also interprets a single undefined value as eval
having failed. This is to account for a use-case similar to:
my $ee = Devel::EvalError->new();
my $ok = eval { ...; 1 };
$ee->ExpectOne( $ok );
But this interpretation may be subject to change in a future release of Devel::EvalError
(to be treated the same as the following case).
Being passed any other value will cause ExpectOne()
to "croak" (see the Carp
module), reporting that the module has been used incorrectly.
If ExpectOne()
gets passed the empty list, then the value of $@
is immediately checked. If $@
is not empty, then its value is saved as the failure reason (other failure reasons may have been collected by the "die handler", but those will mostly be ignored in this case).
ExpectOne()
also restores the previous "die handler" (if any).
Reason
Reason()
returns either the empty string or a string (or object) containing (at least) the reason that the earlier eval
failed. If it is unclear which of several different reasons actually caused the eval
to fail, then a string will be returned containing all of the possible reasons in chronological order.
To simplify some coding cases, Reason()
will safely return an empty string if called on an Erase()d
object or one where ExpectOne()
has not yet been called [nor ExpectNonEmpty()
].
AllReasons
AllReasons()
returns the list of strings and/or objects that repesent exceptions thrown between when our object was created and when ExpectOne()
was called [or ExpectNonEmpty()
], in chronological order.
Usually the last reason returned is the reason that the eval
failed.
Note that if a DESTROY
method tries to throw an exception (a rather pointless thing to do unless the exception is caught within the DESTROY method), then the real reason for the eval
failing can have other reasons after it in the returned list of reasons. If that DESTROY method also did local $@;
(or equivalent) such that $@
was still properly set after eval
finished failing, then the last reason returned will be the real reason why the eval
failed; that reason will just appear in the list of reasons twice.
Succeeded
Succeeded()
returns a true value if the earlier eval
succeeded. It returns a false value if the earlier eval
failed. Otherwise it "croaks" (if ExpectOne()
has not yet been called or the invoking object has been Erase()
d, etc.).
Failed
Failed()
returns a true value if the earlier eval
failed. It returns a false value if the earlier eval
succeeded. Otherwise it "croaks".
Reuse
Reuse()
cleans up an existing Devel::EvalError
object and then prepares it to be used again. The following two snippets are equivalent:
undef $ee;
$ee = Devel::EvalError->new();
# Same as
$ee->Reuse();
Note that you should not re-use a variable by simply puting a new Devel::EvalError
object over the top of a previous one. Don't ever write code like the line marked "WRONG!" below:
my $ee = Devel::EvalError->new();
# ...
$ee = Devel::EvalError->new(); # WRONG!
my $e2 = Devel::EvalError->new();
# ...
$e2->Reuse(); # RIGHT!
Here is a quick example of how badly that can go wrong:
my $ee = Devel::EvalError->new();
if ( $DoStuff ) {
$ee->ExpectOne( eval { do_stuff(); 1 } );
# ...
}
$ee = Devel::EvalError->new();
The above code produces output like:
$SIG{__DIE__} changed out from under Devel::EvalError at ...
Devel::EvalError::_revertHandler...
Devel::EvalError::Erase...
Devel::EvalError::DESTROY...
...
$SIG{__DIE__} changed out from under Devel::EvalError at ...
Devel::EvalError::_revertHandler...
Devel::EvalError::Erase...
Devel::EvalError::DESTROY...
...
This is because the second Devel::EvalError
object is created before the first one gets destroyed. The lifetimes of Devel::EvalError
objects must be strictly nested or else they can't properly deal with sharing the single global $SIG{__DIE__}
slot.
Calling $ee-
Reuse();> ensures that the previous object gets cleaned up before the next one is initialized, preventing such noisy problems.
Note that Reuse()
returns the invoking object so that you can choose to use the following shortened form, despite the fact that it relies on a particular (undefined) order of evaluation:
$ee->Reuse()->ExpectOne( eval ... );
Erase
Erase()
cleans up and clears out a Devel::EvalError
object. The below two snippets are equivalent:
my $ee = Devel::EvalError->new();
# ... # eval() 1
undef $ee;
# ... # non-eval() code
$ee = Devel::EvalError->new();
# ... # eval() 2
my $ee = Devel::EvalError->new();
# ... # eval() 1
$ee->Erase();
# ... # non-eval() code
$ee->Reuse();
# ... # eval() 2
Notice how using Erase()
leaves the $ee
variable holding an object so you can just use $ee->Reuse()
rather than having to repeat the whole module name in order to call new()
.
Note also that $ee->new()
is not allowed. If you don't want to re-type the module name and you want to use one object to create another separate object, then you can use ref($ee)->new()
. But remember that you need to ensure that the lifespans of Devel::EvalError
objects are strictly nested.
The following contrived example shows how not being explicit with the nesting of the lifespans of Devel::EvalError
objects can be a problem:
{
my $e1 = Devel::EvalError->new();
my $e2 = ref($e1)->new();
# Both $e1 and $e2 get destroyed here ...
# in what order?
}
The above code produces two
$SIG{__DIE__} changed out from under Devel::EvalError ...
complaints. You can fix it as follows:
{
my $e1 = Devel::EvalError->new();
{
my $e2 = ref($e1)->new();
# Only $e2 is destroyed here
}
# Only $e1 is destroyed here
}
Sadly, the above contrived example may still give the annoying warnings due to a rare appearance of Perl 5 optimizations. Adding just one line of useless code prevents the optimization and the warnings. In real code, this optimization problem is much less likely to appear.
{
my $e1 = Devel::EvalError->new();
{
my $e2 = ref($e1)->new();
# Only $e2 is destroyed here
}
my $x= "You may need code here to thwart optimizations";
# Only $e1 is destroyed here
}
ExpectNonEmpty
You should probably not use the ExpectNonEmpty()
method.
No, really. Just go read some other section of the manual now.
Are you still here? Okay, since I wrote it, I guess I'll let you read the documentation about it as well.
ExpectNonEmpty()
can be used to use eval
to return an interesting value. For example:
my $ee = Devel::EvalError->new();
my @list = $ee->ExpectNonEmpty(
eval { getListDangerously() }
);
But you really shouldn't do it that way. You should do it this way instead:
my $ee = Devel::EvalError->new();
my @list;
$ee->ExpectOne(
eval { @list = getListDangerously(); 1 }
);
For one thing, if getListDangerously()
returned an empty list, then much confusion would likely ensue.
For another, scalar context isn't preserved when changing code from:
my $return = eval ...;
to:
my $return = $ee->ExpectNonEmpty( eval ... );
In the second line above, the eval
is called in a list context. That code would be better written like:
my $return;
$ee->ExpectOne( eval { $return = ...; 1 } );
Or, in the case of eval
'ing a string of Perl code:
my $return;
$ee->ExpectOne( eval "\$return = $code; 1" );
CONTRIBUTORS
Original author: Tye McQueen, http://perlmonks.org/?node=tye
LICENSE
Copyright (c) 2008 Tye McQueen. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
SEE ALSO
The Troll Under the Bridge, Fremont, WA