Using Static Analysis Tools

Michael McCabe

Revision History
Revision 1.01October 18th, 2005
Fixed markup problems
Revision 1.0August 30th, 2005
Completed tutorial for presentation
Revision 0.1July 27th, 2005
Initial Draft

Abstract

This tutorial will show you how to use Splint, a C Source code analysis tool.


Table of Contents

Feedback
Copyright
Disclaimer
Introduction
Installing Splint
A Simple Example
Running this on the Xen Mini OS
Annotations
Other Static Tools
Other resources

Feedback

Comments on this Guide may be directed to Michael McCabe ().

Copyright

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.

Disclaimer

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.

Introduction

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

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.

A Simple Example

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.

Running this on the Xen Mini OS

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

Annotations

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.

Other Static Tools

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.

Other resources

Here's a listing of some other usefull resources. Some of these are documentation for Splint and others are more static analysis tools.