Gimpel Software
  Order        Patches        Discussion Forum        Blog 
Contact      Site Map       
   Home
   Bug of the Month
   Products
   Order
   Support
   Company
   Links
   Interactive Demo

PC-lint/FlexeLint Value Tracking

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).


Home | Contact | Order

PC-lint and FlexeLint are trademarks of Gimpel Software LLC
Copyright © 2015, Gimpel Software LLC, All rights reserved.