BEGIN, eval and $^S



Hi all.

If I run that piece of code:


use strict;
use warnings;

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

print "Done\n";

It prints:

Well, that, I understand.

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

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?





Quoth Adrien BARREAU said:
Hi all.

If I run that piece of code:
[ code skipped ]

It prints:

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.)

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).
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.



This is really where I started my reasoning. The 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?

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.



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.
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.





And you really can't arrange to call these scripts through a wrapper
something like this?


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 [email protected], @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 :).


Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question