Failing to properly initialize or destroy a pthread
mutex can lead to undefined behavior.
Why is this an issue?
Mutexes are synchronization primitives that allow managing concurrency.
Their use requires following a well-defined life cycle:
- Mutexes need to be initialized (using
pthread_mutex_init
) before being used. Once it is initialized, a mutex is
in an unlocked state.
- Mutexes need to be destroyed (using
pthread_mutex_destroy
) to free the associated internal resources. Only
unlocked mutexes can be safely destroyed.
Before initialization and after destruction, a mutex is in an uninitialized state.
During a mutex' life cycle, the following patterns should be avoided as they result in undefined behavior:
- trying to initialize an already initialized mutex
- trying to destroy an initialized mutex that is in a locked state
- trying to destroy an uninitialized mutex
- trying to lock an uninitialized mutex
- trying to unlock an uninitialized mutex
In C++11 and higher, std::mutex
is less error-prone and is supported by more platforms.
In C++03, it is recommended to wrap mutex creation/destruction in an RAII class, as well as mutex lock/unlock. Those RAII classes will perform the
right operations, even in the presence of exceptions.
What is the potential impact?
Failing to properly initialize or destroy a POSIX Thread Mutex leads to undefined behavior.
For programs that exercise undefined behavior, the compiler is no longer bound by the language specification. The application may crash or, even
worse, the application may appear to execute correctly while losing data or producing incorrect results. In a multi-threaded context, additionally,
the application may experience spurious deadlocks or data races.
How to fix it
Ensure that all mutexes follow the well-defined life cycle.
If C++11 (or higher) is available, use std::mutex
and std::lock_guard
, instead, to avoid invalid usage patterns that lead
to undefined behavior. Depending on the scenario, std::atomic
may be used as an alternative if only small and primitive data shall be
modified in a concurrent context and in a synchronized manner.
Code examples
Noncompliant code example
#include <pthread.h>
pthread_mutex_t mtx;
void double_initialization(void)
{
pthread_mutex_init(&mtx, /*mutex attribute=*/NULL); // Initializes the mutex using an implementation-defined default attribute.
pthread_mutex_init(&mtx, /*mutex attribute=*/NULL); // Noncompliant: initializing an already initialized mutex
}
void destroy_locked(void)
{
pthread_mutex_init(&mtx, /*mutex attribute=*/NULL);
pthread_mutex_lock(&mtx);
pthread_mutex_destroy(&mtx); // Noncompliant: destroying a locked mutex
}
void double_destruction(void)
{
pthread_mutex_init(&mtx, /*mutex attribute=*/NULL);
pthread_mutex_destroy(&mtx);
pthread_mutex_destroy(&mtx); // Noncompliant: destroying an uninitialized mutex
}
void lock_destroyed(void)
{
pthread_mutex_init(&mtx, /*mutex attribute=*/NULL);
pthread_mutex_destroy(&mtx);
pthread_mutex_lock(&mtx); // Noncompliant: locking an uninitialized mutex
}
void unlock_destroyed(void)
{
pthread_mutex_init(&mtx, /*mutex attribute=*/NULL);
pthread_mutex_destroy(&mtx);
pthread_mutex_unlock(&mtx); // Noncompliant: unlocking an uninitialized mutex
}
Compliant solution
C solution:
#include <pthread.h>
pthread_mutex_t mtx;
void destroy_initialized(void)
{
pthread_mutex_init(&mtx, /*mutex attribute=*/NULL);
pthread_mutex_destroy(&mtx);
}
void use_and_destroy_initialized(void)
{
pthread_mutex_init(&mtx, /*mutex attribute=*/NULL);
pthread_mutex_lock(&mtx);
// Critical code that processes a shared resource (e.g. memory).
pthread_mutex_unlock(&mtx);
pthread_mutex_destroy(&mtx);
}
For static mutex variables:
#include <pthread.h>
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
void use_statically_initialized(void)
{
pthread_mutex_lock(&mtx); // Compliant: mutex is initialized statically
// Critical code that processes a shared resource (e.g. memory).
pthread_mutex_unlock(&mtx);
}
C++11 and higher solution:
#include <mutex>
#include <stdexcept>
std::mutex mtx;
void use_lock_guard(bool condition) {
std::lock_guard<std::mutex> lock(mtx);
// Critical code that processes a shared resource (e.g. memory).
if (condition) {
// Compliant: mutex will unlock automatically even in case of an exception
throw std::invalid_argument("Expected false");
}
// Compliant: mutex will unlock automatically at the end of the scope
}
C++03 solution:
#include <pthread.h>
pthread_mutex_t mtx;
class Mutex {
public:
Mutex(pthread_mutex_t* mtx) : pmtx(mtx) {
pthread_mutex_init(pmtx, /*mutex attribute=*/NULL);
}
~Mutex() {
pthread_mutex_destroy(pmtx);
}
pthread_mutex_t* pmtx;
private:
// Disallow copy operations to avoid double-free issues.
Mutex(Mutex const& other);
Mutex& operator=(Mutex const& other);
};
struct LockGuard {
LockGuard(Mutex &m) : mtx(m) {
pthread_mutex_lock(mtx.pmtx);
}
~LockGuard() {
pthread_mutex_unlock(mtx.pmtx);
}
Mutex &mtx;
};
void destroy_initialized()
{
Mutex m(&mtx);
// Compliant: mtx will be properly initialized and destroyed
}
void use_and_destroy_initialized()
{
Mutex m(&mtx);
{
LockGuard lock(m);
// Critical code that processes a shared resource (e.g. memory).
// Compliant: mutex will unlock automatically at the end of the scope
}
// Compliant: mtx will be destroyed properly
}
Resources
Documentation
Conference presentations
Related rules
- S5486 enforces the proper locking and unlocking of
pthread
mutexes.
- S5489 enforces unlocking multiple held
pthread
mutexes in reverse order.