This is the mail archive of the
gcc-bugs@gcc.gnu.org
mailing list for the GCC project.
[Bug c++/82081] New: Tail call optimisation of noexcept function leads to exception allowed through
- From: "thiago at kde dot org" <gcc-bugzilla at gcc dot gnu dot org>
- To: gcc-bugs at gcc dot gnu dot org
- Date: Sat, 02 Sep 2017 06:43:30 +0000
- Subject: [Bug c++/82081] New: Tail call optimisation of noexcept function leads to exception allowed through
- Auto-submitted: auto-generated
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.