Jon is a professional programmer with experience in medical imaging processing, biotech robotics, embedded software, and interpreters. He can be reached at [email protected].
PC-Lint, from Gimpel Software (http://www.gimpel.com/), is a source-code checker that finds bugs, glitches, inconsistencies, nonportable constructs, redundant code, and other such anomalies in C/C++ code. I've used PC-Lint on projects ranging from 100,000 to 200,000 lines of C++ source code and have always run across bugs that otherwise would have been extremely hard to find and fixsprintf formatting-type errors, uninitialized variables, division-by-zero, case statement fall-through, buffer overruns, and the like.
As you may expect, however, the number of messages that PC-Lint generates from large projects can be overwhelming. Clearly, PC-Lint would be even more useful if it had better report facilities, especially for linting entire projectsand most especially for linting projects for the first time. An extended reporting tool would help in going through generated messages in a scheduled, prioritized manner.
To that end, in this article I present a report generator that reads in all the PC-Lint messages generated from all the files in the project, and then displays them organized into user-specified categories. This gives you an overview of the state of the project, and helps you decide which are the most important messages to deal with first. It also encourages the use of PC-Lint for project management.
The report generator consists of a C++ program and several batch files (available electronically; see "Resource Center," page 5) to help integrate it into Microsoft's DevStudio. I've used these programs on Windows 98/2000/XP. Although I still mostly use DevStudio 6, I've also used them with DevStudio 7. While I've oriented the descriptions toward Microsoft MFC DevStudio, the programs are simple C-type command-line code that should easily port to other development environments. The report generator works on either a single C++ file or all C++ files in a directory. The report generator can be easily executed from DevStudio. The report output is sent to DevStudio's output window pane, making it convenient to use. Double clicking on a message brings up the source-code line in DevStudio.
Listing One is a simple program with errors, and Listing Two is excerpted output from the report generator based on Listing One. It includes several typical programming errors/warnings, such as:
- Formatting errors in sprintf.
- Unreachable code.
- Out-of-bounds access.
- Using uninitialized variables.
- Unintended fall-through in a switch statement.
- Using goto.
- If statement always evaluates True.
- Pointer errors.
The first time you use PC-Lint, it generates far too many messages to handle in one sitting. Consequently, I've developed a process flow (Figure 1) to help you review messages in multiple debug sessions.
Start by installing and configuring PC-Lint for your project, then run PC-Lint on your project. Using the methods described here, messages can then be generated for all .c/.cpp files in the project.
After running the program, messages from all files are automatically sorted by message number. This lets you scan the messages and determine which messages you'd like to work on first. For example, you might want to look at all uninitialized variables first, as this is a common source of programming errors. Another group of key messages are errors in sprintf-format statements. PC-Lint does a great job in syntax checking all the sprintf formatting errors, which is good for legacy code.
I recommend grouping the messages into the following categories:
- Critical errors, which should be looked into in detail. They are probably causing memory overwrites, uninitialized variables, sprintf-formatting errors, unintentional case statement fall-throughs, and miscasting.
- Less-critical errors, which can be scheduled for code cleanup; for example, gotos and making parameters const.
- Noise messages, which are hidden in all the noise.
Deal with the critical messages first. I've found it helpful to schedule the less-critical errors into the project schedule. Noise messages can be eliminated by using the PC-Lint message commands in the configuration (.lnt) file. Then iterate this process as needed.
Noise Messages
Noise messages are the huge number of miscellaneous messages generated by PC-Lint in legacy projects that could be meaningful in another project, but you have decided can be safely ignored in the current one. I've found that a lot of these are signed/unsigned match, loss of precision, and the like. When using PC-Lint with new code, I do pay attention to these messages.
Examples of turning off specific messages include:
- MACRO. I usually turn these off in the lintCPP.lnt file; emacro(506, TRACE) // Warning 506: Constant value Boolean turns off message 506 for the MFC macro "TRACE".
- Routine. I usually turn these off in the lintCPP.lnt file; esym(534,close,create, fclose,fprintf,fputc) // The return values of these routines are commonly ignored turns off message 534.
- Library include file. I usually turn these off in the lintCPP.lnt file; elib(652) turns off message 652.
- File. I use this when I want to turn off a message in source code; for example, /*lint -sem(CDNMCompute::ProcessError, s3) */ // doesn't return, throws exception.
- Specific source-code line. For example, /*lint -e825*/ // allow case fall-thru without warning.
PC-Lint
Since PC-Lint is a DOS-type program, it doesn't require DLLs or registry entries. All examples I present here assume that PC-Lint is installed in the c:\lint directory. Included electronically is the source code for the command-line programs jzLintReport.exe and EchoAndSave.exe, along with the batch files LintAllCPP.bat, LintAllCPPHelper.bat, LintCPP.bat, and DisplayFileToOutputPane.bat. Other files include Cr.txt, LintCPP.lnt, and PC-Lint Header.h.
Although PC-Lint provides several configurations (.lnt), you will want to use your own. I call mine "LintCPP.lnt" (available electronically) and use that filename in my examples. LintCPP.lnt is an input to PC-Lint that contains the configuration information PC-Lint needs to successfully process your project:
- Lists of paths to search for the include files.
- Lists of predefined MACROs.
- Lists of messages to ignore in the include files. (This is important. Without this, MFC header files generate lots of errors.)
- Lists of messages to ignore for all your project files.
- Lists of messages to ignore for specific MACROs.
- Lists of messages to ignore for specific routines.
- Lists of special attributes for specific routines. This makes it possible to tell PC-Lint that some routines are similar to sprintf or exit. For example, I commonly use a routine called "csFMT" that returns a Cstring, given sprintf-type inputs. This way, PC-Lint performs format checking on the parameters (see Listing One).
- Tells PC-Lint how to format the messages. By properly formatting the messages, PC-Lint output can be sent to the DevStudio output pane, and you can navigate to the source code by clicking on PC-Lint messages.
To install my report generator, go to the DevStudio Tools menu, select Customize, and click on the Tools tab. For illustration, I define three new tools: Lint This File, Lint All Files, and Display File To Output Pane. Figure 2 shows how to set up the Lint This File tool; the other two tools are similar. (More detailed installation instructions for DevStudio are available electronically.)
Running PC-Lint with my report generator from DevStudio is straightforward. For a single file, for instance, just select and open the file. Use DevStudio's Tools menu to select the menu item Lint This File. Output messages should start appearing in the output pane window. Sample output of file "Sample lint output.txt" is available electronically. To keep the size manageable, I deleted the first part, which is the raw file-by-file output from PC-Lint. The second part contains the output from the report generator. Messages are sorted by message number. This normally appears in the DevStudio output window, so you would be able to click on the Message and DevStudio would navigate you to the source-code line.
You can also cut the PC-Lint output from the output window pane in DevStudio and paste it into a new file. Later, you can open the file and redisplay it in the output pane using the Display File to Output Pane tool.
Enhancements
Since different parts of the source code are sensitive to different types of programming styles, it would be nice if the program allowed the user to determine which messages were more important on a file-by-file basis. For example, this would allow relaxation of the signed/unsigned messages for GUI event handling, while having them at a higher priority for math intensive routines.
This could be accomplished by having the report generator read in a file containing a list of file names and the category file to use. The report generator would then parse the file name from the PC-Lint message and use the definitions from the associated category definition file.
Another method would have the report generator read the source-code file itself, parsing it for directives specifying the category definition file to use. Then, when the report generator parses the PC-Lint message, it would use the line number to determine which category file to use. This would allow more flexibility; however, it would take longer for the report generator to execute.
DDJ
Listing One
#include <stdio.h> // This is a short file to show some examples of PC-Lint error/warning // detection and reporting of common errors /*lint +e716*/ #define MAX_BUF ( 1024 ) void main() { char buf[MAX_BUF]; int i; for (i = 0; i < MAX_BUF; i++) { buf[i+1] = 0; // Warning 661: Possible access of out-of-bounds pointer } char sbuf[1024]; float fVal; float fMax = 100.0; int nCount = 0; fVal = fMax/nCount; // Warning 414: Possible division by 0 // sprintf(sbuf, "the answer is: %d", fVal); // Warning 559: Size of argument no. 3 inconsistent with // format sprintf(sbuf, "the answer is: %g for %d", fVal); // Warning 558: Too few arguments for format (1 missing) // int nRet; int nTest = nRet * 10; // Warning 530:Symbol 'nRet' (line 28) not initialized int nFoo;switch (nTest) { case 1: nFoo = 1; break; case 2: nFoo = 21; case 3: // Info 825: control flows into case/default // without -fallthru comment nFoo = 321; break; } int nFooVal = nFoo; // Warning 644: Variable 'nFoo' (line 31) may not have // been initialized int nTest2 = 0; for(;;) { nTest2++; if (nTest2 > nFoo) goto done; // Info 801: Use of goto is deprecated } done: unsigned int nTest3 = 1; while (1) // Info 716: while(1) ... { nTest3 = nTest3 << 1; } //Warning 527: Unreachable int nTest4 = 0; if (nTest4 == 0) // Info 774: Boolean within 'if' always evaluates to True { } float *fTest; int *Int; pInt = (int *) &fTest; // Info 740: Unusual pointer cast (incompatible indirect types) // int *pInt; unsigned char nByte; *pInt = (int *) &nByte; // Info 826: Suspicious pointer-to-pointer conversion (area too small) }
Listing Two
C:\Projects\jzLintReport>echo off Current date is Fri 10-25-2002 Enter new date (mm-dd-yy): Current time is 3:12:30.06p Enter new time: --- Module: C:\Projects\jzLintReport\example.cpp _ buf[i+1] = 0; // Warning 661: Possible access of out-of-bounds pointer C:\Projects\jzLintReport\example.cpp(18) : Warning 661: Possible access of out-of-bounds pointer (1 beyond end of data) by operator '[' C:\Projects\jzLintReport\example.cpp(16) : Info 831: Reference cited in prior message C:\Projects\jzLintReport\example.cpp(18) : Info 831: Reference cited in prior message _ fVal = fMax/nCount; // Warning 414: Possible division by 0 C:\Projects\jzLintReport\example.cpp(25) : Warning 414: Possible division by 0 C:\Projects\jzLintReport\example.cpp(24) : Info 831: Reference cited in prior message _ sprintf(sbuf, "the answer is: %d", fVal); // Warning 559: Size of argument no. 3 inconsistent with format C:\Projects\jzLintReport\example.cpp(26) : Warning 559: Size of argument no. 3 inconsistent with format _ sprintf(sbuf, "the answer is: %g for %d", fVal); // Warning 558: Too few arguments for format (1 missing) C:\Projects\jzLintReport\example.cpp(27) : Warning 558: Too few arguments for format (1 missing) _ int nTest = nRet * 10; // Warning 530: Symbol 'nRet' (line 28) not initialized C:\Projects\jzLintReport\example.cpp(30) : Warning 530: Symbol 'nRet' (line 29) not initialized _ case 3: // Info 825: control flows into case/default without -fallthrough comment C:\Projects\jzLintReport\example.cpp(40) : Info 825: control flows into case/default without -fallthrough comment _ int nFooVal = nFoo; // Warning 644: Variable 'nFoo' (line 31) may not have been initialized C:\Projects\jzLintReport\example.cpp(44) : Warning 644: Variable 'nFoo' (line 32) may not have been initialized _ goto done; // Info 801: Use of goto is deprecated C:\Projects\jzLintReport\example.cpp(52) : Info 801: Use of goto is deprecated _ while (1) // Info 716: while(1) ... C:\Projects\jzLintReport\example.cpp(57) : Info 716: while(1) ... _ int nTest4 = 0; C:\Projects\jzLintReport\example.cpp(64) : Warning 527: Unreachable _ if (nTest4 == 0) // Info 774: Boolean within 'if' always evaluates to True C:\Projects\jzLintReport\example.cpp(65) : Info 774: Boolean within 'if' always evaluates to True C:\Projects\jzLintReport\example.cpp(64) : Info 831: Reference cited in prior message C:\Projects\jzLintReport\example.cpp(65) : Info 831: Reference cited in prior message _ pInt = (int *) &fTest; // Info 740: Unusual pointer cast (incompatible indirect types) C:\Projects\jzLintReport\example.cpp(71) : Error 40: Undeclared identifier 'pInt' C:\Projects\jzLintReport\example.cpp(71) : Info 740: Unusual pointer cast (incompatible indirect types) C:\Projects\jzLintReport\example.cpp(71) : Error 63: Expected an lvalue _ *pInt = (int *) &nByte; // Info 826: Suspicious pointer-to-pointer conversion (area too small) C:\Projects\jzLintReport\example.cpp(75) : Error 64: Type mismatch (assignment) (int = pointer) C:\Projects\jzLintReport\example.cpp(75) : Info 826: Suspicious pointer-to-pointer conversion (area too small) C:\Projects\jzLintReport\example.cpp(75) : Warning 530: Symbol 'pInt' (line 73) not initialized ******************************************************** * Lint Error/Warning messages follow * ********************************************************* ********** Category: OTHER - Messages that haven't been defined in a category C:\Projects\jzLintReport\example.cpp(71) : Error 40: Undeclared identifier 'pInt' C:\Projects\jzLintReport\example.cpp(71) : Error 63: Expected an lvalue C:\Projects\jzLintReport\example.cpp(75) : Error 64: Type mismatch (assignment) (int = pointer) C:\Projects\jzLintReport\example.cpp(25) : Warning 414: Possible division by 0 C:\Projects\jzLintReport\example.cpp(64) : Warning 527: Unreachable C:\Projects\jzLintReport\example.cpp(30) : Warning 530: Symbol 'nRet' (line 29) not initialized C:\Projects\jzLintReport\example.cpp(75) : Warning 530: Symbol 'pInt' (line 73) not initialized C:\Projects\jzLintReport\example.cpp(44) : Warning 644: Variable 'nFoo' (line 32) may not have been initialized C:\Projects\jzLintReport\example.cpp(18) : Warning 661: Possible access of out-of-bounds pointer (1 beyond end of data) by operator '[' C:\Projects\jzLintReport\example.cpp(57) : Info 716: while(1) ... C:\Projects\jzLintReport\example.cpp(65) : Info 774: Boolean within 'if' always evaluates to True C:\Projects\jzLintReport\example.cpp(52) : Info 801: Use of goto is deprecated C:\Projects\jzLintReport\example.cpp(40) : Info 825: control flows into case/default without -fallthrough comment ********** Category: Critical Errors C:\Projects\jzLintReport\example.cpp(27) : Warning 558: Too few arguments for format (1 missing) C:\Projects\jzLintReport\example.cpp(26) : Warning 559: Size of argument no. 3 inconsistent with format C:\Projects\jzLintReport\example.cpp(71) : Info 740: Unusual pointer cast (incompatible indirect types) C:\Projects\jzLintReport\example.cpp(75) : Info 826: Suspicious pointer-to-pointer conversion (area too small) ********** Category: Less Critical Errors No messages have been defined for this category ********** Category: Noise Messages No messages have been defined for this category Total Files Processed: 1 Total Files With Messages: 1 Total Messages Processed: 17 Current date is Fri 10-25-2002 Enter new date (mm-dd-yy): Current time is 3:12:31.81p Enter new time: C:\Projects\jzLintReport>rem ************ Done ************** C:\Projects\jzLintReport> C:\Projects\jzLintReport> C:\Projects\jzLintReport> C:\Projects\jzLintReport> Tool returned code: 0