Tye McQueen > Devel-EvalError-0.001002 > Devel::EvalError

Download:
Devel-EvalError-0.001002.tar.gz

Dependencies

Annotate this POD

CPAN RT

New  1
Open  0
View/Report Bugs
Module Version: 0.001002   Source  

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 dieing.

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 croaks).

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

syntax highlighting: