Open
Description
Hi!
I am trying to create a movable scoped lockable. It kind of works, but there are some cases that generate false positives. It seems that there are two interconnected problems:
- The lack of support for
scoped_lockable
members- This has a side effect with lambda captures which are lambda class members under the hood
- Some issue with temporaries, presumably around copy elision via temporary binding
- Might also affect lambda capture initialization
Here is a compiler explorer example with all of the observed issues: https://godbolt.org/z/vfcb78jqc
In case the above compiler explorer snippet is not available, please see the full source code under the cut:
#include <memory>
#include <iostream>
#include <cassert>
struct __attribute__((lockable)) Mutex {
void lock() __attribute__((exclusive_lock_function())) {
assert(!locked);
locked = true;
}
void unlock() __attribute__((unlock_function())) {
assert(locked);
locked = false;
}
bool locked = false;
};
struct __attribute__((scoped_lockable)) MutexLocker {
MutexLocker(Mutex* pmutex) __attribute__((exclusive_lock_function(*pmutex)))
: pmutex(pmutex)
{
assert(pmutex);
pmutex->lock();
std::cout << "constructor lock" << std::endl;
}
~MutexLocker() __attribute__((unlock_function(*pmutex))) {
if (pmutex) {
std::cout << "destructor unlock" << std::endl;
pmutex->unlock();
}
}
// we mark this as exclusive lock because
// although we're taking the mutex locked already
// the static analysis doesn't know that other.pmutex
// and this->pmutex are referencing the same object
// With the current accounting we should pretend that
// we took our new lock, and the other will unlock
// in the destructor - not in the runtime, but static analysis-wise.
MutexLocker(MutexLocker &&other) __attribute__((exclusive_lock_function(*pmutex))) {
pmutex = other.pmutex;
other.pmutex = nullptr;
std::cout << "constructor move" << std::endl;
}
MutexLocker(const MutexLocker& other) = delete;
MutexLocker& operator=(const MutexLocker& other) = delete;
MutexLocker& operator=(MutexLocker&& other) = delete;
Mutex *pmutex;
};
int main() {
Mutex m;
std::cout << "=== no warnings in most cases" << std::endl;
{
MutexLocker l(&m);
MutexLocker l2 = std::move(l);
auto val = [&]() {
auto l3 = std::move(l2);
std::cout << "in lambda 1" << std::endl;
return 10;
}();
}
std::cout << std::endl << "=== warnings 1.1: struct members" << std::endl;
{
MutexLocker l(&m);
struct Wrapper {
MutexLocker wrapped_locker;
};
auto wl = Wrapper { std::move(l) };
assert(m.locked);
}
std::cout << std::endl << "=== warnings 1.2: lambda capture initialization" << std::endl;
{
MutexLocker l(&m);
auto val = [l2 = MutexLocker(std::move(l))]() mutable {
auto l3 = std::move(l2);
std::cout << "in lambda 2" << std::endl;
return 20;
}();
assert(!m.locked);
}
std::cout << std::endl << "=== warnings 2: binding a temporary" << std::endl;
{
MutexLocker locker(&m);
auto && locker2 = [&]() -> MutexLocker {
return std::move(locker);
}();
assert(m.locked);
}
assert(!m.locked);
return 0;
}