Vector Help

Reference Manual for PC-lint® Plus

17 Other Features

17.1 Format Checking

The printf-like and scanf-like functions are fertile ground for programming errors as they are not type safe due to their variadic nature, and incorrect use often involves undefined behavior that is not diagnosed by compilers. PC-lint Plus performs comprehensive analysis of the use of these functions diagnosing format incompatibilities, inconsistent and redundant specifier combinations, missing and unused arguments, mis-use of positional specifiers, use of non-standard conversion specifiers, unbounded conversions, and other anomalies.

There are several categories of checking performed and over two dozen messages dedicated to analysis of format string functions.

17.1.1 Dangerous Use

The messages in this section focus on particularly egregious errors that always have the potential to result in undefined behavior. A relatively common error is to provide fewer data arguments than required by the format string. This often happens for particularly large format strings or when the format string is changed. Another possibility is a missing comma between string literal arguments such as in:

printf("\%10s \%s", "Name" "Value");  

which will be diagnosed with:

warning 558: too few data arguments for format string (1 missing) 
   printf("%10s %s", "Name" "Value"); 
              ~^

showing the location of the first conversion specifier without a value and the total number of missing data arguments. Such a call results in undefined behavior as printf processes data on the stack looking for the next argument.

The scanf-like functions have additional potential concerns. Using the %s or %[ conversion specifier without a maximum field width will result in undefined behavior if the stored string exceeds the provided buffer, e.g.:

char buf[10]; 
scanf("%s", buf);

which will result in:

warning 498: unbounded scanf conversion specifier 's' may result in buffer overflow 
   scanf("%s", buf); 
         ~^

This message will not be issued if the non-standard ’m’ prefix is used (e.g. %ms), which specifies that scanf should dynamically allocate a buffer large enough to hold the result.

A missing closing bracket for the %[ conversion specifier is yet another instance of undefined behavior and is diagnosed with message 2406 . For example: 

char buf[100]; 
scanf("%99[^]", buf);

Here the programmer intended to store a series of consecutive ^ characters into buf but a special exception for the scanf function causes the closing bracket to be considered part of the pattern in this case, not the closing bracket to the %[ conversion specifier. PC-lint Plus will issue:

warning 2406: no closing ']' for '%[' in scanf format string 
   scanf("%99[^]", buf); 
           ~~^

Another source of undefined behavior stemming from scanf is when a field width of zero is specified:

scanf("\%0s", buffer);  

which will elicit:

warning 2407: zero field width in scanf format string is unused 
   scanf("%0s", buffer); 
          ^

Less common is a format string that is not null terminated, this can happen when a sized array of char is initialized in a way that prevents the terminating NUL character from being appended, e.g.:

const char fmt[2] = "\%s";    

which when used as a format string will result in warning 496 (the declaration alone is enough to prompt info 784 ).

17.1.2 Argument Inconsistencies

This group of messages will diagnose arguments to format functions that do not match the corresponding conversion specifier in the format string. Warning 559 is issued for significant discrepancies:

printf("\%s", 12);    

will elicit:

warning 559: format '%s' specifies type 'char *' which is inconsistent with 
argument no. 2 of type 'int' 
   printf("%s", 123); 
          ~~  ^~~

Messages 705 and 706 are used to diagnose "nominal" inconsistencies between the expected and actual type or the type pointed to. A "nominal" difference means that the size and basic type of the argument was correct but the type was not exactly the type prescribed by the Standard, e.g. a difference in sign. printf-like functions can accept a star (*) in place of the field width and/or precision in which case the value is taken from the next int argument. If this argument is missing warning 2402 is issued. Warning 2403 is issued if the type of this argument is incorrect.

17.1.3 Positional Arguments

The POSIX positional argument syntax allows arguments to be referred to by number using the syntax n$ where n refers to a data argument. For example the following two calls to printf are equivalent:

printf("%d %d", 1, 2);     // ISO syntax 
printf("%1$d %2$d", 1, 2);  // POSIX positional notation

Positional arguments allow format strings to reference arguments in an order that is different from how they are supplied to the format function as well as using the same argument multiple times in the format string. There are several caveats when using positional arguments. For starters, the position starts at 1, not 0; an attempt to use 0 as a position will elicit message 493 . Positional arguments cannot be mixed with non-positional arguments in the same format string, violations of this rule are diagnosed by message 2401 . Referencing a non-existent positional argument will be diagnosed by messages 494 (data argument positions) and 2404 (field width and field precision positions).

17.1.4 Non-ISO features

Features that are not specified by the ISO C Standard may not be portable to other platforms and their use can be diagnosed by PC-lint Plus. These features include positional arguments described above (message 855 ), non-ISO format specifiers such as %m for printf-like functions and %C, and %D for printf-like and scanf-like functions (message 816 ), and non-standard length modifiers / conversion specifier combinations (message 499 ). Since the behavior of these features are not specified by the Standard, their use on platforms that do not support them may result in unintended or undefined behavior.

17.1.5 Incorrect Format Specifiers

There are several reasons that a conversion specifier may be invalid and PC-lint Plus will diagnose these.

1.
An incomplete format specifier (e.g. %h) will be diagnosed by warning 492 ,
2.
an unknown conversion specifier (e.g. %b) by warning 557 ,
3.
inconsistent or redundant format specifiers (e.g. %+u) are diagnosed by warning 566 , and
4.
illegal use of a field width or precision with a conversion specifier will result in warning 2405 .

Each of these messages represent a programming error or the use of extensions that PC-lint Plus is not aware of. For example:

printf("\%.10c", 'a');  

will result in:

warning 2405: precision used with 'c' conversion specifier is undefined 
   printf("%.10c", 'a'); 
          ~^~~~

A precision is not allowed with the %c conversion specifier and providing one results in undefined behavior.

17.1.6 Suspicious Format Specifiers

There are several suspicious constructs that by themselves do not represent errors but are sufficiently unusual to warrant review. This includes

1.
an empty format string (message 497 ),
2.
a format string that contains an embedded NUL character (message 495 ),
3.
the use of a non-literal format string (messages 592 and 905 ),
4.
unused data arguments (message 719 ).

17.1.7 Elective Notes and Customization

Message 983 will point out uses of dash(-) within a scanf scan-list (e.g. %[A-Z]). As the behavior of the dash in this position is implementation defined, some implementations interpret this as a range, others do not.

printf and scanf conversion specifiers can also be deprecated using the -deprecate option, which will cause message 586 to be emitted when they are seen. E.g. -deprecate(printf_code, n) will cause a warning to be issued whenever the %n conversion specifier is used in a printf-like function. Similarly, use scanf_code to deprecate scanf conversion specifiers. See -deprecate for additional information.

The -printf and -scanf options allow a user to specify functions that resemble a member of the printf or scanf family. PC-lint Plus has built-in support for the following formatting functions, including those from Annex K in the C11 Standard:





printf-like functions scanf-like functions Annex K printf-like functions Annex K scanf-like functions








fprintf fscanf fprintf_s fscanf_s




printf scanf printf_s scanf_s




snprintf sscanf snprintf_s sscanf_s




sprintf vfscanf sprintf_s vfscanf_s




vfprintf vscanf vfprintf_s vscanf_s




vprintf vsscanf vprintf_s vsscanf_s




vsnprintf vsnprintf_s




vsprintf vsprintf_s




17.2 Precision, Viable Bit Patterns, and Representable Values

Several messages (including 650 , 587 , and 734 ) deal with the notion of “precision” or otherwise involve static determination of whether or not a value is representable in a particular context. In PC-lint Plus precision has been expanded from covering only the conceptual width of a value (e.g. a bitfield or right-shifted variable) to encompass the potential bit patterns that can result from the use of bitwise operators, addition, subtraction, or values of enum type. Many such messages utilize supplemental messages to convey bit pattern information when relevant. For example, for some unsigned int u:

   if ( (u & 0x10) == 0x11 ) { }

will result in:

warning 587: predicate '==' can be pre-determined 
   and always evaluates to false 
   if( (u & 0x10) == 0x11 ) { } 
      ~~~~~~~~~~ ^  ~~~~ 
supplemental 891: incompatible bit patterns: 
   U32_00000000000000000000000000010001 vs 
   U32_000000000000000000000000000?0000 
   if ( (u & 0x10) == 0x11 ) { } 
                ^

where U32_00000000000000000000000000010001 is the binary representation of the constant 0x11 (17) as an unsigned 32-bit integer and U32_000000000000000000000000000?0000 shows the potential values of the left operand which may or may not have the bit in the sixteens place set but cannot have the bit in the ones place set.

In a more complex example, the effects of implicit conversions may be visible, for example:

1void f(char c1, char c2) { 
2   if ( (c1 & 13) + (c2 & 8) == 6 ) { } 
3}

will result in:

warning 587: predicate '==' can be pre-determined and always evaluates to false 
   if ( (c1 & 13) + (c2 & 8) == 6 ) { } 
       ~~~~~~~~~~~~~~~~~~~~ ^  ~ 
supplemental 891: incompatible bit patterns: 
   S32_00000000000000000000000000000110 vs 
   S32_............................??0? 
   if ( (c1 & 13) + (c2 & 8) == 6 ) { } 
                         ^

The first bit pattern represents the constant 6. The second bit pattern is masked with periods beyond its meaningful precision because it is signed in order to reduce confusion regarding the potential sign extension of an inexact value. This message is indicating that the resultant sum cannot ever have the bit in the twos place set and therefore cannot represent the value to which it is being compared.

Supplemental messages displaying bit patterns will only appear when they will provide useful information beyond that conveyed by the precision specified in the original message. For example:

1void f(unsigned char uc) { 
2   if (uc == -1) { } 
3}

will result in:

warning 650: constant '-1' out of range for operator '==' 
   if (uc == -1) { } 
         ^

with no accompanying supplemental message.

The following message numbers currently utilize the unified precision and viable bit pattern architecture:

# Context Category



572 >> or >>= by a constant loss of precision
587 ==, !=, <, <=, >, or >= with one constant operand pre-determined predicate
650 ==, !=, <, <=, >, or >= with one constant operand, switch case pre-determined predicate
685 <, <=, >, or >= with one constant operand pre-determined predicate
734 assignment loss of precision
2415 ==, !=, <, <=, >, or >= in a loop condition with no constant operands pre-determined predicate

17.3 Static Initialization

Traditional lint Compilers do not flag uninitialized static (or global) variables because the C/C++ language defines them to be 0 if no explicit initialization is given. But uninitialized statics, because they can cover such a large scope, can be easily overlooked and can be a serious source of error. Additionally, some embedded compilers do not perform this standard mandated implicit initialization. PC-lint Plus will flag static variables (see messages 727 , 728 and 729 ) that have no initializer and that are assigned no value. For example, consider:

int n; 
int m = 0;

There is no real difference between the declarations as far as C/C++ is concerned but PC-lint Plus regards m as being explicitly initialized and n not explicitly initialized. If n is accessed by nowhere assigned a value, a complaint will be emitted.

17.4 Indentation Checking

Indentation checking can be used to locate the origins of missing left and right braces. It can also locate potential problems in a syntactically correct program. For example, consider the code fragment:

if( ... ) 
   if( ... ) 
      statement 
else statement

Apparently the programmer thought that the else associates with the first if whereas a compiler will, without complaint, associate the else with the second if. PC-lint Plus will signal that the else is negatively indented with respect to the second if.

There are three forms of messages; Informational 725 is issued in the case where there is no indentation (no positive indentation) when indentation is expected, Warning 525 is issued when a construct is indented less than (negatively indented from) a controlling clause, and 539 is issued when a statement that is not controlled by a controlling clause is nonetheless indented from it.

Of importance in indentation checking is the weight given to leading tabs in the input file. Leading tabs are by default regarded as 8 blanks but this can be overridden by the -t# option. For example -t4 signifies that a tab is worth 4 blanks (see the -t# option in Section 4.3.3 Message Presentation ).

Recognizing indentation aberrations comes dangerously close to advocating a particular indentation scheme; this we wish to avoid. For example, there are at least three main strategies for indentation illustrated by the following templates:

if( e ) { 
   statements 
} 
 
if( e ) 
   { 
   statements 
   } 
 
if( e ) 
{ 
   statements 
}

Whereas the indentation methods appear to differ radically, the only real difference is in the way braces are handled. Statements are always indented positively from the controlling clause. For this reason PC-lint Plus makes what is called a strong check on statements requiring that they be indented (or else a 725 is issued) and only a weak check on braces requiring merely that they not be negatively indented (or else a 525 is issued).

case, and default undergo a weak check. This means, for example, that

switch() 
   { 
case 'a' : 
   break; 
default: 
break; 
   }

raises only the informational message (725 ) on the second break but no message appears with the case and default labels.

The while clause of a do ... while(e); compound undergoes a weak check with respect to the do, and an else clause undergoes a weak check with respect to its corresponding if.

An else if() construct on the same line establishes an indentation level equal to the location of the else not the if. This permits use of the form:

if() 
   statement} 
else if() 
   statement 
else if() 
   statement 
... 
else 
   statement

Only statement beginnings are checked. Thus a comment can appear anywhere on a line and it will not be flagged. Also a long string (if it does not actually begin a statement) may appear anywhere on the line.

A label may appear anywhere unless the +fil flag is given (Section 4.11 Flag Options ) in which case it undergoes a weak check.

Message 539 is issued if a statement that is not controlled by a loop is indented from it. Thus:

while ( n > 0 ); 
   n = f(n);

draws this complaint, as well it should. It appears to the casual reader that, because of the indentation, the assignment is under the control of the while clause whereas a closer inspection reveals that it is not.

17.5 Size of Scalars

Since the user of PC-lint Plus has the ability to set the sizes of various data objects (See the -s .. options in Section 4.5.1 Scalar Data Size ), the reader may wonder what the effect would be of using various sizes.

Several of the loss of precision messages (712 , 734 , 735 and 736 ) depend on a knowledge of scalar sizes. The legitimacy of bit field sizes depends on the size of an int. Warnings of format irregularities are based in part on the sizes of the items passed as arguments.

One of the more important effects of type sizes is the determination of the type of an expression. The types of integral constants depend upon the size of int and long in ways that may not be obvious. For example, even where int are represented in 16 bits the quantity:

35000  

is long and hence occupies 4 (8-bit) bytes whereas if int is 32 bits the quantity is a four byte int. If you want it to be unsigned use the u suffix as in 35000u or use a cast.

Here are the rules: the type of a decimal constant is the first type in the list (int, long, long long) that can represent the value. The maximum values for these types are taken to be 2sizeof(type)*bits-per-byte-1 - 1. The quantities sizeof(int), sizeof(long), and sizeof(long long) are based on the -si#, -sl#, and -sll# options respectively. The type of a hex or octal constant, however, is the first type on the list (int, unsigned int, long, unsigned long, long long, unsigned long long).

For any constant (decimal, hex or octal) with a u or U suffix, one selects from the list (unsigned int, unsigned long, unsigned long long). If an l or L suffix, the list is (long, long long) for decimal constants and (long, unsigned long, long long, unsigned long long) for hex and octal constants. If both suffixes are used (e.g. UL), the list is (unsigned long, unsigned long long) for any constant. If the suffix is ll or LL, the type is unsigned long long for decimal constants and either long long or unsigned long long for hex and octal constants. Finally, constants containing both the u/U and ll/LL suffixes are always of type unsigned long long, regardless of base.

The size of scalars enters into the typing of intermediate expressions in a computation. Following ANSI/ISO standards, PC-lint Plus uses the so-called value-preserving rule for promoting types. Types are promoted when a binary operator is presented with two unlike types and when unprototyped function definitions specify subinteger parameters. For example, if an int is added to an unsigned short, then the latter is converted to int provided that an int can hold all values of an unsigned short; otherwise, they are both converted to unsigned int. Thus the signedness of an expression can depend on the size of the basic data objects.

17.6 Stack Usage Report

    +stack(sub-option,...)
    -stack(sub-option,...)

The +stack version of this option can be used to trigger a stack usage report. The -stack version is used only to establish a set of options to be employed should a +stack option be given. To prevent surprises if a -stack option is given without arguments it is taken as equivalent to a +stack option.

The sub-options are:

&file=filename

This option designates the file to which the report will be written. This option must be present to obtain a report.

&overhead(n)

Establishes a call overhead of n bytes. The call overhead is the amount of stack consumed by a parameterless function that allocates no auto storage.

Thus if function A(), whose auto requirements are 10, calls function B(), whose auto requirements are also 10, and which calls no function, then the stack requirements of function A() are 20+n where n is the call overhead. By default, the overhead is 8.

&external(n)

Establishes an assumption that each external function (that is not given an explicit stack requirement, see below) requires n bytes of stack. By default this value is 32.

&summary

This option indicates that the programmer is interested in at least a summary of stack usage (stack used by the worst case function). The summary comes in the form of Elective Note 974 and is equivalent to issuing the option +e974. This option is not particularly useful since a summary report will automatically be given if a +stack option is given. It is provided for completeness.

name(n)

Where name is the name of a function, explicitly designates the named function as requiring n bytes of total stack. This is typically used to provide stack usage values for functions whose stack usage could not be computed either because the function is involved in recursion or in calls through a function pointer. name may be a qualified name.

&prealloc_vars

Allocations for all variables within a function will occur at the start of that function. This will typically increase stack usage since all variables are considered simultaneously reachable and are not removed from the stack when their scope ends. This is consistent with how some compilers allocate variables within a function.

Example:
+stack( &file=s.txt, alpha(12), A::get(30) )

requests a stack report to be written to file s.txt and further, that function alpha() requires 12 bytes of stack and function A::get() requires 30.

At global wrap-up, a record is written to the file for each defined function. The records appear alphabetized by function name.

Each record will contain the name of a function followed by the amount of auto storage required by its local auto variables. Note that auto variables that appear in different and non-telescoping blocks may share storage so the amount reported is not simply the sum of the storage requirements of all auto variables.

Each function is placed into one of seven categories as follows:

1.
recursive loop – a function is recursive loop if it is recursive and we can provide a call to a function such that that call is in a recursive loop that terminates with the original function. Thus the function is not merely recursive but demonstrably recursive. The record contains the name of a function called and it is guaranteed that the called function will also be reported as recursive loop.

It is assumed that any recursive function requires an unbounded amount of stack. If that assumption is incorrect and you can deduce an upper bound of stack usage, then you can employ the +stack option to indicate this upper bound. In a series of such moves you can convert a set of functions containing recursion to a set of functions with a known bound on the stack requirements of each function.

2.
recursive – a function is designated as recursive if it is recursive but we do not provide a specific circular sequence of calls to demonstrate the fact. Thus the function is recursive but unlike recursive loop functions it is not demonstrably recursive. The record contains the name of a function called. This function will either be recursive loop, recursive or calls recursive (see next category). If you follow the chain of calls it is guaranteed that you will ultimately arrive at a function that is labeled recursive loop.

The distinction between the recursive and recursive loop categories is illustrated by the example:

void x(), y(), z(); 
void x() { y(); z(); } 
void y() { x(); } 
void z() { x(); }

The stack report will show that x calls y, and that y calls x. As this demonstrates the recursive nature of both functions, they will be placed in the recursive loop category. The report will also show z calling x, but the displayed callee for the x entry is y. As the report does not demonstrate the call chain leading to a recursive call to z, it will be classified as recursive.

3.
calls recursive – a function may itself be non-recursive but may call a function (directly or indirectly) that is recursive. The stack requirements of functions in this category are considered to be unbounded. The record will contain the name of a function that it calls. This function will either be ’recursive loop’, ’recursive’ or ’calls recursive’. If you follow the chain of calls it is guaranteed that you will ultimately arrive at a function that is labeled ’recursive loop’.
4.
non-deterministic – a function is said to be non-deterministic if it calls through a function pointer. The presumption is that we cannot determine by static means the set of functions so called. No function is labeled non-deterministic unless it is first determined that it is not in the recursive categories. That is, it could not be determined following only deterministic calls that it could reach a recursive function.

If you can determine an upper bound for the stack requirements of a non-deterministic function then, like a recursive function, you may employ the +stack option to specify this bound and in a sequence of such options determine an upper bound on the amount of stack required by the application.

5.
calls a non-deterministic function – a function is placed into this category if it calls directly or indirectly a non-deterministic function. It is guaranteed that we could not find a recursive loop involving this function or even a deterministic path to a recursive function. The record will be accompanied by the name of a function called. It is guaranteed that if you follow the chain of calls you will reach a non-deterministic function.
6.
finite – a function is finite if all call chains emanating from the function are bounded and deterministic. The record will contain a total stack requirement. This will be a worst case stack usage. The record will bear the name of a function called (or ’no function’ if it does not call a function). If you follow this chain you will pass through a (possibly zero length) sequence of finite functions before arriving at a function that

    (a) is labeled as ’finite’ but calls no other function or
    (b) is labeled as ’external’ or
    (c) is labeled as ’explicit’ (see next category).

You should be able to confirm the stack requirements by adding up the contribution from each function in the chain plus a fixed call overhead for each call. The amount of call overhead can be controlled by the stack option.

For ’external’ functions there is an assumed default stack requirement. You may employ the +stack option to specify the stack requirement for a specific function or to alter the default requirement for external functions.

7.
explicit – a function is labeled as explicit if there was an option provided to the -stack option as to the stack requirements for a specific function.

Stack Report Formatting Options

The information provided by this option can be formatted by the user using the -format_stack option. This allows the information to be formatted to a form that would allow it to be used as input to a database or to a spreadsheet. This format can contain the escape codes:

    ’%f’ for the function name
    ’%a’ for the local auto storage
    ’%t’ for type (i.e. one of the seven categories above)
    ’%n’ for the total stack requirement (or -1 if unbounded by recursion)
    ’%c’ for the callee and
    ’%e’ for an ’external’ tag on the callee

See -format_stack for more details. See also Message 974 .

17.7 Migrating to 64 bits

Applications written for the traditional 32-bit model where int, long and pointers are each represented in 32 bits, may have difficulty when ported to one of the 64-bit models. Problems that you may encounter and that PC-lint Plus will catch are described and implemented as options in file au-64.lnt. This file and other .lnt files mentioned below are distributed with the product and/or are downloadable from our web site.

The file au-64.lnt is not intended to be used directly by the programmer. There are a number of wrappers reflecting the different flavors of 64-bit computing. These wrappers specify sizes and other options unique to specific models and then invoke au-64.lnt. The models and the au file that you should be using are described below.





Data Type LP64 Model LLP64 Model ILP64 Model








long long 64 bits 64 bits 64 bits




pointers 64 bits 64 bits 64 bits




long 64 bits 32 bits 64 bits




int 32 bits 32 bits 64 bits





The above table shows the differences between each of the 3 64-bit models. Each model has a corresponding au file: au-lp64.lnt for LP64, au-llp64.lnt for LLP64, and au-ilp64.lnt for ILP64.

17.8 Deprecation of Entities

You may indicate that a particular name is not to be employed in your programs by using this option:

-deprecate(category, name [,commentary])    

category is one of: function, keyword, macro, option, variable, type, basetype, ppw, printf_code or scanf_code.

The commentary in the third argument will be appended to the message. For example,

-deprecate( variable, errno, Violates Policy XX-123 )  

When the use of errno as a variable is detected (but not its definition or declaration) the following Warning is issued.

Warning 586: variable 'errno' is deprecated. Violates Policy XX-123    

When the category of deprecation is variable only the use of external variables are flagged. Local variables may be employed without disparaging comment.

If errno were a macro you would need to deprecate errno as a macro:

-deprecate( macro, errno, Violates Policy XX-123 )    

If errno could be either (the standard allows both forms) then both options should be used.

You may also deprecate functions and keywords. For example:

-deprecate( keyword, goto, goto is considered harmful ) 
-deprecate( function, strcpy, has been known to cause overuns )

could be used to flag the use of suspect features.

Quotes (both single and double) and parentheses within the commentary need to be balanced.

When deprecating functions or variables inside of a C++ anonymous namespace, prefixing their name with "(anonymous namespace)::" will allow them to be deprecated.

17.8.1 Deprecation of Options

Options can also be deprecated. Deprecating an option causes future uses of that option to be met with message 586 although the option is still processed as usual. When deprecating an option, you must specify the name of the option, including a leading ’-’ or ’+’ but must not provide any arguments for the option. For example:

-deprecate( option, -setenv, environment variables should not be set during the 
linting process)

will deprecate the use of -setenv . It is not possible to deprecate the use of individual flag options; using the -deprecate option with -f, +f, –f, or ++f will deprecate all flag options. Note that some options have forms that begin with ’-’ and another form that begins with ’+’; deprecating one form does not automatically cause the other form to be deprecated even if both forms have identical meanings.

17.8.2 Deprecation of Types

The -deprecate option can be used to deprecate types. This can be accomplished using the deprecation categories ’type’ and ’basetype’.

When the category ’type’ is specified, message 586 is issued for any use of the type (outside of typedef declarations and explicit template instantiations) but type alias names (introduced via typedef or using) are not looked through and use of the underlying type is allowed without complaint if it occurs through such an alias.

The category of ’basetype’ is similar except type aliases are looked through and if at any level the deprecated type is present, 586 is issued. If the deprecated type is a typedef type, no diagnostic is issued for the declaration of the type (although use of the type is diagnosed).

Using a deprecated type as a target of a typedef is not diagnosed with 586 . The logic is that for ’basetype’, the use of the typedef that targets the deprecated type will be diagnosed anyway and that for ’type’ the user is not interested in use of the type through typedefs. We do provide a new elective message, 986 , that will be issued when the target of a type alias is deprecated with the ’type’ category.

//lint -deprecate(type, int) warn about uses of int but not through an alias 
//lint -deprecate(basetype, DOUBLE) 
 
typedef int INT;       // 986 issued for 'int' appearing in typedef 
typedef double DOUBLE;  // okay, declaration of deprecated type 
typedef DOUBLE DOUBLE2; // okay, use of deprecated basetype in alias 
 
int i;// 586 - 'int' is deprecated 
INT i2;// okay, use of deprecated type via alias 
 
union u { 
   const int * pci;  // 586 - deprecated type 'int' used in declaration 
   double * pd;      // okay, 'double' is not deprecated 
   DOUBLE d1;       // 586 - deprecated basetype 'DOUBLE' used directly 
   DOUBLE2 d2;       // 586 - deprecated basetype 'DOUBLE' used indirectly 
};

While analyzing C modules, the keyword "struct", "enum", or "union" should be included in the type name of the -deprecate option unless declared using an anonymous typedef declaration. For example:

//lint -deprecate(type, MyData) 
typedef struct { 
   int i; 
} MyData; 
 
//lint -deprecate(type, struct OtherData) 
struct OtherData { 
   int j; 
};

While analyzing C++ modules, the inclusion of keywords such as "struct", "class", "union", or "enum" is optional when using the -deprecate option. They will be ignored.

17.8.3 Deprecation of Preprocessor Directives

Individual preprocessor directives can be deprecated with the ppw category. For example:

-deprecate(ppw, pragma)

will deprecate the use of the #pragma preprocessor directive, note that the # is not included in the -deprecate option. Message 586 will be issued when the deprecated directive is encountered unless it is encountered in a conditionally excluded region (e.g. between #if 0 ... #endif) in which case message 886 will be issued instead.

An unknown preprocessor directive can be deprecated, PC-lint Plus will issue the deprecation message and then complain about the unknown directive as usual. The #error directive can be deprecated in which case the deprecation message will be emitted before the directive is handled. The null directive can be deprecated using an empty second argument to -deprecate.

Note that the -ppw_asgn option does not establish any deprecation relationship between the named directives. For example, if the option -ppw_asgn(foo, line) is used to cause #foo to be handled as #line, deprecating line will not also deprecate foo or vice versa.

17.8.4 Deprecation of Format Function Conversion Specifiers

The -deprecate option can be used to deprecate conversion specifiers for printf-like and scanf-like functions using the printf_code and scanf_code categories. For example,

-deprecate(printf_code, n)    

will deprecate the use of %n in printf-like functions, note that the % is not included in the -deprecate option. Deprecating a conversion specifier will result in message 586 being issued if the conversion specifier is used, regardless of any length modifiers present in the actual use, but will not deprecate other conversion specifiers with the same meaning. For example -deprecate(printf_code, i) will warn for %i and %hi but will not warn for %d (which has the same meaning as %i in printf-like functions).

17.8.5 Deprecation of Templates

The -deprecate option can be used to deprecate C++ template functions, variables, and types. Deprecations can either be applied to abstract template declarations, or to specific template specializations. For example,

-deprecate(function, templateFn) 
template <typename T> void templateFn(void);

will deprecate the use of templateFn with any template arguments passed to it. While,

-deprecate(function, templateFn<int>)

would only deprecate uses of the templateFn where the template argument is resolved to be int.

A specific format must be observed when specifying specific template specializations. All template arguments must be present, including default arguments that were omitted from the as-written type. All types used as a template argument must not use any typedef names or type aliases. Nested template arguments must place the closing caret immediately next to each other without spaces regardless of whether the language version being parsed supports that convention. All non-type arguments that are integer constant expressions must be evaluated to their result. A single space is expected after each comma between template arguments. Due to option parsing, it may be necessary to escape each comma used in the template argument list. An example deprecation comment and its deprecated usage is included below.

typedef int MYINT; 
template <typename T> class TemplateClass {}; 
template <int I, typename T = TemplateClass<MYINT> > void templateFn(void); 
 
//lint -deprecate(function, "templateFn<2, TemplateClass<int>>") 
 
void exampleUsage(void) { 
 templateFn<1+1>();  // warn 586 
}

17.9 Parallel Analysis

PC-lint Plus supports the long-requested feature of utilizing multiple cores to achieve faster processing times. This feature is enabled by placing the new -max_threads=n option before the first module to process. If this option does not appear before the first module is seen, the behavior is as if -max_threads=1 was used. Threads are used both during the main processing phase and the global wrap-up phase. In the main phase, a separate thread is dispatched to handle each module, up to a maximum of n concurrent threads. When all of the modules have been processed, threads are employed to handle wrap-up processing, again up to a maximum of n concurrent threads.

When using C++20 Modules with n > 1, if one or more threads are waiting to process a module import pending the completion of the build of a module interface unit in another thread, then a single extra thread may also be launched to process another module in the meantime. This will never result in more than n threads concurrently performing work, but both waiting threads and the single extra thread contribute to the overall process memory utilization with the potential peak memory usage being similar to what would otherwise be expected from a fractional value somewhere between n and n + 1 threads.

While results will vary depending on a variety of factors, the best overall times are typically achieved when using a value for n that equals the number of available cores, or about twice the number of cores for processors that support hyper-threading. Some experimentation may be necessary to find the best value for n on a particular system. In order to assist in that regard, the option -max_threads=0 will result in PC-lint Plus picking a value for n that it thinks is optimal based on querying of the available hardware for systems that support it. When using -max_threads=0, elective note 999 will report on the number of threads that has been selected.

There are a few caveats to keep in mind when employing multiple threads:

1.
Very little memory is shared between threads, which means that memory usage scales roughly linearly with the number of concurrent threads. For example, if using 1 thread results in memory usage of 500MB, it probably wouldn’t be productive to utilize more than 4 threads on a system with 2GB of RAM, regardless of how many cores may be available.
2.
Output is buffered by module when using multiple threads. This means that the output for a module will not be emitted until the entire module is processed (this happens before global wrap-up). Additionally, the order in which modules are processed is not guaranteed although output will never be interleaved between modules. For example, when processing modules A and B, the diagnostics for module A may appear (in their entirety) before or after module B when using multiple threads and this order may change between runs. When using a single thread, diagnostics for one module will always appear before the diagnostics for a later-provided module.
3.
Interactive features such as the Value Tracking Debugger are supported only when executing with a single thread.

Aside from the above caveats, there is no difference in behavior or functionality when using multiple threads.

17.10 Language Limits

The C and C++ Standards define minimum translation limits that must be supported by a conforming compiler. The limits specify quantities such as the minimum number of significant characters in internal and external identifiers, the minimum number of function parameters that an implementation must support, the minimum number of supported concurrently defined macros, and the minimum number of data members supported in structures. The C99 limits are specified in section 5.4.2.1 of the C99 Standard (ISO/IEC 9899:1999) and the C++ limits are specified in Annex B of the C++ Standard (ISO/IEC 14882:2011).

-lang_limit(C\C++, limit-name, limit-value) specify minimum language translation limits

The -lang_limit option takes three arguments. The first argument is either C or C++ indicating which language the overridden limit applies to. The second argument is the type of limit and must be one of the names in the first column of the table below. The last argument is a value that must be between 0 and 4294967294 (0 indicates the lack of a limit) or the special value of default, which reverts back to the corresponding value of the table below essentially removing a previously overridden value.

The table below lists the language limit checks supported by PC-lint Plus. The Limit Name is the name recognized in the second parameter of the -lang_limit option. The Limit Description is the text that is used in the 793 message when the limit is exceeded and which can be used with the -estring option for suppression purposes. The C89 Limit shows the minimum limits specified by the ANSI C89 standard. The C99 Limit column shows the minimum limits mandated by C99, the C11 Limits are identical. The C++ Limit shows the limits required by the C++ Standard (all versions of C++ share the same limits).






Limit Name

Limit Description

C89 C99 C++

Limit Limit Limit





external_identifiers

external identifiers

511 4095 65526
internal_identifier_chars

significant characters in an internal identifier

31 63 1024
macro_identifier_chars

significant characters in a macro name

31 63 1024
external_identifier_chars

significant characters in an external identifier

6 31 1024
function_parameters

function parameters

31 127 256
function_arguments

function arguments

31 127 256
macro_parameters

macro parameters

31 127 256
string_literal_length

characters in a string literal

509 4095 65536
case_labels

case labels in a switch

257 1023 16384
structure_members

structure members

127 1023 16384
enumeration_constants

enumeration constants

127 1023 4096
base_classes

base classes

n/a n/a 16384
direct_base_classes

direct base classes

n/a n/a 1024
class_members

members in a class

n/a n/a 4096
static_members

static members in a class

n/a n/a 1024
final_functions

final overriding virtual functions in a class

n/a n/a 16384
virtual_base_classes

virtual base classes

n/a n/a 1024
friend_decls

friend declarations in a class

n/a n/a 4096
access_decls

access control declarations in a class

n/a n/a 4096
ctor_initializers

member initializers in a constructor

n/a n/a 6144
scope_qualifiers

scope qualifiers in an identifier

n/a n/a 256
template_arguments

template arguments in a template

n/a n/a 1024
try_handlers

handlers in a try block

n/a n/a 256
throw_specs

throw specifications in a function

n/a n/a 256

By default, the limits shown above are used to determine when a minimum limit has been exceeded and, for C, is dependent on the version of the language used. If your compiler supports different limits, or if you just want to be alerted when a different threshold is reached for a particular limit, you can use the -lang_limit to override the defaults shown above.

The table below provides additional details about some of the limits checked by PC-lint Plus.




Limit Name

Notes




external_identifiers

External identifiers are functions and variables with external linkage.

internal_identifiers

Internal identifiers are any non-preprocessor (e.g. macro) symbols that are not external identifiers. These include type names, class names, local variables, enumeration constants, etc.

case_labels

This does not include the default label or case labels of nested switches.

structure_members

This includes only non-static data members but includes members inherited from base classes.

base_classes

The number of bases for a given class including indirect and virtual bases.

direct_base_classes

Virtual and non-virtual direct base classes for a class.

class_members

Includes all static and non-static data and function members declared directly in the class (e.g. not inherited members). Note that this also includes implicitly generated functions such as constructors, assignment operators, etc.

static_members

All static data and function members, including inherited members for a class.

virtual_base_classes

Direct and indirect virtual base classes for a class.

access_decls

The number of access control (or using) declarations present in a class. Does not include base classes. This is not a count of the access-specifiers present in a class.

ctor_initializers

The number of items initialized in a constructor member initializer list. Includes base class initializers.

scope_qualifiers

This is the number of nested name specifiers present in a qualified-id, e.g. A::B::C contains two scope qualifiers.

17.11 Diagnostic Accounting

PC-lint Plus can produce accounting data for all diagnostic encounters. A diagnostic encounter is any primary (non-supplemental) diagnostic (message) that was either emitted, or would have been emitted if the message was enabled and not otherwise suppressed via a suppression option, and any supplemental message (emitted or suppressed) associated with an emitted primary message. This accounting data includes information about each diagnostic encounter, including details of the diagnostics, the suppression options involved in the encounter, and the impact of those suppression options on the decision to emit or inhibit the message. This information may be used to identify suppressed messages and to examine the role each suppression option played in determining whether or not to emit a message. Diagnostic accounting data can additionally be used to:

Diagnostic Accounting is enabled with the -diag_accounting option. This option accepts several arguments specifying the output files that PC-lint Plus should write accounting data to. Each output file corresponds to a table of information that is collected when diagnostic accounting is enabled and written to the specified file in JSON format. The details of each table are provided below.

17.11.1 The Diagnostics table

The Diagnostics table contains one entry per diagnostic encounter. Each entry contains the standard information about the diagnostic that would appear when it is emitted (its message number, message text, user-defined append text, location) along with whether or not it was actually emitted, and whether the message comes from a library location.

Note that while suppression options can influence the disposition of diagnostic encounters, and all diagnostic encountered are recorded regardless of disposition, there are several factors that can influence whether a diagnostic accounter occurs to begin with including:

When such factors prevent a diagnostic encounter from taking place, the unmanifested encounter is not represented in the Diagnostics table.

Note that supplemental messages are only represented in the Diagnostics table if their corresponding primary message was emitted (supplemental messages of suppressed primary messages are not considered to be diagnostic encounters).




Field name Field type

Description




id Integer

A zero-indexed value that uniquely represents a diagnostic encounter within an invocation of PC-lint Plus.

module_id Integer

A zero-indexed value that represents the module from which the diagnostic was encountered. For messages encountered within a module, this corresponds to the id provided in the Modules table. For messages encountered outside of a module, this value is 0.

pass_num Integer

A zero-indexed value representing the analysis pass. The first pass is represented with a value of 0, the second pass a value of 1, etc.

is_supplemental Boolean

Indicates whether this message is a supplemental message (e.g. 831, 891, 892, 893, 894, 896, or 897). The value of this field is independent of that value of the fin flag option which controls whether supplemental messages are rendered with a category of "info".

parent_id Integer

For supplemental messages, this value is the id of the corresponding primary message. For non-supplemental messages, the value of this field is 0.

supplemental_index Integer

For supplemental messages, this value represents the relative order among all supplemental messages associated with the corresponding primary message. A value of 1 indicates that this supplemental message should be presented first, etc. A value of 0 is used for non-supplemental messages.

msg_num Integer

The PC-lint Plus message number, e.g. 9260.

filename String

The name of the file pointed to by the message. For locations that do not correspond to a file, the value may be empty or (if the fla option is enabled) may have a representative name in angle brackets such as <command line>.

full_filename String

The absolute filename of the file pointed to by the message. Can be an empty string if the filename is not on the filesystem.

line_num Integer

The line number (starting at 1) associated with the diagnostic. This field will have a zero value for locationless diagnostics.

col_num Integer

The column number (starting at 1) associated with the diagnostic. This field will have a zero value for locationless diagnostics.

highlights Array

An array of objects containing the below members that describe the start and end locations of a highlighted region associated with the diagnostic. This field will not be present if there are no highlights.

filename_start String

The file in which the highlight starts.

line_num_start Integer

The line number of the start of the highlight.

col_num_start Integer

The column number (starting at 1) of the start of the highlight.

filename_end String

The file in which the highlight ends.

line_num_end Integer

The line number of the end of the highlight.

col_num_end Integer

The column number of the end of the highlight.

is_fatal Boolean

Indicates whether the message is a fatal message, i.e. whether PC-lint Plus would terminate as a result of issuing the message.

is_suppressible Boolean

Indicates whether this message can be suppressed (while most messages are suppressible, several messages are not).

is_library Boolean

Indicates whether the diagnostic is considered to emanate from a library location.

was_rejected Boolean

Indicates whether this message was suppressed due to a -reject option encountered during evaluation of a Hook in a message context.

was_emitted Boolean

Indicates whether the message was emitted, i.e. not suppressed.

msg_text String

The diagnostic text of the message, not including text appened by -append options.

append_text String

The text to append to the message as per relevant -append options.


17.11.2 The Options table

The options table contains one entry for every suppression option encountered. The first two options in this table correspond to the implicit -w3 and -wlib=1 options that reflect the default warning level and library warning level at startup. These implicit options have a location filename of builtin-default.




Field name Field type

Description




id Integer

A zero-indexed value that uniquely represents a suppression option within an invocation of PC-lint Plus.

module_id Integer

A zero-indexed value that represents the module being analyzed when the suppression option was encountered. For options encountered within a module, this corresponds to the id provided in the Modules table. For messages encountered outside of a module, this value is 0.

pass_num Integer

A zero-indexed value representing the analysis pass. The first pass is represented with a value of 0, the second pass a value of 1, etc.

option_text String

The text of the suppression option.

filename String

The name of the file containing the suppression option. For locations that do not correspond to a file, the value may be empty or (if the fla option is enabled) a representative name in angle brackets such as <command line>.

full_filename String

The absolute filename of the file containing the suppression option.

line_num Integer

The line number (starting at 1) of the suppression option.

col_num Integer

The column number (starting at 1) of the suppression option. For options encountered in comments, this value is the start of the comment.

17.11.3 The Suppressions table

The Suppressions table contains an entry for every suppression object. Each suppression option represented in the Options table will correspond to one or more suppression objects. Parameterized suppression options that specify multiple parameter patterns are represented by one entry in the Options table and one entry in the Suppressions table for every parameter pattern provided.




Field name Field type

Description




id Integer

A zero-indexed value that uniquely represents a suppression object within an invocation of PC-lint Plus.

option_id Integer

The id of the corresponding option entry from the Options table.

effective_msg_set String

A canonicalized representation of the messages affected by this suppression object. Messages that were frozen (via +efreeze /++efreeze ) at the time the corresponding suppression option was encountered are excluded from this list. The canonicalized representation consists of a space-separated list of message numbers and/or hyphenated message number ranges, e.g. 12 120-129 230-239 1200-1299.

param_pattern String

The provided parameter pattern, if any.

is_enable Boolean

true if the suppression object votes in favor of message issuance, false if the suppression object votes against message issuance.

is_library Boolean

true if this suppression only affects diagnostics within library regions.

is_dss Boolean

true if this suppression affects the default suppression set. If is_library and is_dss are both true, this suppression affects the default library suppression set.


PC-lint Plus maintains a default suppression set and a default library suppression set which represent which messages are globally enabled for non-library and library regions, respectively. In the absence of any other suppression options, a message will be emitted within a non-library region if it is enabled in the default suppression set. Within a library region, in the absence of any other suppression options, a message will be emitted if it is enabled in both the default suppression set and the default library suppression set. The +e, -e, and -w options operate on the default suppression set while the +elib, -elib, and -wlib options operate on the default library suppression set.

The is_dss field indicates whether this suppression corresponds to a +e/-e/-w/+elib/-elib/-wlib option. This information can be used to determine whether an orbit with a vote count of zero represents no interaction with the diagnostic encounter (this occurs, for example, when a parameterized suppression has a message pattern that matches the message number of the diagnostic but a message parameter pattern that does not match a message parameter) or is responsible for setting the default message set state for the encounter. Being able to distinguish between these cases is useful for identifying unused suppression options.

17.11.4 The Orbits table

An orbit represents the effect of a candidate suppression object within a particular diagnostic encounter. For each diagnostic encounter, there is one orbit that corresponds to the default suppression set (manipulated via the +e/-e/-w options) and, for library diagnostics, one orbit that corresponds to the default library suppression set (manipulated via the +elib/-elib/-wlib options). In addition, any suppression object currently in effect that contains the message number corresponding to the diagnostic encounter in its effective message set participates in message issuance determination and is represented by an orbit entry.




Field name Field type

Description




id Integer

A zero-indexed value that uniquely represents an orbit within an invocation of PC-lint Plus.

diag_id Integer

The id of the corresponding diagnostic entry from the Diagnostics table.

supp_id Integer

The id of the corresponding suppression entry from the Suppressions table.

votes Integer

The total number of votes that were issued in favor of emitting the diagnostic by the suppression object. A negative value represents votes cast against issuance (votes to suppress).

overrider Boolean

true only if the vote cast by the suppression object cannot be overruled (see below).


The overrider field is always true for interactions with suppression objects resulting from single-line suppression options (!e#), scoped suppression options (-e(#), –e(#), -e{#}, or –e{#}), and for options that affect the default library suppression set (-elib and -wlib) when such options vote against issuance. In all other situations, the value of the overrider field is false. As these options are only used to inhibit messages (they never cast a vote in favor of message issuance), the value of the votes field will always be negative when overrider is true. For library messages, if the message is enabled in the default library suppression set, the corresponding orbit will have a value of 0 for votes and overrider will be false (messages enabled in the library default suppression set do not add a vote for issuance). For library messages where the message is disabled in the default library suppression set, the orbit will have a value of -1 for votes and overrider will be true. Multiple suppression objects may act as overriders within a particular encounter. If any orbits act as an overrider within a particular encounter, the message will be suppressed (unless it is an unsupressible message).

17.11.5 The Modules table

The Modules table contains one entry for each module analyzed by PC-lint Plus. The id field corresponds to the module_id field found in the Diagnostics and Options tables.




Field name Field type

Description




id Integer

A one-indexed value that uniquely represents an analyzed module within an invocation of PC-lint Plus. The first module provided to PC-lint Plus always has the value of 1, the second module 2, etc.

filename String

The name of the module as provided.

full_filename String

The absolute filename of the module.

is_cpp Boolean

true for C++ modules, false for C modules.


17.11.6 The Files table

The Files table contains one entry for each source file (module and header) processed by PC-lint Plus.




Field name Field type

Description




id Integer

A zero-indexed value that uniquely represents a processed source file within an invocation of PC-lint Plus.

module_id Integer

The id of the corresponding entry in the Modules table representing which module was being analyzed when this file was processed.

filename String

The name of the source file as encountered.

full_filename String

The absolute filename of the source file, if available.

sha1_hash String

The SHA-1 hash value of the contents of the source file rendered as a string of 40 hexadecimal digits.


Headers that are included in multiple modules will be represented by a separate entry for each module. Headers that are included multiple times within a single module may result in multiple entries with the same module_id.

17.11.7 The Metadata table

The Metadata table contains run-level information about Diagnostic Accounting results. In consists of a single object with the fields described in the table below.




Field name Field type

Description




messages_output_file String

The name of the file that diagnostic encounteres were written to.

options_output_file String

The name of the file that suppression options were written to.

suppressions_output_file String

The name of the file that suppressions were written to.

orbits_output_file String

The name of the file that orbits were written to.

modules_output_file String

The name of the file that modules were written to.

metadata_output_file String

The name of the file that metadata was written to.

diag_queue_size Integer

The diagnostic queue size.

filter_null_orbits Boolean

Whether orbits that did not cast a vote (the votes field was zero) were excluded to the orbits file.

exclude_library Boolean

Whether library diagnostics (and corresponding orbits) were excluded from output.

exclude_suppressed_library Boolean

Whether suppressed library diagnostics (and corresponding orbits) were excluded from output.

suppressions_processed Integer

The number of suppression objects that were processed.

options_processed Integer

The number of suppression options that were processed.

orbits_processed Integer

The number of orbits that were processed.

diagnostics_processed Integer

The number of diagnostic encounters that were processed.

modules_processed Integer

The number of modules that were processed.

suppressions_written Integer

The number of suppression objects that were written.

options_written Integer

The number of suppression options that were written.

orbits_written Integer

The number of orbits that were written. This may be less than the number of orbits processed if filter_null_orbits, exclude_library, or exclude_suppressed_library is true.

diagnostics_written Integer

The number of diagnostic encounters that were written. This may be less than the number of diagnostic encounters processed if exclude_library or exclude_suppressed_library is true.

modules_written Integer

The number of modules that were written.


17.11.8 Understanding why a message is suppressed/emitted

The was_emitted field in the Diagnostics table indicates whether the diagnostic was emitted or suppressed. The rules that dictate how suppression options interact with each other to determine the final disposition of a diagnostic encounter are described in Section 4.9 How Suppression Options are Applied . The way that these interactions are reflected in the Diagnostics and Orbits tables are described below which may be used to understand why a message was suppressed or emitted and to identify unused suppression options.

17.12 Unused Suppressions

While using PC-lint Plus for a long running project, suppression options may accumulate over time. It is natural to wonder if all of those options are still relevant. The code, parameterized type, module, etc. which prompted the suppression may no longer be present, or may now comply with the suppressed message. Identifying and removing unused or redundant suppressions may reduce the complexity of reading the static analysis configuration and can help with the understanding of what each suppression applies to.

Using the output of the Diagnostic Accounting feature, the usefulness of suppression options can be determined. PC-lint Plus provides a script named unused.py in the extras/ directory of the PC-lint Plus distribution which detects unused and redundant suppression options.

17.12.1 Prerequisites

Refer to the 17.11 Diagnostic Accounting section to gather the diagnostic accounting output.

17.12.2 Optional Dependencies

These are python packages that may be installed to add functionality to the script.

Package

Effect

tqdm

Displays a progress bar while the script is running.

ijson

Greatly reduces the peak memory (RAM) required to run the script, at the cost of slightly slower processing times.

pyzstd

Allows processing compressed *.zst files without having to decompress them first.

17.12.3 Usage

The script must be provided with at least one set of Diagnostic Accounting data. The –diagnostic-data argument tells the script where to locate the data, and how to interpret the paths specified within. It receives seven arguments, which are the following Diagnostic Accounting JSON files in order, Options, Diagnostics, Suppressions, Orbits, Metadata; then followed by the project directory, and a 0 or 1 depending on if the Diagnostic accounting run was on a Windows machine. The project directory is a directory where most modules are relative to, typically this is the current working directory of PC-lint Plus when run.

Multiple sets of Diagnostic Accounting data for the same project can be provided to the script at once by adding another –diagnostic-data option and its arguments. This can create quite a long command line, therefore arguments to the script can be passed as response files by using a @ prefix, then the file name. Comments starting with # are ignored until the end of line, an example of such a file would be as follows.


Listing 1: config.txt
# Diagnostic Data Input 
 
--diagnostic-data 
               "json/options.json" 
               "json/messages.json.zst" 
               "json/suppressions.json" 
               "json/orbits.json.zst" 
               "json/metadata.json" 
               "/home/user/my_project/" 
               "0"

With the command invocation consisting of

python unused.py @config.txt

Where config.txt is a text file in the same directory as unused.py with the contents of the Listing 1 .

After processing time, the unused, overlapping, and redundant suppression options will be output in as <file>:<line>:<column>: <option> [Patterns = <patterns>]. Where <file> is the location of the option, which may be the command line as signified by <command line option>, <line> is the line number of the option, <column> is the column of the start of the option block, <option> is the full option text, and <patterns> is an optional comma separated list of patterns that are unused in the case of a parameterized suppressions with multiple patterns that all aren’t unused. This output format is equivalent to the vim errorfmt %f:%l:%c: %m.

17.12.4 Output Categories

There are three output categories. Unused or Impotent options are options which either never apply to or vote in any potential message emission. Overlapping overriders are options, such as single line suppressions, that override all other suppressions, but are duplicated for a given encounter. Those should be rare. Redundant options are a set of options that can be removed without affecting any message emission outcome. Consider that there are many possible sets of redundant options for a given run of PC-lint Plus, and this script tries to find the largest set and report those.

17.12.5 Suppressing Suppressions

Sometimes certain suppression options are added to a project that only apply to a certain build target or build configuration. This may look like a false positive for an unused suppression if the Diagnostic Accounting data was run over the project with a different build configuration than the one the suppression was intended for. In order to prevent this, the script can receive multiple diagnostic accounting –diagnostic-data options, each with its own arguments containing diagnostic accounting files corresponding to different runs with different build configurations. When this occurs the results from each set of data is combined to remove suppressions that are used in at least one of the runs.

Another default that helps reduce the amount of unwanted reports is that any suppression option that is simply enabling a message without parameterization, such as +e , is considered to be used. Otherwise a preventative message such as 584 (trigraph sequence detected) would appear unused in a project that does not currently have any trigraphs. Presumably the point of a +e584 option would be to prevent any trigraphs from entering the project code, and it being unused is wanted. If this assumption does not hold for your project, the –report-unused-default-enables allows reversal of this assumption.

There is one other way to silence reported suppression options. Occasionally there will be some suppression options that are unwanted to be reported, either because they are false positives, used in a different build configuration, or from a third party library. The –freeze option can be passed to the script with five additional arguments to specify which suppression option the freeze applies to. The effect of freezeing suppression options is to consider these suppressions used and necessary. The implication of considering these suppressions necessary is that when processing overlapping or redundant options, any frozen options will not be considered for elimination, however their effects will be considered. This means that you can make the script ‘reconsider’ its choices of redundant options to present, by freezing some of them. If the proposed options to eliminate are desired to stay, freezing some of them may produce a different set of redundant options instead.

The five arguments to –freeze are in order, a file path glob, an option text regex, a suppression pattern regex, a line number, and column number. The suppression pattern is matched against the patterns present in a parameterized suppression options such as -esym . In order for a –freeze option to affect a given suppression option, all provided arguments must match. An empty string passed as an argument will be considered to apply to all suppression options, and can be used to indicate a field doesn’t matter. An example in the format of a response file is listed below,

# Frozen suppressions specification file 
       # Path                   # Option Text   # Pattern   # Line # Col 
--freeze "*co-gcc-c++17.lnt"        ""            ""         ""      "" 
--freeze "**/third_party/**"        ""            ""         ""      "" 
--freeze "OneFile.cpp"             "\-fallthrough"  ""         "421"   "17" 
--freeze ""                      "\-esym.*"      "MyClass"   ""      ""

17.12.6 Peak Memory Usage

This script processes a lot of data, as can be seen from the disk space used by the input files. All of this data needs to reside in system memory for processing, so the risk of running out of RAM or swap is present. There are some measures that can be taken to reduce the peak memory usage. The easiest measure is to install the ijson python package for the script to use, which allows the processing of the json data to occur without loading the entire file’s data into memory at once.

If multiple –diagnostic-data arguments are passed to the script at once, the default behavior is to spawn multiple processes to read the files concurrently, up to the number of cores on the system. The –concurrent-processes argument can be provided to limit the number processes spawned for reading the files, this can lower peak memory usage, which usually occurs during the file read processing. Note that if the progress bar library tqdm is installed, a process will be spawned to update the terminal progress bars which does not count towards the amount specific by the argument to the script.

A very drastic measure to reduce the peak memory usage is the pass the –only-unused-and-impotent argument which greatly reduces peak memory usage, typically down to megabytes when used in conjunction with ijson. However the effect of doing so means that redundant and overlapping suppression options cannot be calculated, and won’t be reported. Note that this also reduces processing time since the script can skip loading the large Diagnostics file.