Remember Your Responsibilities

grantm on 2009-01-31T07:42:10

A colleague encountered an odd problem the other day. It took us a while to realise what was going on, you'll probably work it out much quicker ...

Here's a simplified version of what we were dealing with:

    use strict;
    use warnings;

    eval {
        my $obj = SomeClass->new();
        die "Code Red!\n";
    };

    if($@) {
        print "Exception caught: $@\n";
    }
    else {
        print "No exception occurred\n"
    }

We both expected it to output:

    Exception caught: Code Red!

but instead we got:

    No exception occurred

You probably worked it out straightaway but for the record, here's what was happening ...

The die statement did generate an exception and the message was stored in $@. Of course the code in the eval block stopped executing immediately but when control flow exited the block, any lexcial variables declared in the block went out of scope. In this case there was only one: $obj. What we didn't realise is that $obj's class defined a destructor method ('DESTROY'). This method is called when the object goes out of scope and apparently in our case the method wrapped something in an eval. Whatever that something was, it completed without generating an exception, so $@ ended up empty - indicating no error and overwriting the exception message that our code put there.

So, if you write a destructor method and it needs to use eval, please remember to use local($@) to restore the previous value of $@ when your routine completes.

Cross-posted from my shiny new blog


critic to the rescue

daxim on 2009-01-31T11:32:34

The problem of needing to localise special variables is discussed in http://perldoc.perl.org/perlobj.html#Destructors.

There is a P::C policy ErrorHandling::RequireCheckingReturnValueOfEval that catches this programming mistake. Its documentation also offers an alternative approach where you don't need to mess with other people's destructors to make it work.

package SomeClass;
sub new {
    return bless \my $foo => __PACKAGE__;
}

sub DESTROY {
    eval {'resetting $@'};
}

package main;
use strict;
use warnings;

if (
    eval {
        my $obj = SomeClass->new();
        die "Code Red!\n";
        1;
    }
) {
    print "Exception caught: $@\n";
} else {
    print "No exception occurred\n";
}

Re:critic to the rescue

davebaker on 2009-02-03T05:36:26

Did you mean to say

if (
    eval {
        my $obj = SomeClass->new();
        die "Code Red!\n";
        1;
    }
) {
    print "No exception occurred\n";
} else {
    print "Exception caught: $@\n";
}

How?

Downes on 2009-01-31T15:38:38

> use local($@) to restore the previous value of $@ when your routine completes.

How?

(Searched web but didn't find an answer)

Re:How?

Aristotle on 2009-01-31T17:27:46

Err. How what?

local $@;

Just like he said.

Thanks

Downes on 2009-01-31T17:42:15

Thanks.

(This shows the danger of just mixing in code with plain English when you're explaining things; sometimes people don't recognize that it *is* code (even though it's obvious to others))

eval {} or do_something

davebaker on 2009-01-31T20:56:59

Following the eval block with an "or" seems to be more reliable than using "if ($@)" to check for an exception in an eval block, because an eval block (or even an eval EXPR) returns undef if there is a syntax error or runtime error or a die statement is executed (according to perldoc -f eval).


        eval {
                my $obj = SomeClass->new();
                die "Code Red!\n";
        } or print "Exception caught: $@\n";

But even with this technique the $@ is reset by the destructor in your example, so while the exception at least can be caught we lose the information $@ would provide but for the destructor. The output is merely

"Exception caught:"

The documentation for eval says that $@ is guaranteed to be a null string if no exception occurred; I had not appreciated the precise scope of that statement until seeing your example (it doesn't go on to say that, conversely, $@ is guaranteed not to be a null string if an exception occurred).

I believe I heard schwern advocating at YAPC 2008 the use of

eval { ... }
        or warn_about_problem();

rather than

eval { ... };
if ($@) { warn_about_problem() }

This might have been the kind of situation he had in mind.