Jak dla mnie to co robisz to UB. bind
kopiuje swoje argumenty, więc a
w TestChangeCallback::Do
jest referencją do zmiennej, której czas życia jest równy z czasem życia m_Callback
. W momencie gdy przypisujesz nową wartość do m_Callback
, czas życia starej się kończy, a więc nie masz prawa już użyć a
. Założę się (a zaraz to sprawdzę), że jeśli w gcc/clangu przepuścisz to z ubsanem to dostaniesz informację o use-after-free. W tym momencie otrzymywane wyniki są efektem detali implementacji dla std::function
, gdzie MSVC korzysta z czegoś analogicznego do SSO (stąd adres a
się nie zmienia), a clang i gcc alokują na stosie.
[pts/0:krzaq@ArchVM:~/tmp]% g++ -fsanitize=address -g marek.cpp
[pts/0:krzaq@ArchVM:~/tmp]% ./a.out
Invoke start 1
Before: 1
=================================================================
==10150==ERROR: AddressSanitizer: heap-use-after-free on address 0x60300000eff0 at pc 0x000000401657 bp 0x7ffdc2815410 sp 0x7ffdc2815400
READ of size 4 at 0x60300000eff0 thread T0
#0 0x401656 in TestChangeCallback::Do(int const&) /home/krzaq/tmp/marek.cpp:15
#1 0x402e3a in void std::__invoke_impl<void, void (TestChangeCallback::* const&)(int const&), TestChangeCallback*&, int&>(std::__invoke_memfun_deref, void (TestChangeCallback::* const&)(int const&), TestChangeCallback*&, int&) /usr/include/c++/6.1.1/functional:235
#2 0x402cbc in std::result_of<void (TestChangeCallback::* const&(TestChangeCallback*&, int&))(int const&)>::type std::__invoke<void (TestChangeCallback::* const&)(int const&), TestChangeCallback*&, int&>(void (TestChangeCallback::* const&)(int const&), TestChangeCallback*&, int&) /usr/include/c++/6.1.1/functional:260
#3 0x402c50 in decltype (__invoke((*this)._M_pmf, (forward<TestChangeCallback*&>)({parm#1}), (forward<int&>)({parm#1}))) std::_Mem_fn_base<void (TestChangeCallback::*)(int const&), true>::operator()<TestChangeCallback*&, int&>(TestChangeCallback*&, int&) const /usr/include/c++/6.1.1/functional:613
#4 0x402ba4 in void std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)>::__call<void, , 0ul, 1ul>(std::tuple<>&&, std::_Index_tuple<0ul, 1ul>) /usr/include/c++/6.1.1/functional:943
#5 0x4028b3 in void std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)>::operator()<, void>() /usr/include/c++/6.1.1/functional:1002
#6 0x40211b in std::_Function_handler<void (), std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)> >::_M_invoke(std::_Any_data const&) /usr/include/c++/6.1.1/functional:1740
#7 0x401a53 in std::function<void ()>::operator()() const /usr/include/c++/6.1.1/functional:2136
#8 0x4016fe in TestChangeCallback::Invoke() /home/krzaq/tmp/marek.cpp:23
#9 0x401328 in main /home/krzaq/tmp/marek.cpp:35
#10 0x7f133fe43740 in __libc_start_main (/usr/lib/libc.so.6+0x20740)
#11 0x401168 in _start (/home/krzaq/tmp/a.out+0x401168)
0x60300000eff0 is located 16 bytes inside of 32-byte region [0x60300000efe0,0x60300000f000)
freed by thread T0 here:
#0 0x7f1340b2db30 in operator delete(void*, unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cc:108
#1 0x402aa9 in std::_Function_base::_Base_manager<std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)> >::_M_destroy(std::_Any_data&, std::integral_constant<bool, false>) /usr/include/c++/6.1.1/functional:1595
#2 0x402207 in std::_Function_base::_Base_manager<std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation) /usr/include/c++/6.1.1/functional:1619
#3 0x40151b in std::_Function_base::~_Function_base() /usr/include/c++/6.1.1/functional:1699
#4 0x401761 in std::function<void ()>::~function() /usr/include/c++/6.1.1/functional:1843
#5 0x4019d3 in std::enable_if<std::function<void ()>::_Callable<std::decay<std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)> >::type, std::result_of<std::decay<std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)> >::type ()>::type>::value, std::function<void ()>&>::type std::function<void ()>::operator=<std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)> >(std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)>&&) /usr/include/c++/6.1.1/functional:2001
#6 0x40161f in TestChangeCallback::Do(int const&) /home/krzaq/tmp/marek.cpp:14
#7 0x402e3a in void std::__invoke_impl<void, void (TestChangeCallback::* const&)(int const&), TestChangeCallback*&, int&>(std::__invoke_memfun_deref, void (TestChangeCallback::* const&)(int const&), TestChangeCallback*&, int&) /usr/include/c++/6.1.1/functional:235
#8 0x402cbc in std::result_of<void (TestChangeCallback::* const&(TestChangeCallback*&, int&))(int const&)>::type std::__invoke<void (TestChangeCallback::* const&)(int const&), TestChangeCallback*&, int&>(void (TestChangeCallback::* const&)(int const&), TestChangeCallback*&, int&) /usr/include/c++/6.1.1/functional:260
#9 0x402c50 in decltype (__invoke((*this)._M_pmf, (forward<TestChangeCallback*&>)({parm#1}), (forward<int&>)({parm#1}))) std::_Mem_fn_base<void (TestChangeCallback::*)(int const&), true>::operator()<TestChangeCallback*&, int&>(TestChangeCallback*&, int&) const /usr/include/c++/6.1.1/functional:613
#10 0x402ba4 in void std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)>::__call<void, , 0ul, 1ul>(std::tuple<>&&, std::_Index_tuple<0ul, 1ul>) /usr/include/c++/6.1.1/functional:943
#11 0x4028b3 in void std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)>::operator()<, void>() /usr/include/c++/6.1.1/functional:1002
#12 0x40211b in std::_Function_handler<void (), std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)> >::_M_invoke(std::_Any_data const&) /usr/include/c++/6.1.1/functional:1740
#13 0x401a53 in std::function<void ()>::operator()() const /usr/include/c++/6.1.1/functional:2136
#14 0x4016fe in TestChangeCallback::Invoke() /home/krzaq/tmp/marek.cpp:23
#15 0x401328 in main /home/krzaq/tmp/marek.cpp:35
#16 0x7f133fe43740 in __libc_start_main (/usr/lib/libc.so.6+0x20740)
previously allocated by thread T0 here:
#0 0x7f1340b2ce30 in operator new(unsigned long) /build/gcc/src/gcc/libsanitizer/asan/asan_new_delete.cc:60
#1 0x4027f7 in std::_Function_base::_Base_manager<std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)> >::_M_init_functor(std::_Any_data&, std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)>&&, std::integral_constant<bool, false>) /usr/include/c++/6.1.1/functional:1656
#2 0x4020f0 in std::_Function_base::_Base_manager<std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)> >::_M_init_functor(std::_Any_data&, std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)>&&) /usr/include/c++/6.1.1/functional:1627
#3 0x401fca in std::function<void ()>::function<std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)>, void, void>(std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)>) /usr/include/c++/6.1.1/functional:2123
#4 0x4019b4 in std::enable_if<std::function<void ()>::_Callable<std::decay<std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)> >::type, std::result_of<std::decay<std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)> >::type ()>::type>::value, std::function<void ()>&>::type std::function<void ()>::operator=<std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)> >(std::_Bind<std::_Mem_fn<void (TestChangeCallback::*)(int const&)> (TestChangeCallback*, int)>&&) /usr/include/c++/6.1.1/functional:2001
#5 0x40131c in main /home/krzaq/tmp/marek.cpp:34
#6 0x7f133fe43740 in __libc_start_main (/usr/lib/libc.so.6+0x20740)
SUMMARY: AddressSanitizer: heap-use-after-free /home/krzaq/tmp/marek.cpp:15 in TestChangeCallback::Do(int const&)
Shadow bytes around the buggy address:
0x0c067fff9da0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9db0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9dc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9dd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9de0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c067fff9df0: fa fa fa fa fa fa 00 00 00 00 fa fa fd fd[fd]fd
0x0c067fff9e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9e10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9e20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9e30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff9e40: 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
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
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
==10150==ABORTING
Jeśli main
zmienisz na:
int main() {
TestChangeCallback test;
int n = 1;
test.m_Callback = std::bind(&TestChangeCallback::Do, &test, ref(n));
test.Invoke();
cout << __FUNCTION__ << endl;
return 0;
}
To nie ma żadnych błędów i działa zgodnie z oczekiwaniami.