| Revision History | |
|---|---|
| Revision 1.01 | October 18th, 2005 |
| Fixed markup problems | |
| Revision 1.0 | August 30th, 2005 |
| Completed tutorial for presentation | |
| Revision 0.1 | July 27th, 2005 |
| Initial Draft | |
Abstract
This tutorial will show you how to use Splint, a C Source code analysis tool.
Table of Contents
Comments on this Guide may be directed to Michael McCabe (<mccabemt@clarkson.edu>).
This document, Using Static Analysis Tools, is copyright (c) 2005 by the Clarkson Open Source Institute. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is available at http://www.gnu.org /copyleft/fdl.html.
No liability for the contents of this document can be accepted. Use the concepts, examples and information at your own risk. There may be errors and inaccuracies, that could be damaging to your system. Proceed with caution, and although this is highly unlikely, the author(s) do not take any responsibility.
All copyrights are held by their by their respective owners, unless specifically noted otherwise. Use of a term in this document should not be regarded as affecting the validity of any trademark or service mark. Naming of particular products or brands should not be seen as endorsements.
This guide will show you how to use a very powerfull tool Splint. Splint is a C source code analysis tool that can help you find bugs that will crash your program further on down the road. This technique is called static code analysis is very usefull especially in situations where you do not have access to a debugger (at the kernel level).
Here's a brief list of just some of the problem areas the Splint looks for and can help identify problems with. Some of these are the standard lint checks and some are unique to Splint.
Unused Declarations
Unreachable Code
Deferencing a Null Pointer
Type mismatches
Memory leaks
Likely infinite loops
Buffer overflow issues
Installing Splint is very easy. If you are running Fedora Core 4 you can download the RPM from the Fedora Extras Repository or from my webpage. If not you can download the source code from the Splint Homepage. I recommend the source code instead of the binary that they provide on the Splint page because you won't run into any wierd dependency issues that may have arisen due to the way that they built their package, or the libraries that they linked to. The source code has a standard Unix style configure script and makefile.
For this tutorial I've written a file called badcode.c
. This is an example of what not to do in C programs, but problems like
these may creep into programs over a long period of time. I have written to show
some of the example output of Splint. Here is the example output from Splint when
it is run on badcode.c.
Example 1. Running Splint on badcode.c
Splint 3.1.1 --- 27 Jul 2005
badcode.c: (in function undefined_parameter)
badcode.c:9:17: Format argument 1 to printf (%d) expects int gets int *:
pointer
Type of parameter is not consistent with corresponding code in format string.
(Use -formattype to inhibit warning)
badcode.c:9:11: Corresponding format code
badcode.c: (in function main)
badcode.c:13:2: Variable c used before definition
An rvalue is used that may not be initialized to a value on some execution
path. (Use -usedef to inhibit warning)
badcode.c:15:22: Null storage passed as non-null param:
undefined_parameter (NULL)
A possibly null pointer is passed as a parameter corresponding to a formal
parameter with no /*@null@*/ annotation. If NULL may be used for this
parameter, add a /*@null@*/ annotation to the function parameter declaration.
(Use -nullpass to inhibit warning)
badcode.c:11:14: Parameter argc not used
A function parameter is not used in the body of the function. If the argument
is needed for type compatibility or future plans, use /*@unused@*/ in the
argument declaration. (Use -paramuse to inhibit warning)
badcode.c:11:27: Parameter argv not used
badcode.c:4:13: File static function unused_function declared but not used
A function is declared but not used. Use /*@unused@*/ in front of function
header to suppress message. (Use -fcnuse to inhibit warning)
badcode.c:7:1: Definition of unused_function
badcode.c:8:6: Function exported but not used outside badcode:
undefined_parameter
A declaration is exported, but not used outside this module. Declaration can
use static qualifier. (Use -exportlocal to inhibit warning)
badcode.c:10:1: Definition of undefined_parameter
Finished checking --- 7 code warnings
Some of the issues it has identified are just warnings that will not affect the execution of your code. One of these is the unused parameter warning. It has highlighted some large problems with the small segment of code, these include using a variable before it has a value defined, and passing a null parameter, where the function is expecting a non null parameter. If you executing this program the program will reference a null pointer and crash.
Now I've decided to show a real world example of using Splint. I have
decided to run Splint on a small portion of Xen. I
ran this on tools/xenstore/utils.c from an unstable snapshot
as of July 26th, 2005. Here is the output from running splint with no
command line options.
Example 2. utils.c
[mike@thebeast xenstore]$ splint utils.c
Splint 3.1.1 --- 27 Jul 2005
utils.c: (in function xprintf)
utils.c:22:2: Return value (type int) ignored: vfprintf(out, fm...
Result returned by function call is not used. If this is intended, can cast
result to (void) to eliminate message. (Use -retvalint to inhibit warning)
utils.c:24:2: Return value (type int) ignored: fflush(out)
utils.c: (in function barf)
utils.c:35:2: Unrecognized identifier: vasprintf
Identifier used in code has not been declared. (Use -unrecog to inhibit
warning)
utils.c:38:18: Variable str used before definition
An rvalue is used that may not be initialized to a value on some execution
path. (Use -usedef to inhibit warning)
utils.c:40:7: Argument to exit has implementation defined behavior: 1
The argument to exit should be 0, EXIT_SUCCESS or EXIT_FAILURE (Use -exitarg
to inhibit warning)
utils.c: (in function barf_perror)
utils.c:55:22: Variable str used before definition
utils.c:57:7: Argument to exit has implementation defined behavior: 1
utils.c: (in function _realloc_array)
utils.c:62:13: Unrecognized identifier: SIZE_MAX
utils.c:63:10: Null storage returned as non-null: NULL
Function returns a possibly null pointer, but is not declared using
/*@null@*/ annotation of result. If function may return NULL, add /*@null@*/
annotation to the return value declaration. (Use -nullret to inhibit warning)
utils.c: (in function realloc_nofail)
utils.c:69:16: Implicitly temp storage ptr passed as only param:
realloc (ptr, ...)
Temp storage (associated with a formal parameter) is transferred to a
non-temporary reference. The storage may be released or new aliases created.
(Use -temptrans to inhibit warning)
utils.c:73:2: Path with no return in function declared to return void *
There is a path through a function declared to return a value on which there
is no return statement. This means the execution may fall through without
returning a meaningful result to the caller. (Use -noret to inhibit warning)
utils.c: (in function malloc_nofail)
utils.c:79:10: Returned storage ptr not completely defined (*ptr is undefined):
ptr
Storage derivable from a parameter, return value or global is not defined.
Use /*@out@*/ to denote passed or returned storage which need not be defined.
(Use -compdef to inhibit warning)
utils.c:77:27: Storage *ptr allocated
utils.c:81:2: Path with no return in function declared to return void *
utils.c: (in function daemonize)
utils.c:94:2: Return value (type int) ignored: close(0)
utils.c:95:2: Return value (type int) ignored: close(1)
utils.c:96:2: Return value (type int) ignored: close(2)
utils.c:99:2: Return value (type __pid_t) ignored: setsid()
Result returned by function call is not used. If this is intended, can cast
result to (void) to eliminate message. (Use -retvalother to inhibit warning)
utils.c:101:2: Return value (type int) ignored: chdir("/")
utils.c:103:2: Return value (type __mode_t) ignored: umask(0)
utils.c: (in function grab_file)
utils.c:117:27: Null storage passed as non-null param: open (..., 0)
A possibly null pointer is passed as a parameter corresponding to a formal
parameter with no /*@null@*/ annotation. If NULL may be used for this
parameter, add a /*@null@*/ annotation to the function parameter declaration.
(Use -nullpass to inhibit warning)
utils.c:120:10: Null storage returned as non-null: NULL
utils.c:122:18: Function malloc expects arg 1 to be size_t gets unsigned int:
max + 1
To allow arbitrary integral types to match any integral type, use
+matchanyintegral.
utils.c:124:41: Function read expects arg 3 to be size_t gets unsigned long
int: max - *size
To allow arbitrary integral types to match long unsigned, use
+longunsignedintegral.
utils.c:124:25: Passed storage buffer not completely defined (*buffer is
undefined): read (..., buffer + *size, ...)
utils.c:122:2: Storage *buffer allocated
utils.c:124:10: Assignment of ssize_t to int:
ret = read(fd, buffer + *size, max - *size)
utils.c:127:29: Function realloc expects arg 2 to be size_t gets unsigned int:
max *= 2 + 1
utils.c:133:3: Index of possibly null pointer buffer: ((char *)buffer)
A possibly null pointer is dereferenced. Value is either the result of a
function which may return null (in which case, code should check it is not
null), or a global, parameter or structure field declared with the null
qualifier. (Use -nullderef to inhibit warning)
utils.c:122:11: Storage buffer may become null
utils.c:134:2: Return value (type int) ignored: close(fd)
utils.c:135:9: Possibly null storage buffer returned as non-null: buffer
utils.c:122:11: Storage buffer may become null
utils.c:135:9: Returned storage buffer not completely defined (*buffer is
undefined): buffer
utils.c:122:2: Storage *buffer allocated
utils.c: (in function release_file)
utils.c:140:7: Implicitly temp storage data passed as only param: free (data)
utils.c:138:45: Parameter size not used
A function parameter is not used in the body of the function. If the argument
is needed for type compatibility or future plans, use /*@unused@*/ in the
argument declaration. (Use -paramuse to inhibit warning)
utils.h:14:20: File static function strends declared but not used
A function is declared but not used. Use /*@unused@*/ in front of function
header to suppress message. (Use -fcnuse to inhibit warning)
utils.h:20:1: Definition of strends
utils.h:33:7: Function exported but not used outside utils: realloc_nofail
A declaration is exported, but not used outside this module. Declaration can
use static qualifier. (Use -exportlocal to inhibit warning)
utils.c:73:1: Definition of realloc_nofail
utils.h:36:6: Function exported but not used outside utils: barf
utils.c:41:1: Definition of barf
utils.h:37:6: Function exported but not used outside utils: barf_perror
utils.c:58:1: Definition of barf_perror
utils.h:50:6: Function exported but not used outside utils: xprintf
utils.c:25:1: Definition of xprintf
Finished checking --- 39 code warnings
As you can see in the previous example, there were many different warnings and some were probably false positives. This is because Splint doesn't and can't know everything that is going to be used. This is where you can use annotations and get some of the false positives to be turned off. This will help you because you'll only see the warnings that you create each time you update the code. More information on using Annotations can be found in the Splint manual.
There are many other static analysis tools that are available. They are all very usefull and will help you track down bugs before you release your software. One of the tools that I've used is Pylint. It's a static checking tool for Python that finds many common mistakes including those especially pesky indentation problems that occasionaly crop up when you've got more than one person working on a project.
Also Linus Torvalds has written a static analysis tool specifically for the Linux kernel. This was needed because of the different issues that you face when you are programming at kernel level. The tool is called sparse and it has been integrated into the Linux kernel build system. If you've got sparse installed and you would like to run that tests all you've got to do is run make C=1 or make C=2.
Here's a listing of some other usefull resources. Some of these are documentation for Splint and others are more static analysis tools.