Bug 1561373

Summary: GCC 8.0.1 miscompiles template using std::initializer_list
Product: [Fedora] Fedora Reporter: Tilmann Scheller <tschelle>
Component: gccAssignee: Jakub Jelinek <jakub>
Status: CLOSED NOTABUG QA Contact: Fedora Extras Quality Assurance <extras-qa>
Severity: medium Docs Contact:
Priority: unspecified    
Version: rawhideCC: aoliva, davejohansen, dmalcolm, fweimer, jakub, jwakely, law, mpolacek, msebor, nickc, tstellar
Target Milestone: ---   
Target Release: ---   
Hardware: x86_64   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: If docs needed, set a value
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2018-03-28 11:41:45 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Bug Depends On:    
Bug Blocks: 1559315    

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.