Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Bug in eval in pre-5.14

by choroba (Bishop)
on Jan 23, 2016 at 16:45 UTC ( #1153468=perlmeditation: print w/replies, xml ) Need Help??

For some reason, the following pattern is still used in code - I've seen it at a recent interview and in an accepted answer at Stack Overflow.
eval { # code that could die }; if ($@) { # Poor man's catch!

On pre-5.14 Perls, this is a bug. Sometimes, the code inside the eval could die, but the catch wouldn't be triggered, because the $@ could be clobbered - see perl5140delta for details. If you want to use modules from other people, there's no way how to prevent the bug. If the foreign code calls eval in an object's destructor, you're doomed.

Fortunately, it's rather easy to improve the pattern:

eval { # code that could die; 1 } or do {

That way, you can really detect the failure in the "try" block, even if the exception could get lost.

Here's a simple example that demonstrates the problem. Note that Try::Tiny doesn't provide more than the "or do" pattern:

#!/usr/bin/perl use warnings; use strict; print $], "\n"; { package My::Obj; sub new { my $class = shift; bless {@_}, $class } sub DESTROY { my $self = shift; eval { 1 } if ! $self->{finished}; } } my $o1 = 'My::Obj'->new(finished => 1); undef $o1; # Exception overlooked. eval { my $o2 = 'My::Obj'->new; die "Exception!"; }; if ($@) { warn "Caught with \$\@: $@"; } # Exception details lost. eval { my $o2 = 'My::Obj'->new; die "Exception!"; 1 } or do { warn "Caught with or: $@"; }; # Same as above. use Try::Tiny; try { my $o3 = 'My::Obj'->new; die "Exception!"; } catch { warn "Caught with Try::Tiny: $_"; };

In 5.20, I'm getting:

5.020001 Caught with $@: Exception! at ./eval.pl line 25. Caught with or: Exception! at ./eval.pl line 34. Caught with Try::Tiny: Exception! at ./eval.pl line 44.

But, in 5.12, the first exception gets overlooked. The second syntax at least notices something went wrong, but the exception is lost anyway.

5.012005 Caught with or: at eval.pl line 36. Caught with Try::Tiny: at eval.pl line 46.
($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

Replies are listed 'Best First'.
Re: Bug in eval in pre-5.14
by Anonymous Monk on Jan 23, 2016 at 22:58 UTC

    Just upgrading to a Perl >= 5.14 alone still does not mean $@ is always safe to use, see e.g. RT#123738 and RT#123773. So the only real solution is the eval { ...; 1 } or ... pattern, Try::Tiny, or similar. It's also a good habit to get into because it's backwards-compatible.

Re: Bug in eval in pre-5.14
by zwon (Abbot) on Jan 25, 2016 at 22:44 UTC
    Note that Try::Tiny doesn't provide more than the "or do" pattern
    It actually does, it preserves the value of $@, the following modifications to your script demonstrate the difference:
    ...; eval { die "Foo!" }; # Exception details lost. eval { my $o2 = 'My::Obj'->new; die "Exception!"; 1 } or do { warn "Caught with or: $@"; }; say "\$\@ after eval or do: $@"; # not same as above. use Try::Tiny; eval { die "Foo!" }; try { my $o3 = 'My::Obj'->new; die "Exception!"; } catch { warn "Caught with Try::Tiny: $_"; }; say "\$\@ after try catch: $@";
      Solvable by localizing $@:
      eval { die "Foo!" }; # Exception details not lost. { local $@; eval { my $o2 = 'My::Obj'->new; die "Exception!"; 1 } or do { warn "Caught with or: $@"; }; } warn "\$\@ after eval or do: $@";

      ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: Bug in eval in pre-5.14
by $h4X4_|=73}{ (Monk) on Jun 06, 2016 at 13:32 UTC

    There are many things you should not do with eval and those are some of them.
    What I have in a code of mine still hits on the error.

    #!/usr/bin/perl use warnings; use strict; print $], "\n"; { package My::Obj; sub new { my $class = shift; bless {@_}, $class } sub DESTROY { #my $self = shift; #eval { 1 } if ! $self->{finished}; } } my $o1 = 'My::Obj'->new(finished => 1); undef $o1; use Carp; # This is what I'm doing eval q^ my $o2 = 'My::Obj'->new; croak "Exception! $!"; ^; if ($@) { croak 'Caught with $@:'.$@; # change $@ to $! on this line, wow... + 5.014002 seems to work? } # Exception overlooked. eval { my $o2 = 'My::Obj'->new; die "Exception!"; }; if ($@) { warn "Caught with \$\@: $@"; } # Exception details lost. eval { my $o2 = 'My::Obj'->new; die "Exception!"; 1 } or do { warn "Caught with or: $@"; }; # Same as above. use Try::Tiny; try { my $o3 = 'My::Obj'->new; die "Exception!"; } catch { warn "Caught with Try::Tiny: $_"; };
    Output:
    Caught with $@:Exception! at (eval 1) line 3 eval ' my $o2 = \'My::Obj\'->new; croak "Exception! $!"; ;' called at C:\xampp\cgi-bin\Test\tester.pl line 25 at C:\xampp\cgi-bin\Test\tester.pl line 29 5.014002

Re: Bug in eval in pre-5.14
by Anonymous Monk on Feb 10, 2016 at 17:29 UTC

    Sorry to heat up this stale topic, but I've just been bitten by this:

    #!/usr/bin/perl use Modern::Perl '2015'; use Try::Tiny; sub foo { try { say 'foo try'; die; return 'try'; } catch { say 'foo catch'; return 'catch'; }; say 'foo outer'; return 'outer'; } sub bar { eval { say 'bar try'; die; return 'try'; } or do { say 'bar catch'; return 'catch'; }; say 'fos outer'; return 'outer'; } say 'begin'; my $r = foo(); say $r; say "########"; my $s = bar(); say $s; say 'end';

    So there is a subtle difference between eval ... or do and Try::Tiny: in the latter, return doesn't mean what you think it means even in the catch block.

    In hindsight, it's obvious, and it is even documented explicitly, still, this behavior makes Try::Tiny slightly more inconvenient.

Re: Bug in eval in pre-5.14
by $h4X4_|=73}{ (Monk) on Aug 13, 2016 at 15:27 UTC

    When I add control over die() and exit() I can make the code stop right at the first die on any version. Tested on 5.8 and 5.22.

    #!/usr/bin/perl use warnings; use strict; BEGIN { # control eval's bypass of die $SIG{__DIE__} = \&Splacker; } print $], "\n"; { package My::Obj; sub new { my $class = shift; bless {@_}, $class } sub DESTROY { my $self = shift; eval { 1 } if ! $self->{finished}; } } my $o1 = 'My::Obj'->new(finished => 1); undef $o1; # Exception overlooked. eval { my $o2 = 'My::Obj'->new; die "Exception!"; }; if ($@) { warn "Caught with \$\@: $@"; } # Exception details lost. eval { my $o2 = 'My::Obj'->new; die "Exception!"; 1 } or do { warn "Caught with or: $@"; }; # Same as above. use Try::Tiny; try { my $o3 = 'My::Obj'->new; die "Exception!"; } catch { warn "Caught with Try::Tiny: $_"; }; sub Splacker { my $error = shift; print $error; CORE::exit(1); }

    Output:
    5.008008 Exception! at C:\xampp\cgi-bin\Test\tester.pl line 32.


    Update: And if you comment out CORE::exit(1); the output is.
    Caught with or: at C:\xampp\cgi-bin\Test\tester.pl line 44. Caught with Try::Tiny: at C:\xampp\cgi-bin\Test\tester.pl line 55. 5.008008 Exception! at C:\xampp\cgi-bin\Test\tester.pl line 32. Exception! at C:\xampp\cgi-bin\Test\tester.pl line 42. Exception! at C:\xampp\cgi-bin\Test\tester.pl line 53.

    Update 2: Forgot about STDERR. If used in print like so.
    sub Splacker { my $error = shift; print STDERR $error; }

    The Output is...
    Exception! at C:\xampp\cgi-bin\Test\tester.pl line 32. Exception! at C:\xampp\cgi-bin\Test\tester.pl line 42. Caught with or: at C:\xampp\cgi-bin\Test\tester.pl line 44. Exception! at C:\xampp\cgi-bin\Test\tester.pl line 53. Caught with Try::Tiny: at C:\xampp\cgi-bin\Test\tester.pl line 55. 5.008008

      The point of try/catch is not to stop on the first error (or print it), but to process it and decide to rethrow or continue.

      ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

        With a little tweaking of the code it can be done. I was playing around with it so there is more code in Splacker then needed.

        #!/usr/bin/perl use warnings; use strict; BEGIN { # control eval's bypass of die $SIG{__DIE__} = \&Splacker; } my $error_log = ''; print $], "\n"; { package My::Obj; sub new { my $class = shift; bless {@_}, $class } sub DESTROY { my $self = shift; eval { 1 } if ! $self->{finished}; } } my $o1 = 'My::Obj'->new(finished => 1); undef $o1; # Exception Found. eval { my $o2 = 'My::Obj'->new; die "Exception 0!"; }; # And Caught if (Splacker()) { warn 'Caught with Splacker: '.Splacker(); } # Give back die control if you want... $SIG{__DIE__} = \&CORE::die; # Exception overlooked. eval { my $o2 = 'My::Obj'->new; die "Exception 1!"; }; if ($@) { warn "Caught with \$\@: $@"; } # Exception details lost. eval { my $o2 = 'My::Obj'->new; die "Exception 2!"; 1 } or do { warn "Caught with or: $@"; }; # Same as above. use Try::Tiny; try { my $o3 = 'My::Obj'->new; die "Exception 3!"; } catch { warn "Caught with Try::Tiny: $_"; }; sub Splacker { my $error = shift || ''; if ($error eq 'DIE') { print STDERR $error.' '.$error_log; CORE::exit(1); } elsif ($error) { $error_log = $error; # print STDERR $error; } else { return $error_log; } }
        Output:
        Caught with Splacker: Exception 0! at C:\xampp\cgi-bin\Test\tester.pl +line 33. Caught with or: at C:\xampp\cgi-bin\Test\tester.pl line 56. Caught with Try::Tiny: at C:\xampp\cgi-bin\Test\tester.pl line 67. 5.008008

Re: Bug in eval in pre-5.14
by Anonymous Monk on Aug 14, 2016 at 20:22 UTC

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (8)
As of 2019-10-14 18:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?