Given the following code, where x
is a dangling const reference
to a vanished object, and is therefore undefined behavior.
auto get_vec() { return std::vector<int>{1,2,3,4,5}; }
const auto& x = get_vec().back();
It seems like neither GCC 7.3, Clang 6.0 and MSVC is able to emit a warning, even with all warnings enabled.
Does anyone know if it is any way to emit a warning in these cases?
Is there any difference between const auto&
and auto&&
in these cases?
Note, if back()
would return by value, it wouldn't be undefined behavior as the lifetime temporary object x is extended to function scoop.
Long story: I have a code base where const auto&
is used as the default way of initializing variables, and for some odd reason these cases executes correctly using MSVC, but when compiled with Clang for android, every occurance results in a wrongly assigned value. For now the solution seems to investigate every const auto&
in the whole code base.
Also, in many cases the const auto&
refers to a heavy object returned by reference so simply removing the &
is not a solution.
One more thing, I'm responsible for the miss use of const auto&
:)
Only thing I can come up with right now is to use CLANG with -fsanitize=address. But of course this will only help at runtime, but then you get something nice like this:
==102554==ERROR: AddressSanitizer: heap-use-after-free on address 0x603000000020 at pc 0x00000050db71 bp 0x7ffdd3a5b770 sp 0x7ffdd3a5b768
READ of size 4 at 0x603000000020 thread T0
#0 0x50db70 in main (/home/user/testDang+0x50db70)
#1 0x1470fb404889 in __libc_start_main (/lib64/libc.so.6+0x20889)
#2 0x41a019 in _start (/home/user/testDang+0x41a019)
0x603000000020 is located 16 bytes inside of 20-byte region [0x603000000010,0x603000000024)
freed by thread T0 here:
#0 0x50a290 in operator delete(void*) (/home/user/testDang+0x50a290)
#1 0x50eccf in __gnu_cxx::new_allocator<int>::deallocate(int*, unsigned long) (/home/user/testDang+0x50eccf)
#2 0x50ec9f in std::allocator_traits<std::allocator<int> >::deallocate(std::allocator<int>&, int*, unsigned long) (/home/user/testDang+0x50ec9f)
#3 0x50ec2a in std::_Vector_base<int, std::allocator<int> >::_M_deallocate(int*, unsigned long) (/home/user/testDang+0x50ec2a)
#4 0x50e577 in std::_Vector_base<int, std::allocator<int> >::~_Vector_base() (/home/user/testDang+0x50e577)
#5 0x50e210 in std::vector<int, std::allocator<int> >::~vector() (/home/user/testDang+0x50e210)
#6 0x50db16 in main (/home/user/testDang+0x50db16)
#7 0x1470fb404889 in __libc_start_main (/lib64/libc.so.6+0x20889)
previously allocated by thread T0 here:
#0 0x509590 in operator new(unsigned long) (/home/user/testDang+0x509590)
#1 0x50e9ab in __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (/home/user/testDang+0x50e9ab)
#2 0x50e94b in std::allocator_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) (/home/user/testDang+0x50e94b)
#3 0x50e872 in std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (/home/user/testDang+0x50e872)
#4 0x50e2ff in void std::vector<int, std::allocator<int> >::_M_range_initialize<int const*>(int const*, int const*, std::forward_iterator_tag) (/home/user/testDang+0x50e2ff)
#5 0x50deb7 in std::vector<int, std::allocator<int> >::vector(std::initializer_list<int>, std::allocator<int> const&) (/home/user/testDang+0x50deb7)
#6 0x50dafb in main (/home/user/testDang+0x50dafb)
#7 0x1470fb404889 in __libc_start_main (/lib64/libc.so.6+0x20889)
SUMMARY: AddressSanitizer: heap-use-after-free (/home/user/testDang+0x50db70) in main
Shadow bytes around the buggy address:
0x0c067fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c067fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c067fff8000: fa fa fd fd[fd]fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
Maybe you have automated unit tests you can easily run as "sanizizer" builds.
Almost certainly there is no way of warning on this. The compiler has no idea whether the referenced object returned by back()
will outlive the line or not, and if it does, there's no problem (though I'd be hard pressed to think of a realistic situation where a non-static member function called on a temporary object returns a reference to an object which outlives the temporary object).
It sounds like whoever wrote that code read about the most important const, and took away entirely the wrong lesson from it.
I have a code base where const auto& is used as the default way of initializing variables
Ouch. :(
for some odd reason these cases executes correctly using MSVC, but when compiled with Clang for android, every occurance results in a wrongly assigned value
UB is UB innit.
For now the solution seems to investigate every const auto& in the whole code base
Yes.
Just as you cannot tell at a glance whether a particular case is "safe"/correct, the compiler cannot tell simply from a function signature.
If it always had access to the full definition of every function, it would be able to warn you in some cases (and analysis tools like -fsanitize=address
will do their best with this), but there is no general-case solution for the compiler to detect dangling references at runtime.
Also congratulations on the payrise you can receive now that the guilty employees (the author and the reviewer) have been fired, right? :)
Obviously, for the above example, one would write something like:
std::vector<int> xx{1,2,3,4,5};
const auto& x = xx.back();
It does not make much sense to create a whole vector to keep only its last element. And if you have an expression like the above one and want to use a single expression, then you should almost never uses auto &
to start with.
It the object is large, then you should either use move semantic or reference counting. So maybe you would have a function like GetLastValue
that would returns by value a copy of the last vector value and then move that into the target destination.
You really need to understand what you are doing. Otherwise, you should use a language like C# where you need less knowledge about the internal working of the compiler or the exact language specifications.
As a general rule, I would say that you should not use auto &
unless you are sure that you want a reference to the returned item. The most common case when I would use auto &
or const auto &
would be for a range based loop. For example, with the above vector named xx
, I would generally write:
for (auto & item : xx) …
except if I know that it returns trivial types.
来源:https://stackoverflow.com/questions/49396276/is-there-any-c-compiler-which-can-issue-a-warning-for-a-dangling-reference