Bug 1561373 - GCC 8.0.1 miscompiles template using std::initializer_list
Summary: GCC 8.0.1 miscompiles template using std::initializer_list
Keywords:
Status: CLOSED NOTABUG
Alias: None
Product: Fedora
Classification: Fedora
Component: gcc
Version: rawhide
Hardware: x86_64
OS: Linux
unspecified
medium
Target Milestone: ---
Assignee: Jakub Jelinek
QA Contact: Fedora Extras Quality Assurance
URL:
Whiteboard:
Depends On:
Blocks: 1559315
TreeView+ depends on / blocked
 
Reported: 2018-03-28 08:51 UTC by Tilmann Scheller
Modified: 2018-03-28 11:41 UTC (History)
11 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed: 2018-03-28 11:41:45 UTC
Type: Bug
Embargoed:


Attachments (Terms of Use)


Links
System ID Private Priority Status Summary Last Updated
GNU Compiler Collection 48562 0 None None None 2019-02-06 11:03:03 UTC

Description Tilmann Scheller 2018-03-28 08:51:00 UTC
This is a result of triaging bug 1559315, it looks like GCC 8.0.1 is miscompiling some code in the LLVM MIPS backend.

Here is a minimized test case that reproduces with gcc-8.0.1-0.19.fc29 (I expect that it reproduces with gcc-8.0.1-0.20.fc29 as well, the original test case definitely does reproduce with 0.20):

$ cat min.sh
#!/bin/bash
set -xe
rm -f test testopt
g++ test.cpp -o testopt -O2
g++ test.cpp -o test -O0 -g
./testopt
./test
$ cat test.cpp
#include <initializer_list>
#include <iostream>

template <typename T> class ArrayRef {
public:
  using size_type = size_t;

private:
  /// The start of the array, in an external buffer.
  const T *Data = nullptr;

  /// The number of elements.
  size_type Length = 0;

public:
  /// Construct an ArrayRef from a std::initializer_list.
  /*implicit*/ ArrayRef(const std::initializer_list<T> &Vec)
      : Data(Vec.begin() == Vec.end() ? (T *)nullptr : Vec.begin()),
        Length(Vec.size()) {}

  const T &operator[](size_t Index) const { return Data[Index]; }
};

int main(int argc, char **argv) {
  const ArrayRef<int> Foo = {42};
  std::cout << "Foo " << Foo[0] << "\n";
  return 0;
}
[$ ./min.sh
+ rm -f test testopt
+ g++ test.cpp -o testopt -O2
+ g++ test.cpp -o test -O0 -g
+ ./testopt
Foo 0
+ ./test
Foo 42

As you can see it doesn't return the correct value for Foo[0] when compiling with -O2.

Comment 1 Florian Weimer 2018-03-28 09:17:50 UTC
Are you sure this is valid?  I'm not a C++ expert, but I think the lifetime extension for const references only applies to declarations, not arguments.  This means that the std::initializer_list object no longer exists when operator[] is invoked, and the Data member is a dangling pointer, resulting in undefined behavior.

Comment 2 Jakub Jelinek 2018-03-28 11:41:45 UTC
=================================================================
==2756==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffd8c1553c0 at pc 0x000000400e34 bp 0x7ffd8c155380 sp 0x7ffd8c155378
READ of size 4 at 0x7ffd8c1553c0 thread T0
    #0 0x400e33 in main /tmp/rh1561373.C:26
    #1 0x7fd3a7803009 in __libc_start_main (/lib64/libc.so.6+0x21009)
    #2 0x400b99 in _start (/tmp/rh1561373+0x400b99)

Address 0x7ffd8c1553c0 is located in stack of thread T0 at offset 32 in frame
    #0 0x400c55 in main /tmp/rh1561373.C:24

  This frame has 3 object(s):
    [32, 36) '<unknown>' <== Memory access at offset 32 is inside this variable
    [96, 112) 'Foo'
    [160, 176) '<unknown>'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope /tmp/rh1561373.C:26 in main
Shadow bytes around the buggy address:
  0x100031822a20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100031822a30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100031822a40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100031822a50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100031822a60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x100031822a70: 00 00 00 00 f1 f1 f1 f1[f8]f2 f2 f2 f2 f2 f2 f2
  0x100031822a80: 00 00 f2 f2 f2 f2 f2 f2 f8 f8 f2 f2 f3 f3 f3 f3
  0x100031822a90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100031822aa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100031822ab0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100031822ac0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==2756==ABORTING


says g++ -fsanitize=address -g on this.
The std::initializer_list object {42} ends lifetime at the end of
const ArrayRef<int> Foo = {42};
but the ctor stores a pointer into it and you then access it when it is already out of scope.


Note You need to log in before you can comment on or make changes to this bug.