CertC++-CON56

Do not speculatively lock a non-recursive mutex that is already owned by the calling thread

Required inputs: IR

The C++ Standard Library supplies both recursive and non-recursive mutex classes used to protect critical sections. The recursive mutex classes ( std::recursive_mutex and  std::recursive_timed_mutex) differ from the non-recursive mutex classes ( std::mutexstd::timed_mutex, and  std::shared_timed_mutex) in that a recursive mutex may be locked recursively by the thread that currently owns the mutex. All mutex classes support the ability to speculatively lock the mutex through functions such as  try_lock()try_lock_for()try_lock_until()try_lock_shared_for(), and  try_lock_shared_until(). These speculative locking functions attempt to obtain ownership of the mutex for the calling thread, but will not block in the event the ownership cannot be obtained. Instead, they return a Boolean value specifying whether the ownership of the mutex was obtained or not.

The C++ Standard, [thread.mutex.requirements.mutex], paragraphs 14 and 15 [ ISO/IEC 14882-2014], state the following:

The expression m.try_lock() shall be well-formed and have the following semantics:
Requires: If m is of type std::mutex, std::timed_mutex, or std::shared_timed_mutex, the calling thread does not own the mutex.

Further, [thread.timedmutex.class], paragraph 3, in part, states the following:

The behavior of a program is undefined if:
- a thread that owns a timed_mutex object calls lock(), try_lock(), try_lock_for(), or try_lock_until() on that object

Finally, [thread.sharedtimedmutex.class], paragraph 3, in part, states the following:

The behavior of a program is undefined if:
- a thread attempts to recursively gain any ownership of a shared_timed_mutex.

Thus, attempting to speculatively lock a non-recursive mutex object that is already owned by the calling thread is undefined behavior. Do not call  try_lock()try_lock_for()try_lock_until()try_lock_shared_for(), or  try_lock_shared_until() on a non-recursive mutex object from a thread that already owns that mutex object.

Noncompliant Code Example

In this noncompliant code example, the mutex  m is locked by the thread's initial entry point and is speculatively locked in the  do_work() function from the same thread, resulting in undefined behavior because it is not a recursive mutex. With common implementations, this may result in deadlock.

#include <mutex>
#include <thread>

std::mutex m;

void do_thread_safe_work();

void do_work() {
  while (!m.try_lock()) {
    // The lock is not owned yet, do other work while waiting.
    do_thread_safe_work();
  }
  try {

    // The mutex is now locked; perform work on shared resources.
    // ...

  // Release the mutex.
  catch (...) {
    m.unlock();
    throw;
  }
  m.unlock();
}

void start_func() {
  std::lock_guard<std::mutex> lock(m);
  do_work();
}

int main() {
  std::thread t(start_func);

  do_work();

  t.join();
}
Compliant Solution

This compliant solution removes the lock from the thread's initial entry point, allowing the mutex to be speculatively locked, but not recursively.

#include <mutex>
#include <thread>

std::mutex m;

void do_thread_safe_work();

void do_work() {
  while (!m.try_lock()) {
    // The lock is not owned yet, do other work while waiting.
    do_thread_safe_work();
  }
  try {
    // The mutex is now locked; perform work on shared resources.
    // ...

  // Release the mutex.
  catch (...) {
    m.unlock();
    throw;
  }
  m.unlock();
}

void start_func() {
  do_work();
}

int main() {
  std::thread t(start_func);

  do_work();

  t.join();
}
Risk Assessment

Speculatively locking a non-recursive mutex in a recursive manner is undefined behavior that can lead to deadlock.

Rule Severity Likelihood Remediation Cost Priority Level
CON56-CPP Low Unlikely High P1 L3
Related Guidelines
MITRE CWE CWE-667, Improper Locking
Bibliography
[ ISO/IEC 14882-2014] Subclause 30.4.1, "Mutex Requirements"
Excerpt from SEI CERT C++ Coding Standard [https://cmu-sei.github.io/secure-coding-standards/sei-cert-cpp-coding-standard/rules/concurrency-con/con56-cpp], Copyright (C) 1995-2026 Carnegie Mellon University. See section 9.4. "3rd-Party Licenses" in the documentation for full details.

Possible Messages

Key

Text

Severity

Disabled

non_recursive_lock_add

Non-recursive lock is acquired while it is already locked.

None

False

Options

enter_critical_functions

enter_critical_functions

Type: set[bauhaus.analysis.config.QualifiedName]

Default: {'mtx_lock', 'std::_Mutex_base::lock', 'std::_Mutex_base::try_lock', 'std::lock_guard::lock_guard', 'std::mutex::lock', 'std::mutex::try_lock'}

Set of function names to enter a critical region.
 

enter_critical_macros

enter_critical_macros : set[bauhaus.analysis.config.MacroName] = set()

Set of macro names to enter a critical region (macros must expand to asm() statement).
 

exit_critical_functions

exit_critical_functions

Type: set[bauhaus.analysis.config.QualifiedName]

Default: {'mtx_unlock', 'std::_Mutex_base::unlock', 'std::lock_guard::~lock_guard', 'std::mutex::unlock'}

Set of function names to exit a critical region.
 

exit_critical_macros

exit_critical_macros : set[bauhaus.analysis.config.MacroName] = set()

Set of macro names to exit a critical region (macros must expand to asm() statement).
 

nested_critical_regions

nested_critical_regions : bool = True

If set to true, critical regions nest; if set to false, a single exit-critical-region terminates all open critical regions.