Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Severe potential bug #20

Open
Philippe91 opened this issue Nov 24, 2020 · 6 comments
Open

Severe potential bug #20

Philippe91 opened this issue Nov 24, 2020 · 6 comments

Comments

@Philippe91
Copy link

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);
}

mikezackles added a commit to mikezackles/signals that referenced this issue Jan 16, 2021
signal::connect has been reworked. It now does one of the following 3:
* Stores a function pointer if the callable can be converted to one
* Constructs the callable in the pointer's memory via perfect forwarding
* Constructs the callable on the heap via perfect forwarding

One relevant test in main.cpp has been updated, and a memory leak in another
test was fixed.

Fixes TheWisp#20
mikezackles added a commit to mikezackles/signals that referenced this issue Jan 16, 2021
signal::connect has been reworked. It now does one of the following 3:
* Stores a function pointer if the callable can be converted to one
* Constructs the callable in the pointer's memory via perfect forwarding
* Constructs the callable on the heap via perfect forwarding

One relevant test in main.cpp has been updated, and a memory leak in another
test was fixed.

Also marks conn_nontrivial destructor as an override

Fixes TheWisp#20
@TheWisp
Copy link
Owner

TheWisp commented Jan 27, 2021

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: #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!

@Philippe91
Copy link
Author

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::value, "Use std::move() or construct{} the functor at call's location");

@mikezackles
Copy link

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 :)

@dumblob
Copy link

dumblob commented May 31, 2021

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

@mikezackles
Copy link

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?

@dumblob
Copy link

dumblob commented May 31, 2021

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants