https://gcc.gnu.org/bugzilla/show_bug.cgi?id=123227
Bug ID: 123227
Summary: -Os miscompiles code by merging member and non-member
functions with different nullability assumptions
Product: gcc
Version: 13.2.1
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: ipa
Assignee: unassigned at gcc dot gnu.org
Reporter: dave.w4 at coalgebraic dot com
Target Milestone: ---
Compiling the code below with -Os generates a binary which segfaults.
Tentative diagnosis:
- Under -Os, GCC appears to merge a non-static member function and a free
function with identical loop bodies but different nullability guarantees.
- The member function may assume that the `this` pointer is non-null on entry;
the free function may not assume that its argument is non-null.
- After merging, the resulting code dereferences its argument unconditionally,
causing a segfault when the free function is called with a null pointer.
The problem goes away with any of the following steps:
- Compiling without optimization, or compiling with -O1, -O2 or -O3.
- Removing the definition of the member function `get_vals` (which is never
called).
- Changing the definition of either the member function or the free function
so that the compiler does not try to merge them.
- Replacing the enum with an integer type.
My local GCC is 13.2.1, but checking on Compiler Explorer suggests that the
problem came in with GCC 7 and is present through to 15.2.
Compiler invocation:
gcc -Os -Wall -Wextra fail.cc -o fail
Code:
enum Val { zero = 0 };
inline Val&
operator|=(Val& a, Val b)
{
return a = static_cast<Val>(static_cast<int>(a) | static_cast<int>(b));
}
struct Data { Val val; };
struct Link {
Val get_vals();
Data* data;
Link* next;
};
Val
Link::get_vals()
{
Val v = zero;
for (Link* l = this; l; l = l->next)
v |= l->data->val;
return v;
}
Val
get_vals(Link* l)
{
Val v = zero;
for (; l; l = l->next)
v |= l->data->val;
return v;
}
int
main(int, char**)
{
return get_vals(0);
}