https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124395
Bug ID: 124395
Summary: [contracts] ICE in check_postconditions_in_redecl when
instantiating function template with pre()/post() and
empty parameter pack
Product: gcc
Version: 16.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: kachalenko.denis at gmail dot com
Target Milestone: ---
GCC trunk (16.0.1 20260302, x86_64-w64-mingw32) crashes with an internal
compiler error (segfault) when a function template with a parameter pack used
as function parameters has a pre() or post() contract annotation, and the
template is instantiated with an empty pack.
Non-empty pack instantiations compile correctly. All three evaluation semantics
(enforce, observe, ignore) crash identically.
Minimal reproducer:
template<typename... Ts>
void f(Ts...) pre(true) {}
void g() { f(); }
$ g++ -std=c++2c -fcontracts -c file.cpp
Additional test cases:
Member function (also crashes):
template<typename... Ts>
struct S { void f(Ts...) pre(true) {} };
S<> s;
post() (also crashes):
template<typename... Ts>
struct S { int f(Ts...) post(r: r > 0) { return 1; } };
S<> s;
Non-empty pack (compiles successfully):
template<typename... Ts>
void f(Ts...) pre(true) {}
void g() { f(1); } // OK
void h() { f(1, 2, 3); } // OK
Expected behavior:
Code compiles without error. P2900R14 (Contracts for C++) places no restriction
on using pre()/post() with parameter packs.
Actual behavior:
file.cpp: In substitution of 'template<class ... Ts> void f(Ts ...) [with Ts =
{}]':
file.cpp:3:13: required from here
3 | void g() { f(); }
| ~^~
file.cpp:2:6: internal compiler error: Segmentation fault
2 | void f(Ts...) pre(true) {}
| ^
Backtrace:
0x4007d9d3 check_postconditions_in_redecl(tree_node*, tree_node*)
../../gcc-source/gcc/cp/contracts.cc:583
0x4007d9d3 check_postconditions_in_redecl(tree_node*, tree_node*)
../../gcc-source/gcc/cp/contracts.cc:573
0x40203e93 tsubst_function_decl
../../gcc-source/gcc/cp/pt.cc:15304
0x401ff499 tsubst_decl
../../gcc-source/gcc/cp/pt.cc:15701
0x401f2593 tsubst(tree_node*, tree_node*, int, tree_node*)
../../gcc-source/gcc/cp/pt.cc:16871
0x40216cc1 instantiate_class_template(tree_node*)
../../gcc-source/gcc/cp/pt.cc:12984
0x4026d8b5 complete_type(tree_node*)
../../gcc-source/gcc/cp/typeck.cc:138
0x4008b100 start_decl_1(tree_node*, bool)
../../gcc-source/gcc/cp/decl.cc:6862
Analysis:
check_postconditions_in_redecl iterates parameter lists of the original and
substituted declarations:
tree t1 = FUNCTION_FIRST_USER_PARM (olddecl);
tree t2 = FUNCTION_FIRST_USER_PARM (newdecl);
for (; t1 && t1 != void_list_node;
t1 = TREE_CHAIN (t1), t2 = TREE_CHAIN (t2))
When a pack expands to zero parameters, newdecl has no user parameters (t2 is
null), while olddecl still has the unexpanded pack parameter. The loop advances
t2 via TREE_CHAIN(t2) when t2 is already null, causing the segfault.
Workaround: Use contract_assert(expr) inside the function body instead of
pre().