signals icon indicating copy to clipboard operation
signals copied to clipboard

Severe potential bug

Open Philippe91 opened this issue 4 years ago • 6 comments

Run the following code: the assert will fail. This is simply because the lambda is not copied to 'sig', hence its data destructed before sig(10); is reached.

By using std::move(lambda), the problem is solved. But it's easy to forget this and have this bug during run time.

{
	fteng::signal<void(int)> sig;
	struct foo
	{
		~foo() { x = 999; }
		int x = 5;
	};
	int result = 0;
	{
		foo value;
		auto lambda = [&result, value](int count) { result = value.x + count; };
		sig.connect(lambda);
		//sig.connect(std::move(lambda));
	}

	sig(10);

	assert(result == 15);
}

Philippe91 avatar Nov 24 '20 15:11 Philippe91

Thank you for bringing up the topic, it is actually a more complex design problem than it seems. See an opposite bug, which was sorted out earlier: https://github.com/TheWisp/signals/issues/9 The danger of always copying a functor into the signal is not just about performance; It may subtly affect the correctness and confuse the user about the semantic of connecting-signal-to-functor. Generally, I believe managing the object's lifetime is easier to do than overriding the copying behavior. Also, it is widely accepted that mentioning an l-value means by reference (such as, if you have an int x;, an expression x or especially (x) would refer to the variable, rather than a temporary copy). On the other hand, an r-value returned from an expression or explicit moving is considered to be temporary and always either moved or copied into the destination, hence my earlier design.

I would like more feedback from you!

TheWisp avatar Jan 27 '21 12:01 TheWisp

I have added this compile-time assert to connect() to be on the safe side and keep best performances:

static_assert(! std::is_lvalue_reference<F>::value, "Use std::move() or construct{} the functor at call's location");

Philippe91 avatar Jan 27 '21 12:01 Philippe91

Sorry, I would have commented earlier, but I just noticed the activity here.

I would argue that silently storing references is usually nonintuitive behavior and that copying lvalues is more idiomatic (e.g., vector::push_back). Storing references invites dangling as in the motivating example, which looks like it results in a use after free.

If lvalues are copied, then given the example in #9, I think sig.connect(std::ref(s)) should yield the desired result without any need to modify the functor, and this makes it explicit that the user wants to store a reference (see https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper/operator()).

Furthermore, the existing behavior means that callables that are both movable and copyable can never be copied into connect and must move instead. So it seems to me that copying lvalues is strictly more flexible than storing references.

Just my two cents :)

mikezackles avatar Apr 22 '21 03:04 mikezackles

Isn't this fixed already? I've skimmed latest commits and it seems addressed there.

dumblob avatar May 31 '21 13:05 dumblob

I do not believe this has been fixed, but you can try my pull request above (#21). The last commit I see was a year ago -- where did you see it was fixed?

mikezackles avatar May 31 '21 14:05 mikezackles

My bad - I've read the commit comment incorrectly and now I've looked at the code and it seems unrelated.

dumblob avatar May 31 '21 18:05 dumblob