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);
    }

Reply via email to