Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Unexpected result after localizing eval_error variable "$@" within "BEGIN" block

by bdimych (Monk)
on Dec 26, 2007 at 14:25 UTC ( [id://659045]=perlquestion: print w/replies, xml ) Need Help??

bdimych has asked for the wisdom of the Perl Monks concerning the following question:

> >cat 1.pl BEGIN { local $@; call_undefined_subroutine_or_another_error("argument"); } print "blabla\n"; > > > >perl 1.pl blabla > >
Why? I've found nothing about this in perlmod and perlvar.
  • Comment on Unexpected result after localizing eval_error variable "$@" within "BEGIN" block
  • Download Code

Replies are listed 'Best First'.
Re: Unexpected result after localizing eval_error variable "$@" within "BEGIN" block
by demerphq (Chancellor) on Dec 26, 2007 at 17:17 UTC

    Interesting. Presumably somehow there is an implied eval at work here, and your localiying $@ somehow prevents perl from noticing the. It doesnt make much sense to me tho, ill report this to p5p.

    ---
    $world=~s/war/peace/g

      From perlmod:

      It should be noted that "BEGIN" code blocks are executed inside string "eval()"’s.

      Try also:

      eval q{ local $@; call_undefined_subroutine_or_another_error("argument"); };

      versus:

      eval q{ call_undefined_subroutine_or_another_error("argument"); };

      versus:

      BEGIN { call_undefined_subroutine_or_another_error("argument"); }

        I'd call both cases a bug. local's restore needs to be done before die sets $@. Yes, I understand that this makes the implementation slightly more difficult since it requires storing what will end up in $@ somewhere while the stack is unwound. But it also allows for fixing the long-standing bug of DESTROY methods being able to clobber $@.

        Given what I perceive as the perversity of p5p's decision making, I'll note that at the very least BEGIN handling needs to be fixed to check the return value of eval rather than checking $@. Yes, this might mean forcing something like "; 1" onto the end of the BEGIN block's code before it is string-eval'd. But a better fix is discussed below.

        To reduce the impact of the change, I'd still set $@ early but also have eval save that value and set $@ to it right before it returns.

        (Update: Actually, eval isn't in the picture until the stack unwinding runs into it, so die needs to save $@ somewhere for eval to copy back into $@. And the stack unwinding can involve other cases of eval and die so we really need a stack of these saved values. So die should set $@ then push @@, $@;. Just before eval returns, it should $@= pop @@;?) (Update2: Perhaps better to have eval push a blank value onto @@ up front and have die set both $@ and $@[-1] then $@= pop @@; when eval finishes will always be safe. @@ would start out with one entry in it that perl itself would use so that DESTROY/local can't clobber the error message that caused perl to die. This means that you can use 'die' inside of DESTROY or such to change the error message but you can't just change $@ to do that.)

        I didn't mind the DESTROY bug in eval so much because local($@); fixes it. But this bug really sucks (in that I can't see a reasonable way to work around it -- forcing BEGIN { my $pe= $@; ...; $@= $pe } isn't reasonable, IMHO).

        This appears to mean that it is best to use local $@; to prevent $@ from being clobbered in some conditions, but also that any local $@; in a scope that gets unwound will break a surrounding eval. So the fix for one aspect of the bug makes the other aspect of the bug more likely. Time to just fix it.

        Looking into this also provided the following surprise:

        $@= "Before"; my $end= "None"; eval q{ { local $@; eval 'die "DoNotLeak\n"'; } $end= $@; }; print "($end)\n";

        prints "()" when I thought it should print "(Before)". I expect $@ to be set to the empty string when the eval returns successfully. But it appears that instead, $@ is set to the empty string when eval starts. Further testing shows that it is also set to the empty string at the end so I don't see the value in setting it at the start so I'd consider that a low-priority bug.

        Here is a little test script:

        use strict; use warnings; use Test qw( plan ok ); plan( tests => 12 ); sub Test { my $first= "None"; my $end= "None"; eval q{ { local $@ if $_[0]; eval 'die "DoNotLeak\n"'; die "DoNotHide\n" if $_[1]; $first= $@; } $end= $@; }; chomp for $first, $end; return( $first, $end ); } while( <DATA> ) { my( $local, $die, $one, $two, $three )= split /\s*,/, $_; $@= "Before"; my( $first, $end )= Test( $local, $die ); ok( $first, $one, "local:$local die:$die 1" ); ok( $end, $two, "local:$local die:$die 2" ); ok( $@, $three, "local:$local die:$die 3" ); } __END__ 0,0,DoNotLeak,DoNotLeak,, 1,0,DoNotLeak,Before ,, 0,1,None ,None ,DoNotHide 1,1,None ,None ,DoNotHide

        And my results:

        1..12 # Running under perl version 5.008008 for MSWin32 # Using Test.pm version 1.25 ok 1 ok 2 ok 3 ok 4 not ok 5 # Test 5 got: "" (- at line 31 fail #2) # Expected: "Before" (local:1 die:0 2) ok 6 ok 7 ok 8 ok 9 ok 10 ok 11 not ok 12 # Test 12 got: "" (- at line 32 fail #4) # Expected: "DoNotHide\n" (local:1 die:1 3)

        - tye        

        The question is whether the first example you posted is sane. I personally dont think it is. Thats not to say that im right, but id like to hear a cogent explanation of why im wrong before I change my mind. IOW, it seems to me that localizing $@ should affect only evals called from within the block. Once the block ends (either through run-to-bottom or through a fatal error) the localization should end and the value of the failed eval should be avalable in the outer context.

        In short, I consider the behaviour of the first example you posted to be inconsistant with both the general understanding of localization and my understanding of how localization works internally. In short I suspect a bug that is more or less the omission of a LEAVE statement.

        ---
        $world=~s/war/peace/g

Re: Unexpected result after localizing eval_error variable "$@" within "BEGIN" block
by ides (Deacon) on Dec 26, 2007 at 16:44 UTC

    What do you expect to happen? You are localizing the eval_error variable, not using it for anything, and not even using it in an eval.

    Frank Wiles <frank@revsys.com>
    www.revsys.com

      Well, it does make a difference. Without that line, this code throws an error, as expected. With that line, it doesn't. Seems like a potential bug.

      The behavior is not unique to invalid subroutine calls. More or less any error under the (F) category in perldiag seems to produce this behavior - not all though, for example it still dies if I put in a division by zero. Though even then, it just prints the dying message - no mention of it having anything to do with BEGIN (unlike the other errors).

      Update: above paragraph is slightly wrong. The difference has to do with whether the error can be caught at compile time or not. Compile time errors still die. But runtime errors get ignored in this construct.

      Update 2: While re-reading perlmod I failed to notice the line chromatic quoted. So that at least explains why $@ and BEGIN have anything to do with each other at all. But I agree with demerphq; localizing $@ inside eval'd code shouldn't silently cause subsequent errors from that code not to be reported in the enclosing code.

        Is line 7 (now commented out) what you referred to?

        no error here on:
        #!C:/perl/bin use warnings; use strict; BEGIN { local $@; # call_undefined_subroutine_or_another_error("argument"); } print "blabla\n";
        from perldoc perlvar
         Error Indicators
           The variables $@, $!, $^E, and $? contain information about different
           types of error conditions that may appear during execution of a Perl
           program. The variables are shown ordered by the "distance" between the
           subsystem which reported the error and the Perl process. They correspond
           to errors detected by the Perl interpreter, C library, operating system,
           or an external program, respectively.
        
           To illustrate the differences between these variables, consider the
           following Perl expression, which uses a single-quoted string:
        
               eval q{
                   open my $pipe, "/cdrom/install |" or die $!;
                   my @res = <$pipe>;
                   close $pipe or die "bad pipe: $?, $!";
               };
        
           After execution of this statement all 4 variables may have been set.
        
           $@ is set if the string to be "eval"-ed did not compile (this may happen
           if "open" or "close" were imported with bad prototypes), or if Perl code
           executed during evaluation die()d . In these cases the value of $@ is
           the compile error, or the argument to "die" (which will interpolate $!
           and $?). (See also Fatal, though.)
        

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://659045]
Approved by Corion
Front-paged by demerphq
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (5)
As of 2024-04-16 13:02 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found