BEGIN, eval and $^S

Discussion in 'Perl Misc' started by Adrien BARREAU, Jun 5, 2013.

  1. Hi all.

    If I run that piece of code:

    =====
    #!/usr/bin/perl

    use strict;
    use warnings;


    BEGIN {
    $SIG{ __DIE__ } = sub { print "Defined\n" if defined $^S; print
    ($^S ? "True\n" : "False\n"); };
    eval { die };
    }

    print "Done\n";
    =====

    It prints:
    False
    Done

    Well, that, I understand.


    If I change eval { die } to eval 'die', it prints:
    Defined
    True
    Done

    Which I don't get.

    My only guess: since eval "" is described as a "little Perl program",
    $^S is set to 1 because it refers to the "running" part of the eval,
    after it has been successfully parsed. But, my "main" program is in
    compilation time, so it seems strange.


    Did I missed something about all that in the documentation?

    Adrien.
    Adrien BARREAU, Jun 5, 2013
    #1
    1. Advertising

  2. On 06/05/2013 04:20 PM, Ben Morrow wrote:
    >
    > Quoth Adrien BARREAU<>:
    >> Hi all.
    >>
    >> If I run that piece of code:
    >>

    [ code skipped ]
    >>
    >> It prints:
    >> False
    >> Done
    >>
    >> Well, that, I understand.

    >
    > Hmm, well, that's the one that doesn't quite make sense to me. Perl
    > isn't parsing that BEGIN block,


    Aren't BEGIN block resolved during compilation phase?
    I mean:

    $ echo 'BEGIN { print "hello world\n" }' | perl -c -
    hello world
    - syntax OK


    > it's running it


    I can not say otherwise, but it's what you called
    'running-at-compile-time', and I thought that "compile-time" won.

    , so I would expect $^S
    > to be true. (Defined because we're running code, true because we're
    > inside an eval{} so exceptions are being caught.)
    >
    >> If I change eval { die } to eval 'die', it prints:
    >> Defined
    >> True
    >> Done
    >>
    >> Which I don't get.
    >>
    >> My only guess: since eval "" is described as a "little Perl program",
    >> $^S is set to 1 because it refers to the "running" part of the eval,
    >> after it has been successfully parsed. But, my "main" program is in
    >> compilation time, so it seems strange.

    >
    > Yes, that's basically right. Not entirely right, since that would mean
    > the first example should have $^S true as well,


    That's precisely what troubles me :).

    > but I suspect that's an
    > inconsistency or an accident of some kind.
    >
    > You can have as many layers of BEGIN{}/eval"" as you like, each invoking
    > a new level of running-at-compile-time or compiling-at-runtime. I think
    > this covers all the important cases:
    >

    [ skipped code ]
    >
    > The BEGIN entries don't entirely make sense to me: I would have said,
    > before trying it, that you could only get $^S=undef during a __DIE__
    > handler handling an exception from a syntax error. That's certainly what
    > the documentation implies, not to mention the blue Camel.


    Your exemple is really interesting, though I don't get understand
    everything either.

    >
    > If you have 5.14 you can use ${^GLOBAL_PHASE} to get the 'main'
    > interpreter state. On earlier perls you can use Devel::GlobalPhase to
    > emulate the core variable.


    I can not use one of those, I'll have to find something else (but I'll
    remember that module, didn't knew it).

    >
    >> Did I missed something about all that in the documentation?

    >
    > Yes, you missed the bit in perlvar that says 'don't use $SIG{__DIE__} or
    > $^S' :). What are you actually trying to do here? There's probably an
    > easier way to do it.


    I do agree with that, but I have to deal with existing stuff that has to
    stay. So I'm trying to fix insidious issues in it.

    Adrien.
    Adrien BARREAU, Jun 5, 2013
    #2
    1. Advertising

  3. [skip]

    > This is really where I started my reasoning. The English.pm name of $^S
    > is $EXCEPTIONS_BEING_CAUGHT, and that is what it was intended for: to
    > tell you, in a __DIE__ handler, whether the exception you have just
    > intercepted was going to be fatal or not. Having $^S be undefined in
    > eval{}-in-BEGIN rather defeats that purpose, since in that case
    > exceptions most definitely are being caught, yet $^S is false.
    >
    > It is probably possible to get a reliable indication of this using
    > caller: running up the call stack looking for a sub called "(eval)" will
    > tell you if you are inside some type of eval; some of the other elements
    > returned by caller will tell you what sort of eval (require/use,
    > eval-string, eval-block) you're dealing with. If you try this you need
    > to be aware that BEGIN blocks (and other magic blocks like DESTROY and
    > (I think) tie magic) are always wrapped in an implicit eval{}.


    As far as you know, is there any doc about what triggers "implicit eval{}"s?

    [skip]

    >>
    >> I do agree with that, but I have to deal with existing stuff that has to
    >> stay. So I'm trying to fix insidious issues in it.

    >
    > So, explain :). There might be a better answer than fighting with
    > __DIE__ and $^S.
    >


    Well, at first, it is work-related, so I am not that free.

    The code that uses to check $^S in a __DIE__ signal is a custom Logger
    (no link to anythin in CPAN).
    It has to catch when a die occurs in order to run some stuff (some dumps
    of our environment, CGI related stuff if run in that context, etc).

    So basically: catch, do stuff, rethrow.
    Rethrow basic idea: if ($eval) { return } else { exit -1 }
    (so it is that "if ($eval)" that made me start that thread).

    So, it doesn't try to achieve something with that __DIE__ signal, it
    just have to run stuff if a die occurs.
    I doubt I can do that in a relaly different way. But I'd love to get
    ideas if you have some :). "Rewrite it" or "use CPAN stuff instead" are
    not available options, sadly.


    Adrien.
    Adrien BARREAU, Jun 6, 2013
    #3

  4. >
    > Anything calling call_sv with G_EVAL. I don't think there's any
    > comprehensive list of Perl constructions, but essentially anything
    > called implicitly by perl. So, signal handlers, BEGIN/END/INIT/CHECK/
    > UNITCHECK, source filter subs, PROPAGATE methods on exception objects,
    > DESTROY methods, overloaded constant handlers. Interestingly it seems I
    > was wrong about tie magic: these methods are called without an eval,
    > presumably because the call is entirely within the normal control flow.
    >
    > Of course, the point is that exceptions are actually being caught in all
    > these cases. Some of them (signal handlers) will rethrow anything they
    > catch; some (BEGIN) will abort the current compilation unit, and some
    > (INIT) will abort the whole program.


    Interesting, thanks :).
    For now, I not fluent in perlguts stuff, but I'll take a look to the C
    API to better understand that.

    >
    >>>> I do agree with that, but I have to deal with existing stuff that has to
    >>>> stay. So I'm trying to fix insidious issues in it.
    >>>
    >>> So, explain :). There might be a better answer than fighting with
    >>> __DIE__ and $^S.
    >>>

    >>
    >> Well, at first, it is work-related, so I am not that free.
    >>
    >> The code that uses to check $^S in a __DIE__ signal is a custom Logger
    >> (no link to anythin in CPAN).
    >> It has to catch when a die occurs in order to run some stuff (some dumps
    >> of our environment, CGI related stuff if run in that context, etc).
    >>
    >> So basically: catch, do stuff, rethrow.
    >> Rethrow basic idea: if ($eval) { return } else { exit -1 }
    >> (so it is that "if ($eval)" that made me start that thread).
    >>
    >> So, it doesn't try to achieve something with that __DIE__ signal, it
    >> just have to run stuff if a die occurs.
    >> I doubt I can do that in a relaly different way. But I'd love to get
    >> ideas if you have some :). "Rewrite it" or "use CPAN stuff instead" are
    >> not available options, sadly.

    >
    > Have you considered the obvious option: wrapping the code in a top-level
    > eval? The huge advantage of that is that if the exception is caught and
    > handled along the way you don't ever get as far as your top-level
    > handler. The most important disadvantage is that you can't get a stack
    > trace (or any other information about the immediate environment when the
    > exception was thrown), since the stack's been unwound, but there are
    > other ways of doing that.
    >
    > The cleanest is to write an exception class that takes a stack trace
    > when it's created, and make sure you only throw exceptions of that
    > class. If you already have an exception class you can obviously modify
    > it to do this fairly easily; Devel::StackTrace will give you a decent
    > realisation of a stack trace.
    >
    > If you can't do that you could use a CORE::GLOBAL::die override to stuff
    > appropriate information into globals and hand the exception back to
    > CORE::die to throw normally. Then you can pull the information out again
    > in your top-level handler, but *only* if the exception actually ends up
    > getting that far.
    >
    > If for some reason you don't want to wrap the program in eval, you could
    > install an END block instead. END blocks are called before a top-level
    > exception exits, and they are called with $? == 255 since that is the
    > exit code perl is going to use. This is rather less reliable that an
    > eval, though, and there isn't any way of recovering and deciding not to
    > exit after all, so it's something of a last resort. (Of course, if
    > you're interesting in 'logging stuff', you may well want to log non-zero
    > exits in any case.)
    >
    > The point here is to avoid trying to predict the future: it's extremely
    > difficult to guess whether a given exception will end up being fatal or
    > not, especially given that even if it is caught it might be rethrown, so
    > rather than guess just have the 'inner handler' stash the information
    > the 'outer handler' will need to do the proper reporting.


    Once again, I fully agree with you, but I can not something else than
    predicting the future.

    That logger is a core module to us, so it is used in thousands of
    scripts: wrapping all scripts in eval{}s can not be done, neither
    changing all uses of die to use an exception class or overridding
    CORE::GLOBAL::die (because I can not do that in all scripts so nothing
    will ever ensure me that it is loaded).
    END{} stuff is great, but as you said, only for logging purposes.

    I'll, of course, keep all that in mind if big changes have to be done
    one day.


    Adrien.
    Adrien BARREAU, Jun 6, 2013
    #4
  5. On 06/06/2013 03:03 PM, Ben Morrow wrote:
    >
    > Quoth Adrien BARREAU<>:
    >>
    >> That logger is a core module to us, so it is used in thousands of
    >> scripts: wrapping all scripts in eval{}s can not be done, neither
    >> changing all uses of die to use an exception class or overridding
    >> CORE::GLOBAL::die (because I can not do that in all scripts so nothing
    >> will ever ensure me that it is loaded).

    >
    > And you really can't arrange to call these scripts through a wrapper
    > something like this?
    >
    > #!/usr/bin/perl
    >
    > my @stack;
    > *CORE::GLOBAL::die = sub {
    > @stack = ();
    > while (my @frame = caller @stack) {
    > push @stack, \@frame;
    > }
    > # or just save away Carp::longmess, or whatever
    > };
    >
    > my $script = shift @ARGV;
    > # do is a form of string eval, so it catches exceptions
    > unless (do $script) {
    > # emit logs based on $@, @stack and other information
    > }
    >
    > ISTM all that should take is a minor adjustment to the webserver
    > configuration, or something like that, but I suppose the situation might
    > be more complicated than that.
    >


    That gives me some ideas of things I can try, but I can do something
    like that, no.
    The main thing is that I have to cover *every* ways the issue can
    appear, and I'll never be able to do that by adding something. I can
    only do changes in existing things, "paths" that are always used and
    will stay.

    Thanks for your answers Ben :).

    Adrien.
    Adrien BARREAU, Jun 6, 2013
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Eric Newton
    Replies:
    3
    Views:
    9,388
    Brock Allen
    Apr 4, 2005
  2. DataBinder.Eval and Eval.

    , Jun 16, 2006, in forum: ASP .Net
    Replies:
    1
    Views:
    542
    Karl Seguin [MVP]
    Jun 16, 2006
  3. Alex van der Spek

    eval('07') works, eval('08') fails, why?

    Alex van der Spek, Jan 8, 2009, in forum: Python
    Replies:
    6
    Views:
    1,432
    Bruno Desthuilliers
    Jan 8, 2009
  4. Liang Wang
    Replies:
    8
    Views:
    129
    Ben Morrow
    Feb 2, 2008
  5. Marc Girod

    to eval or not to eval?

    Marc Girod, Apr 19, 2011, in forum: Perl Misc
    Replies:
    2
    Views:
    154
    Marc Girod
    Apr 19, 2011
Loading...

Share This Page