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:
unsafe access of static data across multiple threads without the protection of a common mutex.
calls to thread-unsafe functions.
inconsistent mutex acquisition order which can lead to deadlocks.
improper use of trylock functions.
use of improperly initialized mutexes.
inappropriate mutex management such as locking a mutex without unlocking it, locking an already locked non-recursive mutex, attempting to unlock a mutex not held by the curent thread, or attempting to acquire a shared lock on a mutex in which the current thread already holds an exclusive lock.
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.
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.
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.
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 )
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.
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.
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.
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.
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.
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.
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 .
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.
Category 1: A function is considered category 1 in a multi-threaded (MT) program if, when called from more than one thread, each call needs to be made from a protected region.
Category 2: A function is considered category 2 if it belongs to a group of functions such that whenever two members of this group are called from two different threads, all calls to this group need to be made from a protected region.
Category 3: A function is considered category 3 if every call to such a function in an MT program needs to be made from a protected region.
Category 4: A function is considered category 4 if it may only be called from a prescribed subset of the threads.
Category 5: A function is considered category 5 if it may not be called at all.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Mutex lock and unlock functions receiving a mutex pointer or reference for which PC-lint Plus cannot deduce the underlying mutex will not be recognized, which may result in false positive messages. This can occur when locking a mutex from an array of mutex objects (PC-lint Plus does not currently track array elements) or if a function locks a mutex that is passed to it as an argument. Message 2541 (indeterminable mutex) will be issued in such cases.
Mutex lock and unlock operations are expected to be properly paired within the same scope, violations of this expectation are reported by messages 454 , 455 , and 456 .
Thread operations (such as join) that affect the lifetime of a thread are not currently recognized by PC-lint Plus which may result in safe accesses that occur before or after the lifetime of a thread being incorrectly reported as unsafe.
An access of an array element or structure member is considered an access of the entire array/structure. If e.g. two threads access different members of the same structure or class outside the protection of a common mutex, this will be reported as an unsafe access.
Variables passed by pointer/reference as an argument in a function call will be assumed to be accessed at the point of the call.
Modifications made to static data through indirect accesses which cannot be resolved by PC-lint Plus will not be reported as unsafe when occurring outside the protection of a mutex.
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:
Thread report - the threads identified during thread analysis and their root functions.
Function report - functions called by threads, the shared variables they access and the type of access (i.e. read/write), the mutexes held at each access, and calls made by the function (directly or indirectly).
Mutex report - mutexes used, their type (i.e. shared/recursive), and the locations of their use.
Variable report - shared variables accessed by threads.
Reports are requested using the -thread_report option which accepts three sub-options and a number of flags. The sub-options are:
type=report-type where report-type is one of threads, functions, mutexes, or variables.
file=output-file where output-file is the name of the file to write the report to. If the specified file already exists, it is overwritten.
format=output-format where output-format is one of csv, json, text, or xml.
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.
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.
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 |
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.
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 |
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 |
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 |
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).
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:
When Global Wrapup is suppressed with -unit_check or -u options. Inter-module thread analysis occurs after Global Wrapup and will not be performed if Global Wrapup does not occur.
When no threads or mutexes are detected during module analysis. The checks performed by thread analysis in the inter-module phase are not relevant for programs that do not utilize threads or mutexes.
After the attempted issuance of messages 2467 or 2468 . If PC-lint Plus attempts to issue one of these messages but the message is suppressed, inter-module thread analysis will still be disabled. This condition can be overridden by setting the ftc flag option to 2 or greater.
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.
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.
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.
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:
At least one non-move, non-copy constructor must be endowed with the locker_create semantic.
A non-deleted destructor must be provided.
No copy constructors or copy assignment operators may be defined.
The use of the locker_move, locker_assign, and locker_release semantics may only be used if the locker class defines a non-deleted default constructor.
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).
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.