This is the mail archive of the gcc-bugs@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[Bug c++/82081] New: Tail call optimisation of noexcept function leads to exception allowed through


https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82081

            Bug ID: 82081
           Summary: Tail call optimisation of noexcept function leads to
                    exception allowed through
           Product: gcc
           Version: 7.1.1
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: thiago at kde dot org
  Target Milestone: ---

When a noexcept function gets optimised with tail-call, the frame disappears so
the unwinder cannot know that the function was noexcept and thus
std::terminate() should be called.

Code:

$ cat throw.cpp
void noexcept_function() noexcept;

bool false_condition = false;
void will_throw()
{
    throw 1;
}

void wrapper()
{
    noexcept_function();
    if (false_condition)
        throw 42;
}
$ cat main.cpp
#include <iostream>

void will_throw();  // throws int
void wrapper();
extern bool false_condition;

void noexcept_function() noexcept { will_throw(); }

int main()
{
    try {
        wrapper();
    } catch (int v) {
        std::cout << "Caught " << v;
        return v;
    }
    return 0;
}

By bouncing around translation units, we prevent inlining. The compiler cannot
know that wrapper() calls noexcept_function(), which calls will_throw().

In debug mode, the program behaves as expected

$ g++ -O0 -g throw.cpp main.cpp
$ ./a.out
terminate called after throwing an instance of 'int'
[1]    46552 abort (core dumped)  ./a.out
(gdb) bt
#0  0x00007f9df0ce1a90 in raise () from /lib64/libc.so.6
#1  0x00007f9df0ce30f6 in abort () from /lib64/libc.so.6
#2  0x00007f9df1615235 in __gnu_cxx::__verbose_terminate_handler() () from
/usr/lib64/libstdc++.so.6
#3  0x00007f9df1613026 in ?? () from /usr/lib64/libstdc++.so.6
#4  0x00007f9df1611fe9 in ?? () from /usr/lib64/libstdc++.so.6
#5  0x00007f9df1612958 in __gxx_personality_v0 () from
/usr/lib64/libstdc++.so.6
#6  0x00007f9df10633a3 in ?? () from /lib64/libgcc_s.so.1
#7  0x00007f9df10638b0 in _Unwind_RaiseException () from /lib64/libgcc_s.so.1
#8  0x00007f9df16132a6 in __cxa_throw () from /usr/lib64/libstdc++.so.6
#9  0x00000000004009ed in will_throw () at throw.cpp:6
#10 0x0000000000400a2f in noexcept_function () at main.cpp:7
#11 0x00000000004009f6 in wrapper () at throw.cpp:11
#12 0x0000000000400a40 in main () at main.cpp:12

However, when optimised, we see that the exception thrown from will_throw()
does pass through and is caught by main():

$ g++ -O2 -g throw.cpp main.cpp
$ ./a.out 
Caught 1
(gdb) disass noexcept_function
Dump of assembler code for function noexcept_function():
   0x0000000000400b10 <+0>:     jmpq   0x400aa0 <will_throw()>


I see two possible paths to solving this.
1) forbid tail-call optimisation of a noexcept(false) call in a noexcept
function, so that there is a frame in place for the unwinder to find. That is,
the noexcept_function should be:
  sub  %rsp, 8
  call will_throw()
  retq
(GCC generates this under some conditions, like placing all functions in the
same TU but using -fno-inline)

2) wrap the call point of the noexcept function (in this case, wrapper()) with
an EH table that enforces that no exceptions should come out of it.

The first solution implies a performance penalty due to optimisation that could
not be used. If you choose to implement this, please try to disable this
correction under -fno-exceptions.

The second solution allows the runtime performance at the expense of expanding
EH tables around every noexcept function.

Neither solution completely solves the problem for mixed-age code in different
libraries: solution 1 solves the problem if the callee is recompiled but lets
the problem still happen if only the caller is recompiled. Solution 2 is the
dual converse: if the caller is recompiled, the problem is solved, but the
problem still happens if only the callee is recompiled.

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]