https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127790

            Bug ID: 127790
           Summary: [16 Regression] -Warray-bounds false positive in
                    speculatively devirtualized call on object of known
                    dynamic type
           Product: gcc
           Version: 16.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: eindenbom at microsoft dot com
  Target Milestone: ---

Created attachment 65789
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65789&action=edit
Repro code

$ g++ -O2 -Wall -std=c++20 -c devirt-warray-bounds.cpp

GCC 16.2.0 (Ubuntu 16.2.0-2ubuntu1) emits 9 -Warray-bounds warnings on the
attached standalone testcase, which uses only the standard library. GCC 15.2.0
emits none, and -fno-devirtualize-speculatively silences all of them. The same
happens at -O3 and with -std=c++23.
devirt-warray-bounds.cpp: In member function 'int Builder::BuildBias(BlobPtr&,
int) const':
devirt-warray-bounds.cpp:77:47: warning: array subscript 5 is outside array
bounds of 'void [56]' [-Warray-bounds=]
   77 | inline size_t HashOf(const Kit& v) { return v.m_type; }
      |                                             ~~^~~~~~
devirt-warray-bounds.cpp:39:23: note: at offset 80 into object of size 56
allocated by 'operator new'
devirt-warray-bounds.cpp:47:29: warning: array subscript 9 is outside array
bounds of 'void [56]' [-Warray-bounds=]
devirt-warray-bounds.cpp:39:23: note: at offset 72 into object of size 56
allocated by 'operator new'
...

Pattern:
- IKey is a polymorphic base with an intrusive atomic refcount. ProxyKey<F,
Types...> is a final class template derived from it. There are two
instantiations in the TU: a small one (56 bytes, used in BuildBias) and a
larger one (used in BuildTransform).
- GetOrCreate creates the key with new ProxyKey<...> and converts it to
RefPtr<const IKey>, which calls AddRef (a seq_cst __atomic_add_fetch). It then
calls the virtual Hash() through the base pointer. unordered_map::find also
calls IsEqual() virtually.
- The -fdump-ipa-devirt output shows speculative edges to the Hash() and
IsEqual() overrides of both final types.
- After inlining into BuildBias, the object's dynamic type is known from the
new expression. Even so, the guard vptr == &vtable(larger ProxyKey) is never
folded away. The atomic between the vptr store and its reload seems to stop GCC
from forwarding the stored vptr.
- -Warray-bounds then checks the inlined body of the larger type's
Hash()/IsEqual(), which can never run, against the 56-byte allocation. It
reports reads at offsets 64, 72 and 80.
The reproduction depends on inlining heuristics. Removing the unordered_map
find/emplace from GetOrCreate makes the problem disappear, because the Hash()
call is then resolved directly. So does reducing the program to a single
ProxyKey type.

Possible solution:
If devirtualization check succeeds (function pointer matches devirtualized
body), the this pointer at the call site needs to be promoted to the type of
the devirtualized function body for the devirtualized function body branch.

Reply via email to