Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the common pitfalls when using Perl's eval?

What are the common pitfalls associated with Perl's eval, which might make you choose to use a module such as Try::Tiny?

like image 725
Hugh Avatar asked Sep 29 '11 13:09

Hugh


People also ask

Why we use eval in Perl?

eval in all its forms is used to execute a little Perl program, trapping any errors encountered so they don't crash the calling program.

How to use eval in Perl?

This allows you to use a separate, perhaps user-supplied, piece of Perl script within your program. An eval EXPR statement is evaluated separately each time the function is called. The second form evaluates BLOCK when the rest of the script is parsed (before execution).

What does eval return in Perl?

The eval block is parsed with the rest of the code and its return value is a reference to the array @a . That reference is assigned to $ttt . While the eval block goes out of scope, @a still has a non-zero reference count (thanks to $ttt ) so it still exists.

What does eval mean in Perl?

The eval function takes a character string, and evaluates it in the current perl environment, as if the character string were a line in the currently executing perl program.


2 Answers

Perl's eval comes in two flavors, string eval and block eval. String eval invokes the compiler to execute source code. Block eval surrounds already compiled code in a wrapper that will catch the die exception. (string eval catches the die exception also, as well as any compilation errors).

Try::Tiny only applies to the block form of eval, but the following applies to both forms.

Every time you call eval it will change the value of $@. It will either be '' if the eval succeeded or the error caught by the eval.

This means that any time you call an eval, you will clear any previous error messages. Try::Tiny localizes the $@ variable for you, so that a successful eval will not clear the message of a previous failed eval.

The other pitfall comes from using $@ as the check to determine if the eval succeeded. A common pattern is:

eval {...};
if ($@) {
   # deal with error here
}

This relies on two assumptions, first that any error message $@ could contain is a true value (usually true), and that there is no code between the eval block and the if statement.

Visually of course the latter is true, but if the eval block created an object, and that object went out of scope after the eval failed, then the object's DESTROY method will be called before the if statement. If DESTROY happens to call eval without localizing $@ and it succeeds, then by the time your if statement is run, the $@ variable will be cleared.

The solution to these problems is:

my $return = do {
    local $@;
    my $ret;
    eval {$ret = this_could_fail(); 1} or die "eval failed: $@";
    $ret
};

breaking that apart line by line, the local $@ creates a new $@ for the do block which prevents clobbering previous values. my $ret will be the return value of the evaluated code. In the eval block, $ret is assigned to, and then the block returns 1. That way, no matter what, if the eval succeeds it will return true, and if it fails it will return false. It is up to you what to do in the case of failure. The code above just dies, but you could easily use the return value of the eval block to decide to run other code.

Since the above incantation is a bit tedious, it becomes error prone. Using a module like Try::Tiny insulates you from those potential errors, at the cost of a few more function calls per eval. It is important to know how to use eval properly, because Try::Tiny is not going to help you if you have to use a string eval.

like image 51
Eric Strom Avatar answered Nov 15 '22 18:11

Eric Strom


The issues are explained in the Try::Tiny documentation. Briefly, they are:

  • Clobbering $@
  • Localizing $@ silently masks errors
  • $@ might not be a true value
like image 26
Sinan Ünür Avatar answered Nov 15 '22 18:11

Sinan Ünür