Safe Recursion and Modification While Iterating
Based on your README, I think the code snippet below is meant to work, but I'm encountering some kind of stack corruption or memory invalidation or something when running (it's causing allocation of a really large value (probably from uninitialized memory). I'm seeing similar behaviour in my own program which I've tried to recreate in this minimal test case.
#include <iostream>
#include <signals/signals.hpp>
int main( int /*argc*/, char** /*argv*/ )
{
fteng::signal<void(void)> test;
test.connect([&test](){
std::cout << "A" << std::endl;
test.connect([&test](){std::cout << "B" << std::endl;});
test.connect([&test](){std::cout << "C" << std::endl;});
});
std::cout << "=============" << std::endl;
test();
std::cout << "=============" << std::endl;
test();
return 0;
}
Outputs:
=============
A
libc++abi: terminating with uncaught exception of type std::bad_alloc: std::bad_alloc
1 __pthread_kill (x86_64) /usr/lib/system/libsystem_kernel.dylib 0x7fff2049392e
2 pthread_kill (x86_64) /usr/lib/system/libsystem_pthread.dylib 0x7fff204c25bd
3 abort (x86_64) /usr/lib/system/libsystem_c.dylib 0x7fff20417411
4 abort_message (x86_64) /usr/lib/libc++abi.dylib 0x7fff20485ef2
5 demangling_terminate_handler() (x86_64) /usr/lib/libc++abi.dylib 0x7fff204775e5
6 _objc_terminate() (x86_64h) /usr/lib/libobjc.A.dylib 0x7fff20370595
7 std::__terminate(void ( *)()) (x86_64) /usr/lib/libc++abi.dylib 0x7fff20485307
8 __cxxabiv1::failed_throw(__cxxabiv1::__cxa_exception *) (x86_64) /usr/lib/libc++abi.dylib 0x7fff20487beb
9 __cxa_throw (x86_64) /usr/lib/libc++abi.dylib 0x7fff20487bb2
10 operator new(unsigned long) (x86_64) /usr/lib/libc++abi.dylib 0x7fff2048786f
11 std::__libcpp_allocate(unsigned long, unsigned long) new 253 0x1000096cd
12 std::allocator<fteng::details::sig_base::call>::allocate(unsigned long) memory 1664 0x100009615
13 std::allocator_traits<std::allocator<fteng::details::sig_base::call>>::allocate(std::allocator<fteng::details::sig_base::call>&, unsigned long) memory 1400 0x1000094ad
14 std::__split_buffer<fteng::details::sig_base::call, std::allocator<fteng::details::sig_base::call>&>::__split_buffer(unsigned long, unsigned long, std::allocator<fteng::details::sig_base::call>&) __split_buffer 318 0x100009402
15 std::__split_buffer<fteng::details::sig_base::call, std::allocator<fteng::details::sig_base::call>&>::__split_buffer(unsigned long, unsigned long, std::allocator<fteng::details::sig_base::call>&) __split_buffer 317 0x10000900d
16 void std::vector<fteng::details::sig_base::call>::__emplace_back_slow_path<>() vector 1668 0x100008d39
17 fteng::details::sig_base::call& std::vector<fteng::details::sig_base::call>::emplace_back<>() vector 1690 0x100008b2b
18 fteng::connection_raw fteng::signal<void ()>::connect<main::$_0::operator()() const::'lambda0'()>(main::$_0::operator()() const::'lambda0'()&&) const signals.hpp 307 0x100009d57
19 main::$_0::operator()() const main.cpp 11 0x100009c0f
20 fteng::connection_raw fteng::signal<void ()>::connect<main::$_0>(main::$_0&&) const::'lambda'(void *)::operator()(void *) const signals.hpp 308 0x100009b8c
21 fteng::connection_raw fteng::signal<void ()>::connect<main::$_0>(main::$_0&&) const::'lambda'(void *)::__invoke(void *) signals.hpp 308 0x100009b65
22 void fteng::signal<void ()>::operator()<>() const signals.hpp 229 0x100006c6a
23 main main.cpp 15 0x100006948
24 start (x86_64) /usr/lib/system/libdyld.dylib 0x7fff204ddf5d
Originally I thought there was some issue with the scope of the lambda related to #20 but I see the same on the PR that was raised against that issue (https://github.com/mikezackles/signals/tree/issue-20) or when using a simple functor:
void method_B() { std::cout << "B" << std::endl; }
void method_C() { std::cout << "C" << std::endl; }
int main( int /*argc*/, char** /*argv*/ )
{
fteng::signal<void(void)> test;
test.connect([&test](){
std::cout << "A" << std::endl;
test.connect(method_B);
test.connect(method_C);
});
std::cout << "=============" << std::endl;
test();
std::cout << "=============" << std::endl;
test();
return 0;
}
If I comment out the last connection (of the C printout) then I see what might be considered expected output:
=============
A
=============
A
B
Though depending on how the iteration is implemented I might have expected the first invocation to print A\nB and the second to print A\nB\nB (especially since the loop looks like for (size_t i = 0, n = calls.size(); i < n; ++i)
in operator()
.
I've not been able to figure out what is going wrong - I can't see any iterators getting invalidated or something with the recursive modification...
Any thoughts appreciated!