Vector Help

Reference Manual for PC-lint® Plus

11 Thread Analysis

11.1 Introduction

PC-lint Plus will detect functions used in multiple threads and track mutexes and their lock states when accessing static data from such functions to diagnose a variety of multi-threading issues including:

Additionally, PC-lint Plus can produce Thread Analysis reports that provide detailed information about functions, threads, mutexes, and/or variables involved in multi-threaded operation.

11.2 Library Support

PC-lint Plus contains built-in support for the thread initialization, mutex, and locker functions and classes provided by C11, C++11/14/17, the Qt framework, POSIX pthreads, and the critical section management functions of the Windows synchronization API. Support for other multi-threaded libraries can be configured by employing the Function Semantics feature using the thread semantics described in section Supporting Other Thread Libraries . The boost.lnt file (found in the lnt/ directory of the PC-lint Plus distribution) provides support for Boost’s boost::interprocess library.

11.3 Identifying Threads

Threads in PC-lint Plus are associated with functions, each thread having one or more thread root functions which are functions from which the thread is started. A thread consists of its root function(s) and all functions called by its root function(s) to any depth. By default, the main function is presumed to be a thread root, but the no_thread option can override that presumption.

While PC-lint Plus supports threads consisting of multiple root functions, in practice threads typically consist of a single root function. In such cases, the name of the thread is the same as the root function unless a different name is specified using the thread(thread-name) or thread_mono(thread-name) semantics. In the rest of this section, the term thread is used interchangeably to refer to both threads and thread root functions while thread root is used when it is desired to draw a distinction between the two. Messages that involve threads will reference both the thread name and the root function(s) involved where appropriate.

There are two basic ways of identifying threads: (1) specifying individual functions as thread roots using options and (2) automatically when a function is passed as an argument to a thread creation function.

11.3.1 Options to Identify Threads

The option:

    -sem( function-name, thread )

will identify function-name as a thread root. For example,

    -sem( input_reader, thread )

will identify input_reader as a thread root function. The name of the function may be fully qualified if a member of a class or namespace.

The option:

    -sem( function-name, thread( thread-name ) )

will identify function-name as a thread root of thread thread-name . Multiple functions may be specified as thread roots of a thread thereby forming a thread group. Within a thread group, the thread of execution can start in any of the thread root functions and when that function returns the thread of execution may be externally continued at the start of any of the thread root functions in the thread group. Thread groups provide support for Qt QThread slot functions.

By default, it is assumed that a thread can experience multiple concurrent instances. This will trigger the most diagnostics. Consider for example the following code:

//lint -sem( f, thread ) 
void f() { 
   static int n = 0; 
   n++; 
   /* ... */ 
}

This results in:

warning 457: variable 'n' is 'written' in function 'f()' of thread 'f' at line 4 of 'test.c' 
   which is unprotected with the 'read' in function 'f()' of thread 'f' at line 4 of 'test.c' 
warning 457: variable 'n' is 'written' in function 'f()' of thread 'f' at line 4 of 'test.c' 
   which is unprotected with the 'write' in function 'f()' of thread 'f' at line 4 of 'test.c'

See warning 457 .

In some cases, however, it is guaranteed that there will be only one instance of a particular thread. We refer to such threads as mono threads. The appropriate semantic to provide in such a case is thread_mono or thread_mono(thread-name). For example,

    -sem( function-name, thread_mono )

will identify function-name as a root function of a mono thread. If f had been so identified in the example above, the diagnostic would not have been issued.

The option:

    -sem( function-name, thread_mono( thread-name ) )

will identify function-name as a root function of a mono thread thread-name .

By default, main() would be regarded as a (mono) thread. Removing the thread property from main() is accomplished with the no_thread semantic:

    -sem( main, no_thread )

11.3.2 Automatic Identification of Threads

Using POSIX threads, the identity of a thread can be automatically determined because it is passed as the third argument to the pthread_create function. For example:

   ... 
   pthread_create( &input_thread, NULL, do_input, NULL ); 
   ...

will allow PC-lint Plus to deduce that do_input() is a separate thread.

It is assumed that this thread is not mono. If it is mono and your code depends on that fact, then you will have to identify the thread as thread_mono (see above).

Threads created by the Standard C function thrd_create, the Standard C++ std::thread class, and the Standard C++ std::async function are similarly automatically recognized. This same recognition of threads can be extended to other functions using the semantic

    thread_create(i)

which identifies the i th argument of a function as being a thread-endowing argument. Thus after:

    -sem( make_thread, thread_create(1) )

the call:

    make_thread( reader, ... );

will identify reader as a thread.

If PC-lint Plus cannot resolve the function argument of a thread creation function, such as when argument is an array element, thread semantics will not be automatically applied to the function. In this case, the thread semantic (see above) can be used to indicate that the function is a thread root.

11.4 Mutual Exclusion

To make static analysis meaningful, it is necessary to identify areas of the code in which only a single thread can operate. We will refer to these areas as thread protected regions. The use or modification of static variables by two different threads is not considered a violation of thread safety, provided such access lies within a thread protected region. A thread protected region is identified by the acquisition of a mutex to protect against inappropriate concurrent access of shared variables.

PC-lint Plus will automatically recognize calls to POSIX pthread functions pthread_mutex_lock, pthread_mutex_timedlock, pthread_mutex_trylock, pthread_rwlock_rdlock, pthread_rwlock_tryrdlock, pthread_rwlock_trywrlock, and pthread_rwlock_wrlock as locking the provided mutex object. Similar support is provided for the Standard C functions mtx_lock, mtx_timedlock, and mtx_trylock, the Standard C++ std::lock and std::try_lock function templates, the various locking member functions of the std::mutex, std::recursive_mutext, std::recursive_timed_mutex, std::shared_mutex, std::shared_timed_mutex, and std::timed_mutex classes, and the various locking member functions of the Qt mutex classes QMutex and QReadWriteLock. PC-lint Plus will of course recognize the corresponding mutex unlocking functions as well. Recognition of other mutex locking functions, such as those provided by third-party libraries, can be configured using function semantics (see Supporting Other Thread Libraries ).

In addition to identifying thread protected regions for the purpose of diagnosing unsafe accesses, PC-lint Plus will detect various mutex usage anomalies. For example:

#include <mutex> 
#include <thread> 
 
std::mutex m1, m2; 
int data = 0; 
 
void foo() { 
   m1.lock(); 
   ++data; 
   m2.unlock(); 
} 
 
void bar() { 
   std::thread(foo); 
}

will result in:

warning 455: mutex 'm2' unlocked without being locked 
   m2.unlock(); 
      ^ 
warning 454: mutex 'm1' locked without being unlocked 
} 
^ 
supplemental 891: locked here 
   m1.lock(); 
      ^

as the mutex being unlocked is not the same as the previously locked mutex. Mutex lock operations are expected to be paired with corresponding unlock operations in the same scope which is generally accepted as good programming practice. Warning 454 is issued when a mutex is not unlocked before the end of the scope in which it was locked. Warning 455 is issued when a mutex is unlocked without having been locked in the same, or enclosing, scope. Warning 456 is issued when two execution paths are combined that do not have the same lock state. For example:

//lint -sem(foo, thread) 
#include <mutex> 
 
std::mutex m1; 
bool g(); 
 
void foo() { 
   if ( g() ) { m1.lock(); } 
   /* ... */ 
   if ( g() ) { m1.unlock(); } 
}

will elicit:

warning 456: multiple 'if' execution paths are being combined with different lock states 
   if ( g() ) { m1.lock(); } 
   ^

(as well as message 455) while:

void foo() { 
   if ( g() ) { 
      m1.lock(); 
      /* ... */ 
      m1.unlock(); 
   } 
}

will not.

PC-lint Plus can also detect inconsistent mutex acquisition orders which can result in a deadlock. For example:

//lint -sem(foo, thread) 
//lint -sem(bar, thread) 
#include <mutex> 
 
std::mutex m1, m2; 
int data; 
 
void foo() { 
   std::lock_guard<std::mutex> lock1(m1); 
   std::lock_guard<std::mutex> lock2(m2); 
   ++data; 
} 
 
void bar() { 
   std::lock_guard<std::mutex> lock2(m2); 
   std::lock_guard<std::mutex> lock1(m1); 
   ++data; 
}

will be met with:

warning 2462: mutexes 'm1' and 'm2' have lock order mismatch in function 'bar(void)' of 
   thread 'bar' at line 16 of 'test.cpp' and function 'foo(void)' of thread 'foo' at 
   line 10 of 'test.cpp' 
supplemental 891: in function 'bar(void)' of thread 'bar' the lock order is: m2, m1 
supplemental 891: in function 'foo(void)' of thread 'foo' the lock order is: m1, m2

The inconsistencies will be reported even if they occur across modules.

11.4.1 Trylock-like Functions

Traditional mutex locking functions will block execution of the current thread until the requested mutex is available and only return when the mutex has been successfully acquired. Mutex locking functions that immediately return an error code instead of blocking when the mutex cannot be acquired are referred to as trylock-like functions. When calling a trylock-like function, it is necessary to examine the return value of the function in order to determine if locking was successful and proceed accordingly.

PC-lint Plus understands the semantics of such functions and the return values indicating successful mutex acquisition individually employed by trylock-like functions. Failing to properly utilize the result of a trylock-like function will be reported. For example:

//lint -sem(foo, thread) 
#include <pthread.h> 
 
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; 
extern int x; 
 
void foo() { 
   pthread_mutex_trylock(&m); 
   ++x; 
   pthread_mutex_unlock(&m); 
}

will be met with:

warning 2511: try_lock return value was discarded 
   pthread_mutex_trylock(&m); 
                       ^

along with message 457 to warn about the unsafe access of x when mutex m is not acquired. The pthread_mutex_trylock function returns 0 on success. In the below example, the x is mistakenly only updated when this function returns non-zero which will be diagnosed by PC-lint Plus:

void foo() { 
   if (pthread_mutex_trylock(&m)) { 
      ++x;                       // warning 457 - access of 'x' not protected 
      pthread_mutex_unlock(&m);      // warning 455 - mutex unlocked without lock 
   } 
}                                // warning 454 - mutex locked without unlock

The following corrected version will not elicit any such messages and also demonstrates that the result value of a trylock-like function may be stored in a variable before being tested.

//lint -sem(foo, thread) 
#include <pthread.h> 
 
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; 
extern int x; 
 
void foo() { 
   int result = pthread_mutex_trylock(&m); 
   if (result == 0) { 
      ++x; 
      pthread_mutex_unlock(&m); 
   } 
}

If result was modified before being tested, warning 2512 would be issued. Note that pthread_mutex_lock is also considered to be a trylock-like function as it returns a value indicating success or failure.

11.4.2 Locker Classes

In C++, a locker class can be used to manage the lock state of one or several mutexes. For example, std::lock_guard and std::scoped_lock acquire locks for the requested mutexes which are automatically unlocked by the class destructor when the locker goes out of scope and std::unique_lock provides a mechanism to manage operations on a set of mutexes.

PC-lint Plus understands the semantics of locker classes and will recognize the lock state changes of the mutexes managed by such locker classes. Additionally, incorrect use of locker classes will be diagnosed. For example:

1//lint -sem(foo, thread) 
2#include <mutex> 
3 
4std::mutex m; 
5 
6void foo() { 
7   m.lock(); 
8   /* ... */ 
9   std::lock_guard g{m}; 
10   /* ... */ 
11}

will elicit:

warning 2457: non-recursive mutex 'm' locked recursively in function 
   'foo(void)' of thread 'foo' at line 9 of 'test.cpp'

because std::lock_guard will attempt to lock m which is already locked. std::lock_guard can adopt a locked mutex by passing a type of std::adopt_lock_t in the constructor. PC-lint Plus will similarly warn when an attempt is made to adopt an unowned mutex:

#include <mutex> 
 
std::mutex m; 
 
void foo() { 
   std::lock_guard g{m, std::adopt_lock}; 
   /* ... */ 
}

will elicit:

warning 2494: mutex 'm' is not locked 
   std::lock_guard g{m, std::adopt_lock}; 
                  ^ 
supplemental 891: mutex 'm' ownership taken here 
   std::lock_guard g{m, std::adopt_lock}; 
                  ^

Message 2492 will warn when a lock operation is attempted on an already locked locker class. Message 2493 will diagnose attempted unlock operations on an unlocked locker class.

11.4.3 Shared Locks

Some types of mutexes support shared locks and exclusive locks, sometimes called read and write locks. Several threads may hold a shared lock on such a mutex but only one exclusive lock may be held at a time and no shared locks may be held while the mutex is exclusively locked. A shared lock creates a read-only thread protected region. PC-lint Plus will not complain about read accesses to the same static data by multiple threads if each accessing thread holds a shared lock of a common mutex for each access. A modification of the same static data that occurs outside an exclusive lock of the same mutex will still be diagnosed.

11.4.4 Internal Global Recursive Mutex

The thread_lock and thread_unlock semantics (see Supporting Other Thread Libraries ) refer to an internal global recursive mutex. This mutex is not user accessable and is named $Global_Mutex$ in thread analysis messages and reports.

A typical use for these semantics is to apply them to the processor interrupt enable/disable functions of an embedded RTOS.

11.5 Function Pointers

If a function has had its address taken, then we presume that we do not know the context of every call made to that function. In the worst case, it could be called by every thread. For that reason, we feel it is meritorious to report on every non-protected access to every static variable by such a function which is done via message 459 .

11.6 Thread Unfriendly Functions

Functions that are inappropriate with some aspects of multi-threaded programs form five categories of severity. This formulation was inspired by severity ratings of hurricanes and, like hurricanes, the higher the number the more severe the function.

11.6.1 Category 1 Functions

A function is considered thread unsafe if it cannot be used concurrently from two different threads without the protection of a mutex. Generally the function in question is an external function. The reason the function is unsafe is, presumably, that there are static data structures that are manipulated by the function but since the function’s source code is not available, it needs to be identified explicitly. However the function does not have to be external. A fully defined function can be indicated as being unsafe and all the cautionary warnings will still be issued.

Functions that are thread unsafe can be identified using the thread_unsafe semantic:

    -sem( function-name, thread_unsafe )

Consider the following example:

//lint -sem( f, thread_mono ) 
//lint -sem( g, thread_unsafe ) 
//lint -sem( h, thread_unsafe ) 
 
void g(); 
void h(); 
 
void f() { 
   g(); 
   h(); 
} 
int main() { 
   /* ... */ 
   g(); 
}

Here main() and f() are threads; functions g() and h() are both thread unsafe. None of the calls are protected. Warning 460 is issued when it is observed that an unprotected call is made to g() from both threads. Warning 460 is not issued for the function h() since it is called from only one thread (recall that, by default, main() is regarded as a mono thread).

However, info 847 will be issued alerting the user to the fact that thread unsafe functions, h() and g(), are invoked with unprotected calls. If h() is considered Category 3, the call to h() should be placed in a critical section. Otherwise the message can be inhibited for this function (or all functions if there are no Category 3 functions in the program).

If f() were a thread but not thread_mono so that multiple copies of the thread could exist, then warning 460 would be issued for function h().

If either call to g() were protected (but not both) warning 460 would still be issued for g(). This follows from the principle that no thread is permitted to have unrestricted access to any static data if access is made from two or more threads.

11.6.2 Category 2 Functions

An oft-occurring situation is when several external functions in a library access a common pool of data and where thread safety was not part of the library design. Such functions typically can be accessed by one thread even on an unprotected basis without harm. However if another thread accesses any of the functions in the group, then all calls to any member of the group from any thread must be made in a thread protected region. This precisely follows our definition of a Category 2 function.

Category 2 groups of functions are quite common and the usual technique is to designate one thread as the thread that will access the library. If any other thread calls upon the services of the library, such calls will be detected.

To successfully analyze calls to such libraries, the notion of a group of functions is required. The thread_unsafe semantic supports an optional group identification. For example, the option:

    -sem( x, thread_unsafe(xyz) )

specifies that x is a member of the xyz group where xyz is an arbitrary name used to designate the group. There are presumably other functions that will use the same group id.

Consider the following example:

//lint -sem( f, thread_mono ) 
//lint -sem( g, thread_mono ) 
//lint -sem( x, thread_unsafe(xyz) ) 
//lint -sem( y, thread_unsafe(xyz) ) 
//lint -sem( z, thread_unsafe(xyz) ) 
 
void x(); 
void y(); 
void z(); 
 
void f() { 
   x(); 
   y(); 
} 
 
void g() { 
   z(); 
}

Here, two threads that are presumed not to have multiple instances of themselves (thread_mono) invoke members of a single group (the xyz group). Warning 461 is issued for each pair of functions from the xyz group that are called from different threads. For example, a warning is issued for the call to function z() because while z() is called from a different thread than x() and y(), it is in the same group as x() and y() and none of the calls are made from a protected region. Informational messages (847 ) about the unprotected calls to thread unsafe functions will also be issued.

11.6.3 Category 3 Function

To detect abuses of Category 3 functions, info message 847 can be employed. Message 847 will be issued for calls to functions marked with the thread_unsafe or thread_unsafe(group) semantics when called by a thread, directly or indirectly, outside the protection of a mutex in a multi-threaded program. To cause message 847 to be issued only for specific category 3 functions, the message can be disabled using -e847 and then enabled for the desired functions using +esym. For example:

    -e847 +esym( 847, cat3func )

where cat3func is one of the category 3 functions you want to detect. Note that there is no other way to designate a function as Category 3.

11.6.4 Category 4 Function

A function may be identified as a Category 4 function by using the semantic thread_not( list ) or thread_only( list ) where in each case list is a comma separated list of thread names. If a thread appears in a thread_not list then that thread may not invoke the function. By contrast, if a thread does not appear in a thread_only list then that thread also may not invoke the function. This prohibition from use extends even to thread protected regions.

Consider the following example:

//lint -sem( f, thread ) 
//lint -sem( g, thread ) 
//lint -sem( a, thread_not(f) ) 
//lint -sem( b, thread_only( g, main ) ) 
#include <mutex> 
 
std::mutex m; 
 
void a(); 
void b(); 
 
void c() { 
   m.lock(); 
   b(); 
   m.unlock(); 
} 
 
void f() { 
   a(); 
   c(); 
}

Here f(), as a thread, invokes (directly or indirectly) a(), b() and c(). A message is issued for a() since the semantic thread_not(f) explicitly excludes thread f(). A message is also issued for b() since b() may be used only within g() or main().

If both the thread_not semantic and the thread_only semantic are applied to a function, the thread_not semantic will take precedence.

Note that the call to b() is made from a protected region. Unlike Categories 1 through 3, protecting the call does not eliminate the message.

11.6.5 Category 5 Function

Since a function in this category is not to be used at all you could employ the -deprecate option. For example, if function crash is not to be used at all, the option:

    -deprecate( function, crash, "MT illegal" )

would cause message 586 to be issued for each call to the crash function.

Alternatively, and perhaps preferably, the thread_not semantic may be used. Thus:

    -sem( crash, thread_not )

will have the effect of producing warning 462 when crash is called. This has the benefit of providing the name of the thread that is invoking crash.

11.7 Thread Local Storage

Thread Local Storage (TLS) is storage that is allocated separately for each thread initiated. References to TLS data do not need the protection of a mutex and will not be subject to the unsafe access analysis that other static variables are. PC-lint Plus recognizes several syntactic forms for declaring a variable as having thread local storage which are described below.

11.7.1 _Thread_local

C11 defines the keyword _Thread_local which can be used in C modules to declare a TLS variable, e.g.:

    _Thread_local int n;

identifies n as being thread local.

11.7.2 thread_local

C++11 defines the keyword thread_local which can be used in C++ modules to declare a TLS variable, e.g.:

    thread_local int n;

identifies n as being thread local.

11.7.3 __thread

The non-standard __thread keyword recognized by many compilers is supported by PC-lint Plus and can be used to define TLS variables in both C and C++ modules, .e.g:

    __thread int n;

If your compiler supports a different non-standard keyword to declare TLS variables, the option -rw_asgn can be used to cause PC-lint Plus to recognize the TLS-endowing property of the keyword. For example:

    -rw_asgn(__tls, __thread)

will assign the semantics of the __thread keyword to the __tls keyword which can then be used to declare thread local variables.

11.7.4 __declspec(thread)

Several popular compilers support the construct __declspec(thread) to designate a declaration as TLS. PC-lint Plus supports this behavior when the fms flag option is enabled. For example:

    __declspec(thread) int n;

will declare n to be thread local.

11.8 Limitations

11.9 Thread Analysis Reports

PC-lint Plus can generate several types of thread analysis reports which can provide additional insight about an analyzed program’s use of threads, functions, mutexes, and shared variables. These reports can be emitted in XML, JSON, CSV, or plain text formats. The types of reports available and the data they contain are:

Reports are requested using the -thread_report option which accepts three sub-options and a number of flags. The sub-options are:

The type and file sub-options are required. The format option defaults to text if not provided.

Flags are either report filters or fields. Filter flags affect which entries are included in the report. Field flags are used to override the default set of fields that will be present in the report. For example:

    -thread_report(type=functions, file=funcs.txt, function,variables)

will produce a function report containing the function and variables fields. Specifying a field that contains children (such as variables above) will automatically include the children as well.

Each type of report supports a unique set of filter flags that are described in the individual reports below.

11.9.1 Field Types and Representation

Each field in a report has a static value type which is one of Boolean, Numeric, String, Location, Array, Tristate, or an aggregate combination of one or more of these types. An Array is a list of zero or more items of a single type. A Location consists of a file name and a line number. The representation of values of each type is dependent on the output format selected. Boolean types are represented as true or false in JSON, 1 or 0 in XML and CSV, and yes or no in plain text format. Tristate values may be true, false, or unknown. Numeric values are integers represented using base-10 digits in all formats. Strings are surrounded by double quotes in JSON, XML, and CSV.

In JSON, a Location is represented as an object with a file and line keys. In XML, location is an element with line and file attributes. A Location in CSV is represented with the line number and file name in a single quoted field, separated by a comma. In a plain text report, a Location consists of a line number and a double-quoted file name separated by a space.

11.9.2 Thread Reports

A thread report includes information about the threads identified in the program, the root function(s) associated with each thread, and whether the thread is a mono thread. By default, all threads are included in the report. The error filter flag may be specified in the thread_report option to indicate that only threads referenced by messages 457 , 460 , 461 , 462 , 847 , 2457 , or 2462 should be included in the report. The available fields for the thread report are:




Field Type

Description




thread String

The name of the thread

mono Boolean

Indicates whether the thread has thread_mono semantics

in_error Boolean

Indicates whether the thread is referenced in messages 457 , 460 , 461 , 462 , 847 , 2457 , or 2462

roots Array

The thread root function(s)

root String

The name of the root function

definition Location

The definition location of the root function




11.9.3 Function Reports

A function report includes information about the functions that participate in thread analysis, i.e. thread root functions and the functions they call directly or indirectly. This information includes thread semantics (such as thread_protected or thread_unsafe), variables accessed by the function, the type and location of those accesses, and the locks held for each access, the functions called by each function, and the lock state and location of each call. The all filter flag may be used to request that all functions be included in the report, not just those related to thread operations.




Field Type

Description




function String

The function name and parameter list

definition Location

The definition location of the function.

protected Boolean

Indicates whether the thread has thread_protected semantics

thread_not Boolean

Indicates whether the thread has thread_not semantics

thread_unsafe Boolean

Indicates whether the thread has thread_unsafe semantics

addressed Boolean

Indicates whether the function’s address was taken

in_analysis Boolean

Whether the function is included in the thread analysis

variables Array

Array of variables directly accessed by the function. Each item in the array is an aggregate containing the following members:

name String

The name of the variable accessed by the function

accesses Array

Array of accesses of the variable. Each item in the array is an aggregate containing the following members:

type String

The type of access, one of read or write

locked String

A comma-separated list of mutexes locked by the function at the access point

access_locations Array

An array of locations where the variable is accessed with the associated access and mutexes held

calls Array

Array of functions called by function. Each item in the array is an aggregate containing the following members:

called String

The called function

states Array

Array of function call information. Each item in the array is an aggregate containing:

shared String

A comma-separated list of mutexes locked for reading in the order they were locked

exclusive String

A comma-separated list of mutexes locked for writing in the order they were locked

call_locations Array

An array of locations where the function was called with the associated lock states





By default, only the first location of each called function with a unique mutex lock ordering is reported. Enabling the faf flag option (+faf) will cause all function call locations to be included in the report. Similarly, only the first location of each variable access with a unique access type and set of owned mutexes is included in the report by default. If the fav flag option is enabled (+fav), all accesses to each variable will be reported. The faf and fav flag options may be enabled for specific source code regions (e.g. by surrounding a region with lint comments containing ++faf/++fav and –faf/–fav) to enable reporting of all calls and/or variable accesses within the corresponding regions.

11.9.4 Mutex Reports

A mutex report contains information about the mutexes used in the program, their type, and the locations of their initializations and references. The available filter flags for this report are: 2530, 2531, 2770, and 2771. When one or more of these flags are provided, the report is limited to those mutexes that were referenced by the corresponding messages of the same number.




Field Type

Description




name String

The name of the mutex

recursive Tristate

Whether the mutex was initialized as a recursive mutex

shared Tristate

Whether the mutex was initialized as a shared mutex

effective_recursive Boolean

Whether the mutex was deduced as being recursive from its use

effective_shared Boolean

Whether the mutex was deduced as being shared from its use

in_2530 Boolean

Whether the mutex is referenced in message 2530

in_2531 Boolean

Whether the mutex is referenced in message 2531

in_2770 Boolean

Whether the mutex is referenced in message 2770

in_2771 Boolean

Whether the mutex is referenced in message 2771

init_locations Array

An array of locations where the mutex was initialized

ref_locations Array

An array of locations where the mutex was directly referenced




11.9.5 Variable Reports

The variable report provides the set of variables collected during thread analysis. The all filter flag may be used to request all variables be included in the report, not just those related to thread operations.




Field Type

Description




variable String

The name of the variable

in_analysis Boolean

Whether the variable is included in the thread analysis

definition Location

The location of the variable’s definition




11.10 Message Summary

The below table summarizes the messages supporting the Thread Analysis feature. The phase indicates the thread analysis phase at which the message is issued (see "Thread Analysis Phases" below).




Message # Phase

Summary Description




454 module

mutex locked without being unlocked

455 module

unlock without being locked

456 module

multiple execution paths are being combined with different lock states

457 global

function of thread has access to variable which is unprotected with the access by function of thread

459 global

function whose address was taken has an unprotected access of variable

460 global

thread has unprotected call to thread unsafe function which is also called by thread

461 global

thread has unprotected call to function of group while thread calls function of the same group

462 global

thread calling function is inconsistent with the semantic

847 global

thread has unprotected call to thread unsafe function

2457 global

non-recursive mutex locked recursively in thread

2462 global

mutex lock order mismatch

2463 module

statement while locked

2467 module

multiple definitions of function

2468 module

semantic mismatch for function

2482 module

enumeration constant not defined

2483 module

invalid semantics for function

2484 module

invalid semantics for argument

2485 module

invalid semantics for locker class

2486 module

invalid semantics for function

2488 module

mutex passed more than once

2489 module

share lock/unlock an exclusively locked mutex/locker

2490 module

symbolic constant not defined

2492 module

locker already locked

2493 module

locker not locked

2494 module

locked mutex required

2495 module

mutex not locked/unlocked

2496 module

locker not locked/unlocked

2511 module

try_lock return value was discarded

2512 module

try_lock return value was modified

2513 module

try_lock return value was manipulated

2520 module

mutex_attr is not initialized

2521 module

mutex_attr already destroyed

2522 module

mutex_attr already initialized

2530 global

mutexes has inconsistent types

2531 global

mutex has usage incompatible with its type

2541 module

indeterminable ...

2752 module

incompatible semantics for locker class, corrections applied

2753 module

mutex may not be locked/unlocked

2770 global

type of mutex is incomplete but its usage requires specific type

2771 global

type of mutex is unknown

2920 module

unlocking order mismatch




11.11 Thread Analysis Phases

Thread-related analysis occurs in two distinct phases. The first phase occurs during module analysis where module-level threading issues that can be detected during this phase are reported. The second phase occurs after Global Wrap-up and uses the combined information collected during the individual module processing phases to model the inter-module relationships necessary to detect issues from a holistic perspective.

Messages 457 , 459 , 460 , 461 , 462 , 847 , 2457 , 2462 , 2530 , 2531 , 2770 , and 2771 are issued in the inter-module (second) phase, with the remaining thread analysis messages issued during module analysis. These inter-module messages do not contain semantic information (e.g. symbol or type) or location information and cannot be suppressed using most suppression options. All of the parameters employed by these messages are string parameters. The only suppression options that affect these messages are -e , +e , -egrep , +egrep , -estring , and +estring . Finally, for these suppressions to be honored they must be be active at the point these messages are issued. For example, to use -estring to suppress message 457, the option must appear outside of a module and still be in effect after all modules have been processed (the lifetime of options encountered within a module are limited to the module in which they appear).

11.11.1 Inhibition of Thread Analysis

Complete inhibition of thread analysis can be accomplished by turning off the ftc flag option with -ftc. While thread analysis is disabled, no thread-related messages will be reported and the inter-module thread analysis phase described above will not occur. There are several situations outside the use of -ftc in which the inter-module phase of thread analysis will not be performed. These situations are:

11.12 Supporting Other Thread Libraries

The builtin support that PC-lint Plus provides for the Qt, POSIX, and standard C and C++ thread libraries can be extended to other thread libraries by employing Function Semantics to designate functions that create and operate on threads, mutexes, and locker classes. PC-lint Plus provides a rich set of semantics for this purpose. The following sections detail these semantics.

Note that many of the semantics must be used in conjunction with other semantics. For example, the mutex_destroy semantic must be used with the mutex semantic to indicate which mutex argument is destroyed. When multiple semantics are used with the same function, all of these semantics should be included within the same -sem option, e.g.:

    -sem( foo, mutex_destroy, mutex(1) )

and not:

    -sem( foo, mutex_destroy )
    -sem( foo, mutex(1) )

PC-lint Plus performs semantic validation of thread semantics as each -sem option is processed and will produce an error if a -sem option does not include dependent semantics.

11.12.1 Thread Semantics

async

Indicates that the function is comparable to the standard C++ std::async function template.

no_thread

This semantic only has significance when applied to the main function where it indicates that it is not a thread root function. See Options to Identify Threads for additional information.

thread

Indicates that the function is a thread root. See Options to Identify Threads for additional information.

thread(thread_name)

Indicates that the function is a thread root of thread thread_name. This semantic takes precedence over the thread semantic. See Options to Identify Threads for additional information.

thread_args(n)

Indicates that the n th argument and all following arguments should be passed as arguments to the associated thread root function. Requires the thread_create(n) function semantic with a value for n less than the value of the n of the thread_args(n) argument semantic.

thread_atomic(n)

Indicates that the n th parameter is a pointer or reference to an object which will only be accessed atomically within the function. By default, PC-lint Plus assumes that a pointer or reference parameter to non-const will be used to access the target object for non-atomic writes and a pointer or reference to const will be used to access the target for non-atomic reads. If the function parameter is declared as pointer or reference to an atomic type, atomic access will be assumed. This semantic is useful for specifying that a parameter not declared as pointer or reference to atomic will only be accessed atomically.

This semantic may not be combined with any other mutex argument semantics or the mutex_validate function semantic. This semantic may not be combined with a mutex_remaining(n) or thread_args(n) argument semantic with a value of n that is less than the n used in this semantic.

thread_create(n)

Indicates that the n th argument of the function is the address of a thread root function.

thread_immune(n)

Indicates that the n th argument is thread immune and will not participate in thread analysis. This argument semantic prevents the ’read access’ or ’write access’ of the corresponding variable from being tracked and possibly being reported in thread analysis as being unprotected with the access within another thread.

thread_mono

Indicates that the function is a mono thread root. This semantic takes precedence over the thread semantic. See Options to Identify Threads for additional information.

thread_mono(thread_name)

Indicates that the function is a mono thread root of thread thread_name. This semantic takes precedence over the thread_mono semantic. See Options to Identify Threads for additional information.

thread_non_atomic(n)

Indicates that the n th parameter is a pointer or reference to an object that may be accessed non-atomically. By default, PC-lint Plus assumes that a pointer or reference parameter will be used to access the target object non-atomically but if the target is an atomic type, atomic access is assumed. This semantic will cause PC-lint Plus to assume non-atomic access to the target, even if the target is an atomic type. For example, the C++ std::atomic_init function template takes a pointer to an atomic type as the first argument but instantiations of this function may access the target non-atomically.

This semantic may not be combined with any other mutex argument semantics or the mutex_validate function semantic. This semantic may not be combined with a mutex_remaining(n) or thread_args(n) argument semantic with a value of n that is less than the n used in this semantic.

thread_not

Indicates that the function should not be called by any thread. See Category 4 Functions for additional information.

thread_not(thread_name, ...)

Indicates that the function should not be called by any of the given threads. See Category 4 Functions for additional information.

thread_only(thread_name, ...)

Indicates that the function should only be called by the given threads. See Category 4 Functions for additional information.

thread_protected

Indicates that the entire function is a thread-protected region as if it were protected by an invisible, function-specific, recursive mutex. Useful for specifying functions where the compiler provides guarantees against concurrent execution using extensions not recognized by PC-lint Plus.

thread_unsafe

Indicates that the function is not thread safe (category 1). See Category 1 Functions for additional information.

thread_unsafe(group_name)

Indicates that the function is not thread safe (category 2). Functions with the same group_name should never be called without protection by multiple threads. See Category 2 Functions for additional information.

11.12.2 Mutex Semantics

mutex(n)

Indicates that the n th argument is a pointer or reference to a mutex. This semantic is used with the mutex_destroy, mutex_initialize, mutex_lock, mutex_lock_shared, mutex_unlock, and mutex_unlock_shared semantics to specify the mutex argument being destroyed, initialized, locked, or unlocked. The mutex semantic may also be combined with at most one of mutex_attribute, mutex_is_recursive, or mutex_is_shared. A value of t may be used for n to specify the this object instead of an explicitly passed argument.

mutex_attribute(n)

When combined with the mutex(n) argument semantic, indicates that the n th argument is a plain (non-recursive and non-shared) mutex.

Otherwise, this semantic indicates that the n th argument is a pointer or reference to a mutex attribute structure (e.g. pthread_mutexattr_t or pthread_rwlockattr_t). This usage of the semantic is only valid if used with the mutex_attribute_destroy, mutex_attribute_initialize, or mutex_attribute_set mutex special semantics.

This argument semantic may combined with the mutex_is_recursive(n) and/or mutex_is_shared(n) argument semantics if the function has either the mutex_attribute_initialize or the mutex_attribute_set mutex semantics.

mutex_attribute_destroy

Indicates that the function destroys the mutex attribute object received as an argument. This semantic must be combined with a mutex_attribute(n) semantic where n is the attribute argument that will be destroyed by the function.

mutex_attribute_initialize

Specifies that the function initializes the mutex attribute object received as an argument. This semantic must be combined with a mutex_attribute(n) semantic where n is the attribute argument that will be initialized by the function. The mutex_is_recursive and/or mutex_is_shared argument semantics may be used to specify the default settings of the mutex attributes object.

mutex_attribute_set

Specifies that the function sets the attributes of a mutex attribute object. This semantic must be combined with a mutex_attribute(n) semantic where n is the attribute argument that will be set by the function.

mutex_destroy

Indicates that the function will destroy a mutex. This semantic must be combined with a mutex(n) semantic where n is the mutex argument to be destroyed.

mutex_ignore

Specifies that the function will not be walked during value tracking. Useful for functions which accept one or more mutex, mutex attribute, or mutex locker arguments but do not modify the state of those objects to prevent PC-lint Plus from making inferences about them while processing calls to the function.

mutex_initialize

Specifies that the function initializes a mutex argument. This semantic must be combined with a mutex(n) argument semantic which specifies the mutex argument initialized as well as at least one of mutex_is_recursive(n), mutex_is_shared(n), or mutex_attribute(n) to indicate that a mutex initialized with this function will be recursive, shared, or plain, respectively.

mutex_initialize_std_c

Specifies that the function initializes a mutex argument. This semantic must be combined with a mutex(n) argument semantic which specifies the mutex argument initialized as well as at least one of mutex_is_recursive(n), mutex_is_shared(n), or mutex_attribute(n) to indicate that a mutex initialized with this function will be recursive, shared, or plain, respectively.

The mutex_is_recursive(n) argument’s value will be ANDed with the value of the mtx_recursive enumeration constant to determine if the mutex is a recursive mutex. If the mtx_recursive enumeration constant has not been defined when a function with this special semantic is called, warning 2482 will be emitted.

mutex_is_recursive(n)

When combined with the mutex(n) argument semantic, indicates that the n th argument is a recursive mutex. When combined with the mutex_attribute(n) argument semantics (only if the function has either the mutex_attribute_initialize or mutex_attribute_set mutex semantics), indicates that the mutex attribute argument will specify a recursive mutex (see -mutex_attr for details of this behavior). Otherwise, indicates that the n th argument controls whether or not the mutex is recursive.

mutex_is_shared(n)

When combined with the mutex(n) argument semantic, indicates that the n th argument is a shared mutex. When combined with the mutex_locker argument semantic, indicates that the n th argument is a shared locker. When combined with the mutex_attribute(n) argument semantics (only if the function has either the mutex_attribute_initialize or mutex_attribute_set mutex semantics), indicates that the mutex attribute argument will specify a shared mutex (see -mutex_attr for details of this behavior). Otherwise, indicates that the n th argument controls whether or not the mutex is shared.

mutex_lock

The specified function exclusively locks a mutex. This semantic must be accompanied by one or more mutex(n) semantics or exactly one mutex_remaining(n) semantic which specify the argument(s) locked by the function. If the return type of the function is not void, the function must be specified with a try-lock return semantic other than try_lock_none.

mutex_lock_shared

The specified function locks a mutex in shared mode. This semantic must be accompanied by one or more mutex(n) semantics or exactly one mutex_remaining(n) semantic which specify the argument(s) locked by the function. If the return type of the function is not void, the function must be specified with a try-lock return semantic other than try_lock_none.

mutex_must_be_locked(n)

This semantic must be used with mutex_validate. Indicates that the mutex object received as argument n must be locked. Supports functions that expect to receive a locked mutex argument such as std::condition_variable::wait.

mutex_must_be_unlocked(n)

This semantic must be used with mutex_validate. Indicates that the mutex object received as argument n must be unlocked. Supports functions that expect to receive an unlocked mutex argument.

mutex_remaining(n)

Like the mutex semantic, indicates that the n th argument is a pointer or reference to a mutex but also that all following arguments are pointers or references to mutexes as well. The mutex arguments are also checked for being null. n is not allowed to be t. This semantic may be used with any combination of mutex_tag_adopt, mutex_tag_defer, and mutex_tag_try_to_lock to indicate that the last argument may be corresponding locker tag type.

mutex_tag_adopt(n)

When used with the mutex_remaining argument semantic, indicates that the last argument may be a std::adopt_lock_t tag type. Otherwise, indicates that the n th argument may be a std::adopt_lock_t tag type. n may not be t. This semantic may also be combined with mutex_tag_defer and/or mutex_tag_try_to_lock. Alternate tag types may be specified with the -locker_tag option.

mutex_tag_defer(n)

When used with the mutex_remaining argument semantic, indicates that the last argument may be a std::defer_lock_t tag type. Otherwise, indicates that the n th argument may be a std::defer_lock_t tag type. n may not be t. This semantic may also be combined with mutex_tag_adopt and/or mutex_tag_try_to_lock. Alternate tag types may be specified with the -locker_tag option.

mutex_tag_try_to_lock(n)

When used with the mutex_remaining argument semantic, indicates that the last argument may be a std::try_to_lock_t tag type. Otherwise, indicates that the n th argument may be a std::try_to_lock_t tag type. n may not be t. This semantic may also be combined with mutex_tag_adopt and/or mutex_tag_defer. Alternate tag types may be specified with the -locker_tag option.

mutex_unlock

The function unlocks a mutex. This semantic must be accompanied by exactly one mutex(n) semantic specifying the mutex argument which will be unlocked.

mutex_unlock_shared

The function unlocks a share-locked mutex. This semantic must be accompanied by exactly one mutex(n) semantic specifying the mutex argument which will be unlocked.

mutex_validate

This semantic must be used with one or more of mutex, mutex_attribute, or mutex_locker to specify the arguments that should be validated. Implies the semantics of mutex_ignore and requests validation of the specified mutex(es), attribute(s), and/or locker objects during calls to the target function. The mutex_must_be_locked or mutex_must_be_unlocked argument semantics may be combined with mutex_validate to ensure that the provided mutex or mutex locker argument is locked or unlocked when the function is called (violations will be reported with messages 2753 , 2495 , and 2496 ).

thread_lock

The specified function exclusively locks the internal global recursive mutex.

thread_unlock

The specified function unlocks the internal global recursive mutex.

11.12.3 Locker Semantics

A locker class is a class which manages the lock states of one or more mutex objects. Examples of locker classes in the standard C++ library include std::lock_guard and std::scoped_lock. The operations performed by such classes can be configured using the semantics described in this section.

Locker classes whose member functions are targeted by the semantics described in this section must confirm to the following requirements:

locker_assign

This semantic must be used in conjunction with two mutex_locker argument semantics. The semantic specifies that mutexes owned by the second mutex_locker argument are moved to the first mutex_locker argument. Any mutexes previously owned by the first mutex_locker argument are released. Supports move assignment operations for classes like std::unique_lock.

locker_create

Indicates the specified function creates a locker object. This semantic implicitly sets mutex_locker(t). The mutex_is_shared(t) argument semantic can be used to indicate the locker object supports shared mutexes. This semantic must be accompanied by one or more mutex(n) semantics or exactly one mutex_remaining(n) semantic. This semantic may be used with any combination of mutex_tag_adopt, mutex_tag_defer, and mutex_tag_try_to_lock to indicate that the last argument of the locker creation function may be a corresponding locker tag type.

locker_destroy

Indicates that the function destroys a locker object. This semantic implicitly sets the mutex_locker(t) semantic.

locker_fetch

This semantic must be used in conjunction with exactly one mutex_locker argument semantic. The function returns the mutex tracked by the locker object. Supports mutex-fetching functions such as std::unique_lock::mutex.

locker_lock

The specified locker object argument will exclusively lock its mutex(es). This semantic must be combined with exactly one mutex_locker(n) semantic which specifies the argument corresponding to the locker object on which the lock operation will be performed. If the return type of the function is not void, the function must be specified with a try-lock return semantic other than try_lock_none.

locker_lock_shared

The specified locker object argument will share-lock its mutex(es). This semantic must be combined with exactly one mutex_locker(n) semantic which specifies the argument corresponding to the locker object on which the lock operation will be performed. If the return type of the function is not void, the function must be specified with a try-lock return semantic other than try_lock_none.

locker_move

This semantic must be used in conjunction with two mutex_locker argument semantics. The specified function creates a new locker object and the mutexes owned by the second mutex_locker argument are moved to the first mutex_locker argument. Supports move construction for classes like std::unique_lock.

locker_owns

This semantic must be used in conjunction with exactly one mutex_locker argument semantic. The function returns true if the locker argument specified by the mutex_locker semantic owns a locked mutex and returns false otherwise. This semantic implicitly sets the try_lock_true semantic for the function.

locker_release

This semantic must be used in conjunction with exactly one mutex_locker argument semantic. The function releases and returns the mutex owned by the locker argument specified by mutex_locker without unlocking it. Supports mutex-releasing functions such as std::unique_lock::release.

locker_swap

This semantic must be used in conjunction with two mutex_locker argument semantics. The function swaps the mutex(es) and ownership status of the two locker arguments. Supports swap semantics for functions like std::unique_lock::swap.

locker_unlock

The mutex(es) owned by the specified locker argument are unlocked. This semantic must be combined with a single mutex_locker(n) semantic which specifies the locker argument operated on.

mutex_locker(n)

This semantic is used to indicate that the specified argument is a pointer or reference to a mutex locker object. mutex_locker is used in conjunction with locker_assign, locker_fetch, locker_lock, locker_lock_shared, locker_move, locker_owns, locker_release, locker_swap, and locker_unlock to indicate the locker object(s) involved in the corresponding operation. A value of t may be used for n to indicate that the mutex locker object acted on is the this object (a value of t is required when used with locker_create and locker_destroy).

11.12.4 Trylock Semantics

A trylock function is a mutex locking function that may fail and whose success is communicated through its return value. The return values indicating success or failure may be specified for individual trylock functions using the semantics described in this section. Functions that are the target of a mutex_lock, mutex_lock_shared, locker_lock, or locker_lock_shared semantic that do not specify a return value of void must be specified with one of the below trylock semantics other than try_lock_none.

try_lock_false

Indicates that the associated function returns false on success.

try_lock_neg_1

Indicates that the associated function returns -1 (negative one) on success.

try_lock_none

Indicates that the associated function does not return anything indicating success. This is the default if no other try lock return semantic is provided.

try_lock_one

Indicates that the associated function returns 1 (one) on success.

try_lock_std_c

Indicates that the associated function returns the value of the thrd_success enumeration constant on success. A warning message is emitted if the definition of the thrd_success enumeration constant has not been seen prior to the call of a function with this semantic.

try_lock_true

Indicates that the associated function returns true on success.

try_lock_zero

Indicates that the associated function returns 0 (zero) on success.