TartanLlama / function_ref

A lightweight, non-owning reference to a callable.
Creative Commons Zero v1.0 Universal
169 stars 23 forks source link

Member function: Passing object by value cause dangling #12

Open LesleyLai opened 5 years ago

LesleyLai commented 5 years ago
struct S {
  int foo() const { return 3; }
};

int main() {
    tl::function_ref<int(S)> f = &S::foo;
    f(S{}); // crash with GCC 9.1
}
rollbear commented 5 years ago

I took the liberty of analyzing this, and if you compile with -fsanitize=address, you get to see the problem also on clang++.

What happens is that in the pointer to member function constructor, the parameter f is a temporary, and obj_ will point to that temporary. The temporary is destroyed once the function_ref<> object has been constructed. When the call is made, obj_ refers to the corpse. Changing the program to avoid the temporary works:

int main() {
    auto mf = &S::foo;
    tl::function_ref<int(S)> f = mf;
    f({S});
}

I don't know how to fix it, unfortunately.

Demo: https://gcc.godbolt.org/z/XfYrtO

nitronoid commented 5 years ago

This is the same undefined behaviour you would get when storing a temporary lambda and invoking the ref.

tl::function_ref<int(S)> f = [](){};
f();

Of course, the below snippet works and is why a function_ref is constructable from an rvalue in the first place.

// Any rvalue passed to this function will live as long as the function scope
int proxy(tl::function_ref<int(S)> f)
{
  return f(S{});
}

int main()
{
  return proxy(&S::foo);
}

See the "Possible Issues" section: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0792r4.html

A more subtle example, and the reason why you may have tried this:

int foo()
{
  return 3;
}

int main()
{
  // Works fine
  {
      tl::function_ref<int()> f = foo;
      f();
  }
  // Segfault, taking the address produces a temporary
  {
      tl::function_ref<int()> f = &foo;
      f();
  }
}

All compiled with GCC 9.1 at O3