https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128660
Bug ID: 128660
Summary: [14/15/16/17 Regression] wrong code: after an inlined
y = g () throws and the exception is caught, y has
been partly overwritten
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Keywords: wrong-code
Severity: normal
Priority: P3
Component: tree-optimization
Assignee: unassigned at gcc dot gnu.org
Reporter: lzy20000419 at outlook dot com
Target Milestone: ---
Fails with trunk (gcc 17.0.0 20261111, a2a11abfad35) and with 10.1.0 through
16.2.0; 9.5.0 works.
Target: x86_64-pc-linux-gnu (also -m32).
struct S { long a, b, c, d; };
int k;
__attribute__((noinline)) long f (long v) { if (k) throw 1; return v; }
static inline S g (long v) { return S{v, f (v), v, v}; }
int main ()
{
S y = { 1, 2, 3, 4 };
k = 1;
try { y = g (9); } catch (int) {}
if (y.a != 1)
__builtin_abort ();
return 0;
}
g++ -O1 t.C && ./a.out
Observed: abort, y.a is 9. Expected: exit 0. f throws before the assignment to
y completes, so y.a must still be 1.
-O2, -O3 and -Og fail too. -O0 and -O1 -fno-inline work.
Godbolt (trunk, -O1 vs -O1 -fno-inline): https://godbolt.org/z/T36doEdf7
After einline:
y.a = 9;
_23 = f (9); <- throws to the handler in main
y.b = _23; y.c = 9; y.d = 9;
declare_return_variable (tree-inline.cc) reuses MODIFY_DEST as the return
variable when it is an automatic, non-addressable decl. It does not check
whether the call can throw to a handler in the caller, where the old value of
MODIFY_DEST is still live. 9.5 still used a temporary here.
Related: PR128312 is the same symptom for a call that is not inlined, through
return slot optimization in tree-nrv.cc. A fix in dest_safe_for_nrv_p would not
cover this case, which is created by the inliner.
Possible fix: in declare_return_variable, do not reuse MODIFY_DEST when
stmt_can_throw_internal (cfun, id->call_stmt).
Disclaimer: This test case was found with LLM-assisted targeted testing, and
the report was written with LLM assistance.