10. Other Checks

The section describes other errors detected by LCLint that are not directly related to extra information provided in annotations. Many of the checks are significantly improved, however, because of the extra information that is known about the program.

10.1 Undefined Evaluation Order

The order in which side effects take place in a C program is not entirely defined by the code. Certain execution points are known as sequence points -- a function call (after the arguments have been evaluated), the end of a full expression (an initializer, expression in an expression statement, the control expression of an if, switch, while or do statement, each expression of a for statement, and the expression in a return statement), and after the first operand or a &&, ||, ? or , operand.

All side effects before a sequence point must be complete before the sequence point, and no evaluations after the sequence point shall have taken place [ANSI, Section]. Between sequence points, side effects and evaluations may take place in any order. Hence, the order in which expressions or arguments are evaluated is not specified. Compilers are free to evaluate function arguments and parts of expressions (that do not contain sequence points) in any order. The behavior of code that uses a value that is modified by another expression that is not required to be evaluated before or after the other use is undefined.

LCLint detects instances where undetermined order of evaluation produces undefined behavior. If modifies clauses and globals lists are used, this checking is enabled in expressions involving function calls. Evaluation order checking is controlled by the evalorder flag.

When checking systems without modifies and globals information, evaluation order checking may report errors when unconstrained functions are called in procedure arguments. Since LCLint has no annotations to constrain what these functions may modify, it cannot be guaranteed that the evaluation order is defined if another argument calls an unconstrained function or uses a global variable or storage reachable from a parameter to the unconstrained function. Its best to add modifies and globals clauses to constrain the unconstrained functions in ways that eliminate the possibility of undefined behavior. For large legacy systems, this may require too much effort. Instead, the -evalorderuncon flag may be used to prevent reporting of undefined behavior due to the order of evaluation of unconstrained functions.

Figure 19. Evaluation order

10.2 Problematic Control Structures

A number of control structures that are syntactically legal may indicate likely bugs in programs. LCLint can detect errors involving likely infinite loops (Section 10.2.1), fall through cases and missing cases in switch statements (Section 10.2.2), break statements within deeply nested loops or switches (Section 10.2.3), clauses of if, while or for statements that are empty statements or unblocked single statements (Section 10.2.4) and incomplete if-else logic (Section 10.2.5). Although any of these may appear in a correct program, depending on the programming style used they may indicate likely bugs or style violations that should be detected and eliminated.

10.2.1 Likely Infinite Loops

LCLint reports an error if it detects a loop that appears to be infinite. An error is reported for a loop which does not modify any value used in its condition test inside the body of the loop or in the condition test itself. This checking is enhanced by modifies clauses and globals lists since they provide more information about what global variable may be used in the condition test and what values may be modified by function calls in the loop body.

Figure 20 shows examples of infinite loops detected by LCLint. An error is reported for the loop in line 14, since neither of the values used in the loop condition (x directly and glob1 through the call to f) is modified by the body of the loop. If the declaration of g is changed to include glob1 in the modifies clause no error is reported. (In this example, if we assume the annotations are correct, then the programmer has probably called the wrong function in the loop body. This isn't surprising, given the horrible choices of function and variable names!)

If an unconstrained function is called within the loop body, LCLint will assume that it modifies a value used in the condition test and not report an infinite loop error, unless infloopsuncon is on. If infloopsuncon is on, LCLint will report infinite loop errors for loops where there is no explicit modification of a value used in the condition test, but where they may be an undetected modification through a call to an unconstrained function (e.g., line 15 in Figure 20).

10.2.2 Switches

The automatic fall-through of C switch statements is almost never the intended behavior.[25] LCLint detects case statements with code that may fall through to the next case. The casebreak flag controls reporting of fall through cases. A single fall through case may be marked by preceding the case keyword with /*@fallthrough@*/ to indicate explicitly that execution falls through to this case.

For switches on enum types, LCLint reports an error if a member of the enumerator does not appear as a case in the switch body (and there is no default case). (Controlled by misscase.)

An example of switch checking is shown in Figure 21.

10.2.3 Deep Breaks

There is no syntax provided by C (other than goto) for breaking out of a nested loop. All break and continue statements act only on the innermost surrounding loop or switch. This often leads to serious problems[26] when a programmer intends to break the outer loop or switch instead. LCLint optionally reports errors for break and continue statements in nested contexts.

Since continue only makes sense within loops, errors are only reported for continue statements within nested loops. (Controlled by looploopcontinue.) A safe inner continue may be precede by /*@innercontinue@*/ to suppress error messages locally. The deepbreak flag sets all nested break and continue checking flags.

LCLint reports an error if the marker preceding a break is not consistent with its effect. An error is reported if innerbreak precedes a break that is not breaking an inner loop, switchbreak precedes a break that is not breaking a switch, or loopbreak precedes a break that is not breaking a loop.

10.2.4 Loop and If Bodies

An empty statement after an if, while or for often indicates a potential bug. A single statement (i.e., not a compound block) after an if, while or for is not likely to indicate a bug, but make the code harder to read and edit. LCLint can report errors for if or loop statements with empty bodies or bodies that are not compound statements. Separate flags control checking for statements following an if, while or for:

The if statement checks also apply to the body of the else clause. An ifblock error is not reported if the body of the else clause is an if statement, to allow else if chains.

10.2.5 Complete if-else Logic

Although it may be perfectly reasonable in many contexts, an if-else chain with no final else may indicate missing logic or forgetting to check error cases. If elseifcomplete is on, LCLint reports errors when an if statement that is the body of an else clause does not have a matching else clause. For example, the code,
   if (x == 0) { return "nil"; }
   else if (x == 1) { return "many"; }
produces an error message since the second if has no matching else branch.

10.3 Suspicious Statements

LCLint detects errors involving statements with no apparent effects (Section 10.3.1) and statements that ignore the result of a called function (Section 10.3.2).

10.3.1 Statements with No Effects

LCLint can report errors for statements that have no effect. (Controlled by noeffect.) Because of modifies clauses, LCLint can detect more errors than traditional checkers. Unless the noeffectuncon flag is on, errors are not reported for statements that involve calls to unconstrained functions since the unconstrained function may cause a modification.

Figure 22. Statements with no effect.

10.3.2 Ignored Return Values

LCLint reports an error when a return value is ignored. Checking may be controlled based on the type of the return value: retvalint controls reporting of ignored return values of type int, and retvalbool for return values of type bool, and retvalothers for all other types. A function statement may be cast to void to prevent this error from being reported.

Alternate types (Section 8.2.2) can be used to declare functions that return values that may safely be ignored by declaring the result type to alternately by void. Several functions in the standard library are specified to alternately return void to prevent ignored return value errors for standard library functions (e.g., strcpy) where the result may be safely ignored (see Apppendix F).

Figure 23 shows example of ignored return value errors reported by LCLint.

10.4 Unused Declarations

LCLint detects constants, functions, parameters, variables, types, enumerator members, and structure or union fields that are declared but never used. The flags constuse, fcnuse, paramuse, varuse, typeuse, enummemuse and fielduse control whether unused declaration errors are reported for each kind of declaration. Errors for exported declarations are reported only if topuse is on (see Section 10.5).

The /*@unused@*/ annotation can be used before a declaration to indicate that the item declared need not be used. Unused declaration errors are not reported for identifiers declared with unused.

10.5 Complete Programs

LCLint can be used on both complete and partial programs. When checking complete programs, additional checks can be done to ensure that every identifier declared by the program is defined and used, and that functions that do not need to be exported are declared static.

LCLint checks that all declared variables and functions are defined (controlled by compdef). Declarations of functions and variables that are defined in an external library, may be preceded by /*@external@*/ to suppress undefined declaration errors.

LCLint reports external declarations which are unused (Controlled by topuse). Which declarations are reported also depends on the declaration use flags (see Section 10.4).

The partial flag sets flags for checking a partial system. Top-level unused declarations, undefined declarations, and unnecessary external names are not reported if partial is set.

10.5.1 Unnecessary External Names

LCLint can report variables and functions that are declared with global scope (i.e., without using static), that are not used outside the file in which they are defined. In a stand-alone system, these identifiers should usually be declared using static to limit their scope. If the exportstatic flag is on, LCLint will report declarations that could have file scope. It should only be used when all relevant source files are listed on the LCLint command line; otherwise, variables and functions may be incorrectly identified as only used in the file scope since LCLint did not process the other file in which they are used.

10.5.2 Declarations Missing from Headers

A common practice in C programming styles, is that every function or variable exported by M.c is declared in M.h. If the exportheader flag is on, LCLint will report exported declarations in M.c that are not declared in M.h.

10.6 Compiler Limits

The ANSI Standard includes limits on minimum numbers that a conforming compiler must support. Whether of not a particular compiler exceeds these limits, it is worth checking that a program does not exceed them so that it may be safely compiled by other compilers. In addition, exceeding a limit may indicate a problem in the code (e.g., it is too complex if the control nest depth limit is exceeded) that should be fixed regardless of the compiler. The following limits are checked by LCLint. For each limit, the maximum value may be set from the command line (or locally using a stylized comment). If the ansilimits flag is on, all limits are checked with the minimum values of a conforming compiler.


Maximum nesting depth of file inclusion (#include). (ANSI minimum is 8)
Maximum nesting of compound statements, control structures. (ANSI minimum is 15)
Number of members in an enum declaration. (ANSI minimum is 127)
Number of fields in a struct or union declaration. (ANSI minimum is 127)

Since human beings themselves are not fully debugged yet, there will be bugs in your code no matter what you do.
- Chris Mason, Zero-defects memo (Microsoft Secrets, Cusumano and Selby)

Next: Appendix A: Availability