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.