|
The Assert Remedy
Value Tracking was introduced with Version 7.0. By value tracking we mean that
some information is retained about automatic variables (and about data
members of the this class for a member function) across statements
in a fashion similar to what is retained about the state of initialization.
(See Possibly Uninitalized). Consider a
simple example:
int a[10];
int f()
{
int k;
k = 10;
return a[k]; // Warning 415
}
This will result in the diagnostic message Warning 415 (access of out-of-bounds
pointer by operator '[') because the value assigned to k
is retained by PC-lint/FlexeLint and used to decide on the worthiness of
the subscript.
If we were to change things slightly to:
int a[10];
int f( int n )
{
int k;
if ( n ) k = 10;
else k = 0;
return a[k]; // Warning 661
}
we would obtain Warning 661 (Possible access of out-of-bounds pointer).
The word 'possible' is used because not all of the paths leading to the
reference a[k] would have assigned 10 to k.
Information is gleaned not only from assignment statements and initializations
but also from conditionals. For example:
int a[10];
int f( int k, int n )
{
if ( k >= 10 ) a[0] = n;
return a[k]; // Warning 661 -- k could be 10
}
also produces Warning 661 based on the fact that k was tested
for being greater than or equal to 10 prior to its use as a subscript.
Otherwise, the presumption is that k is OK, i.e. the programmer
knew what he or she was doing. Thus the following:
int a[10];
int f( int k, int n )
{ return a[k+n]; } // no warning
produces no diagnostic.
Just as it is possible for a variable to be conceivably uninitialized
(see Possibly Uninitalized). it is possible
for a variable to conceivably have a bad value. For example, if the loop
in the example below is taken 0 times, k could conceivably be out-of-range.
The message given is Informational 796 (Conceivable access of out-of-bounds
pointer).
int a[10];
int f(int n, int k)
{
int m = 2;
if( k >= 10 ) m++; // Hmm -- So k could be 10, eh?
while( n-- )
{ m++; k = 0; }
return a[k]; // Info 796 - - k could still be 10
}
In addition to reporting on the access of out-of-bounds subscripts (messages
415, 661, 796), value tracking allows us to give similar messages for the
division by 0 (414, 414, 795), inappropriate uses of the NULL pointer (413,
613, 794), the creation of illegal pointers (416, 662, 797) and the detection
of redundant Boolean tests (774), as the following examples show.
Value tracking allows us to diagnose the possible use of the NULL pointer.
For example:
int *f( int *p )
{
if ( p ) printf( "\n" ); // So -- p could be NULL
printf( "%d", *p ); // Warning
return p + 2; // Warning
}
will receive a diagnostic for the possible use of a NULL pointer in
both the indirect reference (*p) and in the addition of 2 (p+2).
Clearly both of these statements should have been within the scope of the
if.
To create truly bullet-proof software you may turn on the Pointer-parameter-may-be-NULL
flag (+fpn). This will assume the possibility that all pointer parameters
to any function may be NULL.
Bounds checking is done only on the high side. That is:
int a[10]; ... a[10] = 0;
is diagnosed but a[-1] is not.
There are two sets of messages associated with out-of-bounds checking.
The first is the creation of an out-of-bounds pointer and the second is
the access of an out-of-bounds pointer. By "access" we mean retrieving
a value through the pointer. In ANSI C (3.3.6) you are allowed to create
a pointer that points to one beyond the end of an array. For example:
int a[10];
f( a + 10 ); // OK
f( a + 11 ); // error
But in neither case can you access such a pointer. For example:
int a[10], *p, *q;
p = a + 10; // OK
*p = 0; // Warning (access error)
p[-1] = 0; // No Warning
q = p + 1; // Warning (creation error)
q[0] = 0; // Warning (access error)
As indicated earlier, we do not check on subscripts being effectively
negative. We check only on the high side of an array.
Though not as critical as pointer checking, tracking values allows us
to report that a Boolean condition will always be true (or false). Thus
if ( n > 0 ) n = 0;
else if ( n <= 0 ) n = -1; // Info 774
results in the Informational message (774) that the second test can
be ignored. Such redundant tests are usually benign but they can be a symptom
of faulty logic and deserve careful scrutiny.
The assert() remedy
It is possible to obtain spurious messages (false hits) that can be
remedied by the judicious use of assert. For example
char buf[4];
char *p;
strcpy( buf, "a" );
p = buf + strlen( buf ); // p is 'possibly' (buf+3)
p++; // p is 'possibly' (buf+4)
*p = 'a'; // Warning 661 - possible out-of-bounds reference
PC-lint/FlexeLint is not aware in all cases what the true values of
variables are. In the above case, a warning is issued where there is no
real danger. You can inhibit this message directly by using
*p = 'a'; //lint !e661
Alternatively, you can use your compiler's assert facility as
in the following:
#include <assert.h>
.
.
.
char buf[4];
char *p;
strcpy( buf, "a" );
p = buf + strlen( buf );
assert( p < buf + 3 ); // p is 'possibly' (buf+2)
p++; // p is 'possibly' (buf+3)
*p = 'a'; // no problem
Please note that assert's will become no-op's if NDEBUG
is defined so make sure this is not defined for linting purposes.
In order for the assert() facility to have this effect with your
compiler's assert.h it may be necessary to include an option within
your compiler options file. For example, suppose that the assert
facility is implemented via a macro defined by your compiler as
#define assert(p) ((p) ? (void)0 : __A(...))
Presumably __A() issues a message and doesn't return. It would
then be necessary to give the option
-function( exit, __A )
which transfers the no-return property of exit to the __A
function (see Function Mimicry).
Alternatively, your compiler may implement assert as a function.
For example:
#define assert(k) _Assert(k,...)
To let PC-lint/FlexeLint know that _Assert is the assert
function you may copy the semantics of the __assert() function (defined
in Function Mimicry) using the option
-function( __assert, _Assert )
or
-function( __assert(1), _Assert(1) )
The latter form can be employed for arguments other than the first.
For many of the major compilers on MS-DOS, the appropriate options have
already been placed in the appropriate compiler options file.
In the event that there is no obvious solution that you can employ with
your compiler's assert.h, you can always place an assert.h
in your PC-lint/FlexeLint directory (where presumably file searching is
directed with a -i option and takes precedence over your compiler's
header files).
|