Skip to content

$SIG{__DIE__} handler doesn't work when called explicitly #22984

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mauke opened this issue Feb 10, 2025 · 1 comment · Fixed by #22995
Closed

$SIG{__DIE__} handler doesn't work when called explicitly #22984

mauke opened this issue Feb 10, 2025 · 1 comment · Fixed by #22995

Comments

@mauke
Copy link
Contributor

mauke commented Feb 10, 2025

Description

If you register a subroutine as a $SIG{__DIE__} handler, then call it normally, and that sub then (directly or indirectly) throws an exception, the $SIG{__DIE__} behavior doesn't kick in; i.e. the sub is not re-invoked to handle the exception.

Steps to Reproduce

$ perl -we 'my $x = 0; sub foo { print "foo()\n"; die $x++ } $SIG{__DIE__} = \&foo; foo'
foo()
0 at -e line 1.

(Note that $SIG{__DIE__} has no effect here.)

Expected behavior

$ perl -we 'my $x = 0; sub foo { print "foo()\n"; die $x++ } $SIG{__DIE__} = \&foo; foo'
foo()
foo()
1 at -e line 1.

There should be exactly two calls to foo: One from foo in the main code, the other via $SIG{__DIE__}. It should not recurse further because as perldoc -v %SIG explains:

The __DIE__ handler is explicitly disabled during the call, so that you can die from a __DIE__ handler.

@demerphq
Copy link
Collaborator

There should be exactly two calls to foo

Agreed, with the minor quibble that if you changed the code to

perl -we 'my $x = 0; sub foo { print "foo()\n"; die $x++ } $SIG{__DIE__} = \&foo; $SIG{__DIE__}->()'

I think I would expect that foo is called only once. Although I could be convinced it should be called twice. Regardless we should document this explicitly.

mauke added a commit to mauke/perl5 that referenced this issue Feb 12, 2025
The documentation for %SIG (in perlvar) states:

> The `__DIE__` handler is explicitly disabled during the call, so that
> you can die from a `__DIE__` handler.  Similarly for `__WARN__`.

This has never really been true.

There were two basic checks to prevent infinite recursion from a __DIE__
or __WARN__ handler:

 1. When an exception is thrown, if $SIG{__DIE__} references a
    subroutine that is currently active (somewhere on the call stack at
    the point of the exception), then die() unwinds the stack directly,
    bypassing the handler. (The same applies mutatis mutandis to
    $SIG{__WARN__}/warn().)
    This behavior is wrong because the subroutine may have been invoked
    normally first (i.e. not via the %SIG machinery), so the handler
    should still kick in. This is bug GH Perl#22984.
    It also causes issues if the subroutine transfers control "sideways"
    via goto &othersub because then the registered handler is no longer
    considered "active" even though Perl code is still executing in the
    context of a __DIE__/__WARN__ handler. Then, if the goto'd &othersub
    triggers a warning/exception, the __DIE__/__WARN__ handler will be
    invoked recursively, eventually leading to a C stack overflow. This
    is bug GH Perl#14527.
 2. The code for $SIG{__WARN__} (since c5be5b4) and $SIG{__DIE__}
    (since 8b4094f) mitigates the latter issue by internally
    unsetting the __DIE__/__WARN__ hooks for the duration of the handler
    call.
    Unfortunately, this is not a complete fix because any modification
    of $SIG{__DIE__}/$SIG{__WARN__} within the handler, even seeming
    no-ops such as $SIG{__DIE__} = $SIG{__DIE__} or { local
    $SIG{__DIE__}; }, will reïnstate the internal hooks, thus reärming
    the __DIE__/__WARN__ handlers. This is bug GH Perl#22987.

This patch adds two interpreter-global variables that record whether we
are currently executing a __DIE__/__WARN__ handler. This fully replaces
the old heuristics by a precise check that prevents recursive handler
invocation and nothing more.

Exporter::Heavy had to be patched because it relied on the old (buggy)
behavior: It registered a $SIG{__WARN__} handler that would reässign
$SIG{__WARN__} and then call warn(), expecting the new handler to be
called (i.e. two (nested) warn hooks to be active simultaneously). This
is no longer possible with the new implementation.

Fixes Perl#22984, Perl#22987.
@mauke mauke closed this as completed in 9209630 Feb 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants