Bug 97595 - [11 Regression] bogus -Wstringop-overflow due to DECL_SIZE_UNIT underreporting field size
Summary: [11 Regression] bogus -Wstringop-overflow due to DECL_SIZE_UNIT underreportin...
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: tree-optimization (show other bugs)
Version: 11.0
: P3 normal
Target Milestone: 11.0
Assignee: Martin Sebor
URL:
Keywords: diagnostic
: 98083 (view as bug list)
Depends on:
Blocks:
 
Reported: 2020-10-27 15:22 UTC by Jonathan Wakely
Modified: 2021-02-23 23:12 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Known to work: 10.2.1
Known to fail: 11.0
Last reconfirmed: 2020-10-27 00:00:00


Attachments
preprocessed source, unreduced, gzipped (94.11 KB, application/gzip)
2020-10-27 15:22 UTC, Jonathan Wakely
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Jonathan Wakely 2020-10-27 15:22:06 UTC
This code (reduced from the libstdc++) produces spurious -Wstringop-overflow diagnostics since r11-3827 when compiled with -Wsystem-headers:

#include <iostream>
template class std::basic_iostream<char>;



In file included from /usr/include/c++/10/bits/nested_exception.h:40,
                 from /usr/include/c++/10/exception:148,
                 from /usr/include/c++/10/ios:39,
                 from /usr/include/c++/10/ostream:38,
                 from /usr/include/c++/10/iostream:39,
                 from inst.cc:1:
In member function ‘void std::basic_ios<_CharT, _Traits>::swap(std::basic_ios<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’,
    inlined from ‘void std::basic_istream<_CharT, _Traits>::swap(std::basic_istream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’ at /usr/include/c++/10/istream:634:18,
    inlined from ‘void std::basic_iostream<_CharT, _Traits>::swap(std::basic_iostream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’ at /usr/include/c++/10/istream:882:29,
    inlined from ‘std::basic_iostream<_CharT, _Traits>& std::basic_iostream<_CharT, _Traits>::operator=(std::basic_iostream<_CharT, _Traits>&&) [with _CharT = char; _Traits = std::char_traits<char>]’ at /usr/include/c++/10/istream:876:6:
/usr/include/c++/10/bits/move.h:198:11: warning: writing 1 byte into a region of size 0 [-Wstringop-overflow=]
In file included from /usr/include/c++/10/iostream:40,
                 from inst.cc:1:
/usr/include/c++/10/istream: In member function ‘std::basic_iostream<_CharT, _Traits>& std::basic_iostream<_CharT, _Traits>::operator=(std::basic_iostream<_CharT, _Traits>&&) [with _CharT = char; _Traits = std::char_traits<char>]’:
/usr/include/c++/10/istream:824:11: note: at offset 224 to object ‘std::basic_iostream<char>::<anonymous>’ with size 16 declared here
In file included from /usr/include/c++/10/bits/nested_exception.h:40,
                 from /usr/include/c++/10/exception:148,
                 from /usr/include/c++/10/ios:39,
                 from /usr/include/c++/10/ostream:38,
                 from /usr/include/c++/10/iostream:39,
                 from inst.cc:1:
In member function ‘void std::basic_ios<_CharT, _Traits>::swap(std::basic_ios<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’,
    inlined from ‘void std::basic_istream<_CharT, _Traits>::swap(std::basic_istream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’ at /usr/include/c++/10/istream:634:18,
    inlined from ‘void std::basic_iostream<_CharT, _Traits>::swap(std::basic_iostream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’ at /usr/include/c++/10/istream:882:29,
    inlined from ‘std::basic_iostream<_CharT, _Traits>& std::basic_iostream<_CharT, _Traits>::operator=(std::basic_iostream<_CharT, _Traits>&&) [with _CharT = char; _Traits = std::char_traits<char>]’ at /usr/include/c++/10/istream:876:6:
/usr/include/c++/10/bits/move.h:199:11: warning: writing 1 byte into a region of size 0 [-Wstringop-overflow=]
In file included from /usr/include/c++/10/iostream:40,
                 from inst.cc:1:
/usr/include/c++/10/istream: In member function ‘std::basic_iostream<_CharT, _Traits>& std::basic_iostream<_CharT, _Traits>::operator=(std::basic_iostream<_CharT, _Traits>&&) [with _CharT = char; _Traits = std::char_traits<char>]’:
/usr/include/c++/10/istream:824:11: note: at offset 224 to object ‘std::basic_iostream<char>::<anonymous>’ with size 16 declared here
In file included from /usr/include/c++/10/bits/nested_exception.h:40,
                 from /usr/include/c++/10/exception:148,
                 from /usr/include/c++/10/ios:39,
                 from /usr/include/c++/10/ostream:38,
                 from /usr/include/c++/10/iostream:39,
                 from inst.cc:1:
In member function ‘void std::basic_ios<_CharT, _Traits>::swap(std::basic_ios<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’,
    inlined from ‘void std::basic_istream<_CharT, _Traits>::swap(std::basic_istream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’ at /usr/include/c++/10/istream:634:18,
    inlined from ‘void std::basic_iostream<_CharT, _Traits>::swap(std::basic_iostream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’ at /usr/include/c++/10/istream:882:29:
/usr/include/c++/10/bits/move.h:198:11: warning: writing 1 byte into a region of size 0 [-Wstringop-overflow=]
In file included from /usr/include/c++/10/iostream:40,
                 from inst.cc:1:
/usr/include/c++/10/istream: In member function ‘void std::basic_iostream<_CharT, _Traits>::swap(std::basic_iostream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’:
/usr/include/c++/10/istream:824:11: note: at offset 224 to object ‘std::basic_iostream<char>::<anonymous>’ with size 16 declared here
In file included from /usr/include/c++/10/bits/nested_exception.h:40,
                 from /usr/include/c++/10/exception:148,
                 from /usr/include/c++/10/ios:39,
                 from /usr/include/c++/10/ostream:38,
                 from /usr/include/c++/10/iostream:39,
                 from inst.cc:1:
In member function ‘void std::basic_ios<_CharT, _Traits>::swap(std::basic_ios<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’,
    inlined from ‘void std::basic_istream<_CharT, _Traits>::swap(std::basic_istream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’ at /usr/include/c++/10/istream:634:18,
    inlined from ‘void std::basic_iostream<_CharT, _Traits>::swap(std::basic_iostream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’ at /usr/include/c++/10/istream:882:29:
/usr/include/c++/10/bits/move.h:199:11: warning: writing 1 byte into a region of size 0 [-Wstringop-overflow=]
In file included from /usr/include/c++/10/iostream:40,
                 from inst.cc:1:
/usr/include/c++/10/istream: In member function ‘void std::basic_iostream<_CharT, _Traits>::swap(std::basic_iostream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’:
/usr/include/c++/10/istream:824:11: note: at offset 224 to object ‘std::basic_iostream<char>::<anonymous>’ with size 16 declared here
Comment 1 Jonathan Wakely 2020-10-27 15:22:58 UTC
Created attachment 49452 [details]
preprocessed source, unreduced, gzipped
Comment 2 Jonathan Wakely 2020-10-27 15:27:07 UTC
Reduced:

#include <iostream>
template void std::basic_iostream<char>::swap(basic_iostream&);

The preprocessed code is no smaller, but this should only generate code for the problematic function, not the entire class and all its bases.
Comment 3 Martin Sebor 2020-10-27 16:28:03 UTC
I can confirm the warning but I'm not sure the bug is in the middle end code.  Let me CC Jason for his comments.

The warning triggers for the MEM_REF below:

  MEM[(char &)_10 + 224] = _24;

in the following GIMPLE ("type" is char).  The destination of the access is this_3(D)->D.45669 which is the basic_istream subobject.  Its size reported by DECL_SIZE_UNIT() in component_ref_size() is 16.  The initial offset (i.e., _10) is indeterminate but it's taken to be in the range bounded by the size of the object, or [0, 16].  Given that, the constant offset 224 is determined to be out of bounds.

The change introduced in r11-3827 that triggers the warning is the assumption that unless determined otherwise, an indeterminate offset into an object must be bounded by the object's size.

But sizeof (basic_istream) is 280, not 16.  This is also what TYPE_SIZE_UNIT() reports, so the bug here is either in DECL_SIZE_UNIT(), or in using it in lieu of TYPE_SIZE_UNIT().  I'd expect the two to return the same result so this seems like a front end bug to me but I will defer to Jason.

std::basic_iostream<char>::swap (struct basic_iostream * const this, struct basic_iostream & __rhs)
{
  struct basic_istream * _1;
  struct basic_istream * _2;
  int (*) () * _7;
  long int _8;
  sizetype _9;
  struct basic_ios * _10;
  int (*) () * _11;
  long int _12;
  sizetype _13;
  struct basic_ios & _14;
  long int _15;
  long int _16;
  struct ios_base * _17;
  struct ios_base * _18;
  struct locale * _19;
  struct locale * _20;
  struct basic_ostream * _21;
  struct basic_ostream * _22;
  char _23;
  char _24;
  bool _25;
  bool _26;

  <bb 2> [local count: 1073741824]:
  _1 = &this_3(D)->D.45669;
  _2 = &__rhs_4(D)->D.45669;
  _7 = MEM[(struct basic_istream *)this_3(D)]._vptr.basic_istream;
  _8 = MEM[(long int *)_7 + -24B];
  _9 = (sizetype) _8;
  _10 = _1 + _9;
  _11 = MEM[(struct basic_istream *)__rhs_4(D)]._vptr.basic_istream;
  _12 = MEM[(long int *)_11 + -24B];
  _13 = (sizetype) _12;
  _14 = _2 + _13;
  _17 = &_10->D.42253;
  _18 = &_14->D.42253;
  std::ios_base::_M_swap (_17, _18);
  _19 = &_10->D.42253._M_ios_locale;
  std::basic_ios<char>::_M_cache_locale (_10, _19);
  _20 = &_14->D.42253._M_ios_locale;
  std::basic_ios<char>::_M_cache_locale (_14, _20);
  _21 = MEM[(struct basic_ostream * &)_10 + 216];
  _22 = MEM[(struct basic_ostream * &)_14 + 216];
  MEM[(struct basic_ostream * &)_10 + 216] = _22;
  MEM[(struct basic_ostream * &)_14 + 216] = _21;
  _23 = MEM[(type &)_10 + 224];                        <<< -Wstringop-overflow
  _24 = MEM[(type &)_14 + 224];
  MEM[(char &)_10 + 224] = _24;
  MEM[(char &)_14 + 224] = _23;
  _25 = MEM[(type &)_10 + 225];
  _26 = MEM[(type &)_14 + 225];
  MEM[(bool &)_10 + 225] = _26;
  MEM[(bool &)_14 + 225] = _25;
  _15 = MEM[(type &)this_3(D) + 8];
  _16 = MEM[(type &)__rhs_4(D) + 8];
  MEM[(long int &)this_3(D) + 8] = _16;
  MEM[(long int &)__rhs_4(D) + 8] = _15;
  return;

}
Comment 4 Martin Sebor 2020-11-14 22:19:47 UTC
Here's a small test case that causes a bogus false positive with patched GCC (https://gcc.gnu.org/pipermail/gcc-patches/2020-November/558775.html).

$ cat t.C && gcc -O2 -S -Wall t.C
struct A  { char a[32]; };
struct B: virtual A { };
struct C: B { };

struct D 
{
  B &b;
  D (B&);
};

D::D (B &b): b (b) { }

void f (void*);

void g ()
{
  C c;
  D d (c);
  f (&d);
}
In constructor ‘D::D(B&)’,
    inlined from ‘void g()’ at t.C:18:9:
t.C:11:14: warning: subscript 0 is outside array bounds of ‘B’ [-Warray-bounds]
   11 | D::D (B &b): b (b) { }
      |              ^~~~~
t.C: In function ‘void g()’:
t.C:3:8: note: source object ‘C::<anonymous>’ of size 8
    3 | struct C: B { };
      |        ^
Comment 5 Jason Merrill 2020-11-16 17:10:03 UTC
(In reply to Martin Sebor from comment #3)
> I can confirm the warning but I'm not sure the bug is in the middle end
> code.  Let me CC Jason for his comments.
> 
> The warning triggers for the MEM_REF below:
> 
>   MEM[(char &)_10 + 224] = _24;
> 
> in the following GIMPLE ("type" is char).  The destination of the access is
> this_3(D)->D.45669 which is the basic_istream subobject.  Its size reported
> by DECL_SIZE_UNIT() in component_ref_size() is 16.  The initial offset
> (i.e., _10) is indeterminate but it's taken to be in the range bounded by
> the size of the object, or [0, 16].  Given that, the constant offset 224 is
> determined to be out of bounds.

What you're seeing is that the DECL_SIZE of the base subobject excludes any virtual bases, because they are laid out in the most derived class, while the TYPE_SIZE of the base type includes them.  CLASSTYPE_SIZE of the base type should match the DECL_SIZE.

It's definitely OK for a program to refer to a virtual base through any of the derived classes.

> The change introduced in r11-3827 that triggers the warning is the assumption > that unless determined otherwise, an indeterminate offset into an object must > be bounded by the object's size.

So this assumption is invalid for base subobjects of types with virtual bases; such an offset is only bounded by the most-derived object's size.
Comment 6 Martin Sebor 2020-12-01 16:16:42 UTC
*** Bug 98083 has been marked as a duplicate of this bug. ***
Comment 7 GCC Commits 2020-12-01 22:11:55 UTC
The master branch has been updated by Martin Sebor <msebor@gcc.gnu.org>:

https://gcc.gnu.org/g:b76f83e3859f738809d3aa8bd9dc14e10fc40e24

commit r11-5628-gb76f83e3859f738809d3aa8bd9dc14e10fc40e24
Author: Martin Sebor <msebor@redhat.com>
Date:   Tue Dec 1 15:10:30 2020 -0700

    PR middle-end/97595 - bogus -Wstringop-overflow due to DECL_SIZE_UNIT underreporting field size
    
    gcc/ChangeLog:
    
            PR middle-end/97595
            * tree.c (component_ref_size): Fail when DECL_SIZE != TYPE_SIZE.
            * tree.h (DECL_SIZE, TYPE_SIZE): Update comment.
    
    gcc/testsuite/ChangeLog:
    
            PR middle-end/97595
            * g++.dg/warn/Warray-bounds-14.C: New test.
            * g++.dg/warn/Wstringop-overflow-6.C: New test.
Comment 8 Martin Sebor 2020-12-01 22:13:13 UTC
Fixed in r11-5628.
Comment 9 Martin Sebor 2021-02-23 23:12:48 UTC
See pr22488 for the underlying problem with the difference between DECL_SIZE and TYPE_SIZE of classes with virtual bases.