Search

ParaSoft

HOME
PRODUCTS
SUPPORT
ABOUT
WHAT'S NEW
EVENTS


Insure++

Quick facts

Add-on Modules:
   -INUSE
   -TCA

Comparisons

Technical Papers

Support & Manuals

FAQs

Recent Reviews

User Testimonials

Press Releases


Insure tool to debug c++





Insure++ User's Guide - Interfaces

Part I



Interfaces

The section "Insure++ Code Insertions", described a way of using Insure++ interface descriptions to add user level checking to function calls. This usage is only one of the things that interfaces can do to extend the capabilities of Insure++. This section describes the purpose of these interfaces in more detail and also shows you how to write your own.

What are interfaces for?

Interface descriptions provide an extremely powerful facility which allows you to perform extensive checking on functions in system or third party libraries before problems cause them to crash.

Most problems encountered in libraries are due to their being called incorrectly from the user application. Insure++ interfaces are designed to trap and diagnose errors where your code makes calls to these functions. This provides the most useful information for correcting the error.

Essentially, an interface is a means of enforcing rules on the way that a function can be called and the side-effects it has on memory. Typically, interfaces check that all parameters are of the correct type, that pointers point to memory blocks of the appropriate size, and that parameter values are in correct ranges. Whenever a function is expected to create or delete a block of dynamic memory, they also make calls that allow Insure++'s runtime library to update its internal records.

Writing interfaces for your libraries is a fairly simple task once the basic principles are understood. To help in relating the purpose of an interface to its implementation, the following sections describe two simple examples, one in C and one in C++.

A C example

Consider the following code, which makes a call to a hypothetical library function mymalloc. See the file mymal.c below for a definition of mymalloc.

1:  /*
  	2:   * File: mymaluse.c
  	3:   */
  	4:  main()
  	5:  {
  	6:      char *p, *mymalloc();
  	7:
  	8:      p = mymalloc(10);
  	9:      *p = 0;
  	10:	return (0); 
  	11: }

In order to get the best from Insure++, you need to summarize the expected behavior of the mymalloc function. For this example, let us assume that we want to enforce the following rules:

  • The single argument is an integer which must be positive.
  • The return value is a pointer to a block of memory which is allocated by the routine.
  • The size of the allocated block is equal to the supplied argument.

To do so, we create a file with the following interface


	1:  /*
	2:   * File: mymal_i.c
	3:   */
	4:  char *mymalloc(int n)
	5:  {
	6:       char *retp;
	7:       if(n <=  0) {
	8:            iic_error(USER_ERROR,
	9:                 "Negative argument: %d\n",n);
	10:      retp = mymalloc(n);
	11:      if(retp) iic_alloc(retp, n);
	12:      return retp;
	13:  }

The key features of this code are as follows:

Line 4 A standard ANSI function declaration for the function to be described, including its return type and arguments. (Old-style function declarations can also be used.)
Line 7 A check that the argument supplied is positive, as required by the rules that we are trying to enforce. If the condition fails, we use the special iic_error function to print an Insure++-style error message, using standard printf notation.
Line 10 This (apparently recursive) call to the mymalloc function is where the actual call to the function will be made when this interface is expanded. It appears just as in the function declaration.
Line 11 If the return value from the function call is not zero, we use the iic_alloc function to indicate that a block of uninitialized memory of the given size has been allocated and is pointed to by the pointer retp.
Line 12 The interface description ends by returning the same value returned from the call to the actual function in Line 10.

If you compile and link this interface description into your program (using the techniques described in "Using interfaces"), Insure++ will automatically check for all the requirements whenever you call the function.

	1:	/*
	2:	 * File: mymal.c
	3:	 */
	4:	#include <stdlib.h>
	5:
	6:	char *mymalloc(int n)
	7:	{	
	8:		return (char *)malloc(n);	
	9:	}

A C++ example

C++
	1:  /*
  	2:   * File: bag.h
  	3:   */
  	4:  class Bag {
  	5:      struct store {
  	6:          void *ptr;
  	7:          store *next;
  	8:      };
  	9:      store *top;
  	10:     public:
  	11:     Bag() : top(0) { }
  	12:     void insert(void *ptr);
  	13:  };


	1:  /*
  	2:   * File: bag.C
  	3:   */
  	4:  #include "bag.h"
  	5:
  	6:  int main(void) {
  	7:      Bag bag;
  	8:
  	9:      for (int i = 0; i < 10; i++) {
  	10:         int *f = new int;
  	11:         bag.insert(f);
  	12:     }
   	13:     return 0;
  	14:  }



	1:  /*
  	2:   * File: bagi.C
  	3:   */
  	4:  #include "bag.h"
  	5:
  	6:  void Bag::insert(void *ptr) {
  	7:      store *s = new store;
  	8:      s->next = top;
  	9:      top = s;
  	10:     s->ptr = ptr;
  	11:     return;
  	12:  }
	

Let's assume that bagi.C is a part of a class library which was not compiled with Insure++, e.g. a third-party library. We can simulate this situation by compiling the files with the following commands:


	insure -g -c bag.C
	CC -c bagi.C
        insure -g -o bag bag.o bagi.o

An interface for the insert class function might look like this:


	1:  /*
  	2:   * File: bag_i.C
  	3:   */
  	4:  #include "bag.h"
  	5:
  	6:  void Bag::insert(void *ptr) {
  	7:       iic_save(ptr);
  	8:       insert(ptr);
  	9:       return;
  	10:  }

We can then compile the interface file with the iic compiler as follows:

	iic bag_i.C

To get Insure++ to use the new interface description, we need to use the following compilation commands in place of the earlier commands:


	CC -c bagi.C
	insure -g -o bag bag.C bagi.o bag_i.tqs

The basic principles of interfaces

As shown in the previous examples, interface descriptions have the following elements:

  • The declaration of the interface description looks just like a piece of C code for the described function. It declares the arguments and return type of the function. Either ANSI or Kernighan & Ritchie style declarations may be used, but ANSI style is preferred, since K&R style declarations have implicit type promotions.
  • The body of the interface description uses calls to functions whose names start with iic_ to describe the behavior of the routine.
  • The interface function appears to call itself at some point.

These concepts are common to all interface descriptions.

Interface creation strategy

There are several possible strategies for creating interfaces for your software depending on what resources you have available and how much time you wish to expend on the project.

Normally, we recommend the following steps:

  • Create a file containing ANSI-style prototypes for the functions for which you want to make interfaces.
  • Extend these prototypes by adding additional error checks with the built-in iic_ functions.

Getting to the first stage will allow you to perform strong type-checking on all the functions in your application. Going to the second stage provides full support for all of Insure++'s error checking capabilities.

Various aids are provided to help you implement these two stages, as briefly summarized in the flowchart in Figure 9, which includes page references for the most important steps.

Figure 15

Trivial interfaces - function prototypes

The interfaces described so far have been "complete" in the sense that they contain error checking calls and also the "fake" recursive call typical of an interface function. There is actually one level of interface which is even simpler than this - an ANSI-style function prototype.

If you make a file containing ANSI-style prototypes for all of your functions, compile it with the iic program, and then add it to your insure command (as described in Using interfaces), you will get strong type-checking for all of your functions. You can then incrementally add to this file the extended interface descriptions with better memory checking and the "fake" recursive call.

Using iiwhich to find an interface

The simplest way to generate an interface is to copy one from a routine that does something similar. In the two examples which started this section, we used interfaces to functions that behaved roughly the same way that malloc and memcpy operate. Furthermore, these two system functions are ones that Insure++ knows about automatically, because interfaces to all system calls are shipped with Insure++.

To see how their interfaces are defined, we use the command iiwhich as follows:

	iiwhich malloc memcpy

The output from this command is shown in Figure 12.

Warning

Note that on your system, these may be linkable interfaces. If this is the case, you will need to find the source code to these interfaces in the src.$ARCH/$COMPILER directory).


 	1: malloc: Interface in /usr/local/release/lib.solaris/cc/libc.tqi
	2:
	3:    void *malloc(size_t size)
	4:    {
	5:        void *r;
	6:
	7:        ;
	8:        if (size < 0)
	9:            iic_error(USER_ERROR, "Negative size passed to malloc: %ld",
	10:                (long) size);
	11:        r = malloc(size);
	12:        if (!r) {
	13:            if (!_Insight_is_direct_linked()) {
	14:                iic_error(RETURN_FAILURE, "malloc(%u) failed: %s",
	15:                    size, sys_errlist[errno]);
	16:            }
	17:        } else {
	18:            ;
	19:            iic_alloc(r, size);
	20:            iic_write_uninit_pattern(r, size);
	21:        }
	22:        ;
	23:        return r;
	24:    }
	25:
	26:memcpy: *Linkable* interface in /usr/local/release/lib.solaris/cc/libc.tqi
	27:
	28:    void * memcpy(void *d, void *s, size_t len);
	

Figure 12. Insure++ interfaces for malloc and memcpy

The first block of "code" is the interface which defines the behavior of the malloc function, and the second describes memcpy. Note that they both follow the principles described above: they look more or less like C code with one strange exception - each function appears to call itself!

This is not recursive behavior, because this is not real C code. What really happens is that calls to the functions shown are replaced by the interface code. Nonetheless, it can be thought of as C code when you write your own interfaces.

A second slightly tricky feature concerns the behavior of function calls made within an interface definition. These are of two types:

  • Calls to Insure++ interface functions, whose names begin with iic_, are detected by the iic command and turned into sequences of error checking calls. They are not real function calls in themselves.
  • Calls to other functions are made exactly as requested, with no additional error checking. This can be a problem if you end up passing a bad pointer to an unchecked library call, which may cause the program to fail before Insure++ can print an error message.

Note that the iiwhich command is also useful if you want to see what properties of a function are being checked by Insure++, or if Insure++ knows anything about it. The command

	iiwhich foo

shows you the interface for the function foo, if it exists. If no interface exists, no checking will be done on calls to this function unless you write an interface yourself.

Writing simple interfaces

Using iiwhich can save you a lot of time. Before starting to write your own interface files, particularly for system functions, you should check that one hasn't already been defined. Then, if you can think of a common function that operates in a similar way to the function you're trying to interface, start by copying its definition and modifying it. In either case, you must understand the way that the interfaces work, and to do this, you must first understand their goal.

The malloc function returns blocks of memory, and we need to tell Insure++ about the size and location of such blocks. This is the reason for the call to iic_alloc at line 9 in Figure 12. This is the interface function that tells Insure++ to record the fact that a block of uninitialized memory of the given size has been allocated. From then on, references to this block of memory will be understood properly by Insure++.

Similarly, the purpose of memcpy is to take a number of bytes from one particular location and copy them to another. This activity is indicated by the call to the interface function iic_copy at line 32 of Figure 12. Insure++ uses this call to understand that two memory regions of the indicated size will be read and written, respectively.

The other code shown in the interface descriptions is used to check that parameters lie in legal ranges and is used to provide additional error checking.

Using interfaces

To use an interface, we first compile it with the Insure++ interface compiler, iic. If, for example, we put the interface for the lib_gimme function, in a file called gimme_i.c, we would use the command

	iic gimme_i.c

This results in the file gimme_i.tqs, which can be passed to Insure++ on the command line as follows:

	insure -c gimme_i.tqs wilduse.c
	insure -o wild wilduse.o mylib.a

in which we assume that the library containing the actual code for the lib_gimme routine is called mylib.a .

An additional example of how to use an interface can be found earlier in this section.

The basics for using an interface, therefore, are to:

  • Compile the interface with iic.
  • Recompile your program.

Note that you don't have to limit yourself to a single interface per source file. If you are preparing an interface module for an entire library, or a source file with multiple functions, you can put them all into the same interface description file.

Similarly, you don't have to pass all the names of your compiled interface modules on the insure command line every time. You can add lines to your .psrc files that list interface modules as follows:

	insure++.interface_library /usr/trf/mylib.tqs
	insure++.interface_library /usr/local/ourlib.tqs

Ordering of interfaces

Files containing compiled interface definitions can be placed in any directory. Insure++ can be told to use such files in various ways, and processes them according to the following rules:

  • If a standard library interface exists it is processed first.
  • Interfaces specified in interface_library statements in configuration (.psrc) files are processed next, potentially overriding standard library definitions.
  • Interface modules (i.e., files with the suffix .tqs or .tqi) specified on the insure command line override any other definitions.

Later definitions supercede earlier ones, so you can make a local definition of a library function and it will override the standard one in the library.

To see which interface files will be processed, and in which order, you can execute the command

	iiwhich -l

which lists all the standard library files for your system, and then any indicated by interface_library commands in configuration files.

To find a function in an interface library, you can use the iiwhich command as already described. To list the contents of a particular .tqs, .tqi, or .tql file, use the iiinfo command.

Working on multiple platforms or with multiple compilers

Many projects involve porting applications to several different platforms or the use of more than one compiler. Insure++ deals with this by using two built-in variables, which denote the machine architecture on which you are running and the name of the compiler you are using. You can use these values to switch between various combinations, each specific to a particular machine or compiler.

For example, environment variables, '~'s (for HOME directories) and the '%' notation described in Filenames, are expanded when processing filenames, so the command

	insure++.interface_library 
        $HOME/insure++/%a/%c/foo.tqs

loads an interface file with a name such as

	/usr/me/insure++/sun4/cc/foo.tqs

in which the environment variable HOME has been replaced by its value and the '%a' and '%c ' macros have been expanded to indicate the architecture and compiler name in use. This allows you to load the appropriate .tqs, .tqi, or .tql files for the architecture and compiler that you are using.

Warning

One problem to watch out for occurs when you switch to a compiler for which Insure++ supplies no interface modules. In this case, you will see an error message during compilation. Several work-arounds are possible as described in the FAQ (FAQ.txt).

Common interface functions

Most definitions need only a handful of interface functions of which we've already introduced the most common:

void iic_alloc(void *ptr, unsigned long size);
Declares a block of dynamically allocated, uninitialized, memory.

void iic_source(void *ptr, unsigned long size);
Declares that a block of memory is read.

void iic_sourcei(void *ptr, unsigned long size);
Declares that a block of memory is read and also checks that it is completely initialized.

void iic_dest(void *ptr, unsigned long size);
Declares that a block of memory is modified.

void iic_copy(void *to, void *from, unsigned long size);
Declares that the indicated block of memory is copied.

void iic_error(int code, char *format, ...);
Causes an error to be generated with the indicated error code. Subsequent arguments are treated as though they were part of the printf statement.

Other commonly occurring functions are listed below together with examples of system calls that use them. You can use the iiwhich command on the listed functions to see examples of their use.

int iic_string(char *ptr, unsigned long size);
Declares that the argument should be a NULL terminated character string. This is used in most of the string handling routines such as strcpy, strcat, etc. The second argument is optional, and can be used to limit the check to at most size characters.

void iic_alloci(void *ptr, unsigned long size);
Declares a block of dynamically allocated, initialized memory such as might be returned by calloc.

void iic_allocs(void *ptr, unsigned long size);
Declares a pointer to a block of statically allocated memory. Used by functions that return pointers into static strings. ctime and getenv are examples of system calls that do this.

void iic_unalloc(void *ptr);
Declares that the indicated pointer is de-allocated with the system call free.

A complete list of available functions is given in " Interface Functions".

Checking for errors in system calls

We can make interfaces even more user-friendly by adding checks for common problems, similar to the user level checks that were discussed in "Insure++ Code Insertions".

For example, malloc can fail. This is the reason for the second branch of the code in line 11 of Figure 12. If the actual call to malloc fails, instead of telling Insure++ about a block of allocated memory with iic_alloc, we cause an Insure++ error with code RETURN_FAILURE and the error message shown. This, in turn, will cause a message to be printed (at runtime) whenever malloc fails and the RETURN_FAILURE error code has been unsuppressed. (See "Enabling error messages".)

Similarly, memcpy can cause undefined behavior when given perfectly valid buffers that happen to overlap. We check for this case in the code at line 20, and again, cause an Insure++ error if a problem is detected.

This method provides a very powerful debugging technique, which is used extensively in the interface files supplied with Insure++. Since the RETURN_FAILURE error code is suppressed by default, you will normally not be bothered by messages when system calls fail. The assumption is that the user application is going to deal with the problem. In fact, it may require certain system calls to fail in order to work properly. However, when particularly nasty bugs appear, it is often useful to enable the RETURN_FAILURE error category to look for cases where system calls fail "unexpectedly" and are not being handled correctly by the application. Errors such as missing files (causing fopen to fail) or insufficient memory (malloc fails) can then be diagnosed trivially.

Using Insure++ in production code

A particularly powerful application of the technique described in the previous section is to make two different versions of your application.

  • One with full error checking.
  • One without Insure++ at all.

The first of these is used during application development to find the most serious bugs. The second is the one that will be used in production and shipped to customers.

When you or your customer support team is faced with a problem, they can run this code with the RETURN_FAILURE error class enabled and look for "unexpected" failures such as missing files, incorrectly set permissions, insufficient memory, etc.

Advanced interfaces: complex data types

The interfaces that have been considered so far are simple in the sense that their behavior is determined by their arguments in a straightforward manner.

To show a more complex example, consider the following data structure


	struct mybuf {
              int len;
              char *data;
	};

This data type could be used to handle variable length buffers. The first element shows the buffer length and the second points to a dynamically allocated buffer.

The code which allocates such an object might look as follows:

	#include <stdlib.h>

	struct mybuf *mybuf_creat(n)
              int n;
	{
              struct mybuf *b;

              b = (struct mybuf *)malloc(sizeof(*b));
              if(b) { 
                   b->data = (char *)malloc(n);
                   if(b->data) b->len = n;
                   else b->len = 0;
              }
              return b;
        }

Similarly, we might define operations on a struct mybuf that work in quite complex ways on its data.

To build an interface description of the mybuf_create function which detailed all its behavior would require the following code


	struct mybuf *mybuf_creat(n)
              int n;
        {
              struct mybuf *b;

              b = mybuf_creat(n);
              if(b) {
                   iic_alloci(b, sizeof(*b));
                   if(b->data)
                        iic_alloc(b->data, b->len);
              }
              return b;
        }

Note how the structure of the interface description follows that of the original source.

This matching would be seen in the interface descriptions of all the other functions that operate on the struct mybuf data type, too. In fact, the interface description would probably end up looking quite a lot like the source code!

There are basically three approaches to dealing with this problem:

  • Forget the interface entirely and actually process the real source code with insure and link it in the normal manner.
  • "Go deep" and define an interface that mimics all of the details of the interface, including all the operations on the internal structure elements.
  • "Go opaque" and build an interface that defines some levels of the functions without necessarily going into details of their action.

Each of these is a good approach in a different situation.

The first approach, process the actual source code, is the best in terms of accuracy and reliability. Given the original source code, Insure++ will have complete knowledge of the workings of the code and will be able to check every detail itself.

The second approach is best when the source code is unavailable but you still want to check every detail of your program's interaction with the affected routines. It can be implemented only if you have intimate knowledge of how the routines work, since you will have to use the interface functions to mimic the actions of the functions on the individual elements of the struct mybuf.

The third approach is appropriate when you are sure that the functions themselves work correctly. Perhaps, for example, you've been running Insure++ on their source code at some earlier date and you know that they are internally consistent and robust. In this case, you may want to increase the performance of the rest of your program by checking the high level interface to the routines, but not their internal details.

Another reason for adopting this last approach might be that you actually don't know the details of the functions involved and might not be able to duplicate their exact behavior. A good example would be building an interface to a third party library. You have clear definitions of the upper level behavior of the routines, but may not know how they work internally.

The first and second approaches have already been discussed. The third approach is easily achieved by doing nothing - Insure++ will recognize that the data type has not been declared in detail and should therefore not be checked in detail. You can choose for yourself which fields to declare in detail and which to ignore.

Interface esoterica

Since it is possible to express a wide range of actions in C, interface files must have correspondingly sophisticated capabilities in order to define their actions and check their validity.

One of these features was seen in the previous section: the iic_startup function. This function can be defined in any interface file and contains calls to interface functions that will be made before calling any of the other functions defined in the interface file. Typically, you will place definitions and initializations of known global or external variables in this function.

Note that each interface file may have its own iic_startup.

Variable argument lists are dealt with by using the pre-defined variable __dots__. For example, the interface specification for the standard system call printf is


	int printf(char *format, ...)
	{
               iic_string(format);
               iic_output_format(format);
               return(printf(format, __dots__));
	}

The variable __dots__ in the function call matches the variable arguments declared with "..." in the definition.

Checking of printf and scanf style format strings is done with the iic_output_format and iic_input_format routines. These check that arguments match their corresponding format characters. iic_strlenf returns the length of a string after its format characters have been expanded and can be used to check that buffers are large enough to hold formatted strings.

A complete list of interface functions can be found in " Interface Functions".

Callbacks

In many programming styles, such as programming in the X Window System or when using signal handlers, functions are registered and are then "called-back" by the system. Often the user program contains no explicit calls to these functions.

If the callback functions use only variables that are defined in the user program, nothing unusual will happen, since Insure++ will understand where all this data came from and will keep track of it properly. In many cases, however, the library function making the callback will pass additional data to the called function that was allocated internally, which Insure++ never saw.

For example:

  • UNIX functions such as qsort and scandir take function pointer arguments which are called-back from within the system function.
  • Signal handling functions often pass to their handlers a data structure containing hardware registers and status information.
  • The X Window System library often passes information about the display, screen, and/or event type to its callback functions.

In these cases, Insure++ will attempt to lookup information about these data structures without finding any, which limits its ability to perform strong error checking.

Warning

This is not a serious limitation - it merely means that the unknown variables will not be checked as thoroughly as those whose allocation was processed by Insure++.


If you wish to improve the checking performed by Insure++ in these cases, you can use the interface technology in two different ways:

  • You can make interfaces to the functions which install or register the callbacks (with iic_callback) indicating how to process their arguments when the callbacks are invoked.
  • You can make interface definitions for your callback functions themselves, adding the keyword iic_body to their definition.

These two options are discussed in the next sections.

Using iic_callback

The first of these approaches is more general, since it allows you to define, in a single interface specification, the behavior of any callback which is installed by the function specified. To see how this works, consider the standard utility sorting function, qsort. One of the arguments to this routine is a function pointer that is used to compare pairs of elements during sorting.

The following interface to this function checks that the qsort function does no more than N2 comparisons, where N is the number of elements (this may or may not be a sensible check, but serves the purpose of explaining callback interfaces):


	1:  #include <sys/stdtypes.h>
	2:  #include <math.h>
	3:  
	4:  static int _qsort_num_comparisons;
	5:
	6:  static int _qsort_cb(void *e1, void *e2)
	7:  {
	8:       _qsort_num_comparisons += 1;
	9:       return _qsort_cb(e1, e2);
	10:  }
	11:
	12:  void qsort(void *base, size_t nelem,
	13:      size_t width,
	14:      int (*func)(void *, void *))
	15:  {
	16:      iic_dest(base, nelem*width);
	17:      iic_func(func);
	18:      iic_callback(func, _qsort_cb);
	19:      _qsort_num_comparisons = 0;
	20:      qsort(base, nelem, width, func);
	21:      if (_qsort_num_comparisons >
	22:                         nelem * nelem)
	23:          iic_error(USER_ERROR,
	24:               "Qsort took %d compares.",
	25:               _qsort_num_comparisons);
	26:  }

The main body of the interface is in lines 16-25.

Line 16 checks that the pointer supplied by the user indicates a large enough region to hold all the data to be sorted, while line 17 checks that the function pointer actually points to a valid function. Line 20 contains the call to the normal qsort function.

The interesting part of the interface is the call to iic_callback in line 18. The two arguments connect a function pointer and a "template", which in interface terms is the name of a previously declared static function; in this case _qsort_cb, declared in lines 6-10. The template tells Insure++ what to do whenever the system invokes the called-back, user-supplied function. In this particular case, the interface merely increments a counter so we can see how many times the callback gets called (note that we set the counter to 0 on line 19 of the qsort interface). In general, you can make any other interesting checks here before or after invoking the callback function.

Notice that once this interface is in use, it automatically processes any function that gets passed to the qsort function.

Using iic_body

The second callback option is to define interfaces for each individual function that will be used as a callback.

Consider, for example, the X Window System function XtAddCallback, which specifies a function to be called in response to a particular user interaction with a user interface object. It is quite common for code to contain many calls to this function, for example


	XtAddCallback(widget, ..., myfunc1, ...);
	XtAddCallback(widget, ..., myfunc2, ...);
	XtAddCallback(widget, ..., myfunc3, ...).

One solution for this routine would be to provide an iic_callback style interface for the XtAddCallback function as described in the previous section. The second method is to specify interfaces to the called-back functions themselves, with the additional iic_body keyword. An interface for the routine myfunc1 might be written as follows:


	/*
         * Interface definition for callback function
         * uses the iic_body keyword.
         */
        void iic_body myfunc1(Widget w,
              XtPointer client_data,
              XtPointer call_data)
        {
              if (!call_data)
                   iic_error(USER_ERROR,
                        "myfunc1 passed NULL call_data");
              myfunc1(w, client_data, call_data);
              return;
        }

This interface checks that myfunc1 is never passed NULL client_data.

Note that in this scenario you would have to specify three separate interfaces; one each for myfunc1, myfunc2 and myfunc3. (And, indeed, any other functions used as callbacks.)

Which to use: iic_callback or iic_body?

From the previous discussion it might seem that iic_callback should always be preferred over iic_body, since it is more general and less code must be written. Unfortunately, the general iic_callback method has a severe limitation: the code generated by Insure++ when you use iic_callback is good for "immediate use only".

To understand what this means, consider the difference between the two cases already discussed.

  • In the qsort example, the iic_callback function made the association between function pointer and template, which was then immediately used by the qsort function. By the time the interface code returns to its caller, the connection between function and template is no longer required.
  • In the X Window System example, the callbacks registered by the XtAddCallback function are expected to survive for the remainder of the application (or until canceled by another X Window System call). Similarly, the connection between function pointer and template is expected to survive as long.

As a consequence, the iic_callback method is only applicable to a small number of circumstances, and in general you must either:

  • Use the iic_body method
  • Do nothing, and allow Insure++ to skip checks on unknown arguments to callback functions.

Conclusions

Interfaces play an important, but optional, role in the workings of Insure++.

If you wish, you can always eliminate error messages about library calls by adding suppress options to your .psrc files and running your program again. This approach has the advantage of being very quick and easy to implement, but discards a lot of information about your program that could potentially help you find errors.

To capture all the problems in your program, you need to use interfaces. Insure++ is supplied with interfaces for all the common functions and quite a few uncommon ones. These are provided in source code form in the directory src.$ARCH/$COMPILER so that you can look at them and modify them for your particular needs.

The iiwhich command can help you find existing definitions which can then be used as building blocks in making your own interfaces.

If you build an interface to a library that you'd like to share with other users of Insure++, please send it to us (insure@parasoft.com) and we'll make it available.


For more information, call (888) 305-0041 or send email to: insure@parasoft.com

< Code Insertions <Insure++ User's Guide TOC > Reference Guide TOC
Tools to debug c++ and java
(888) 305-0041 info@parasoft.com Copyright © 1996-2001 ParaSoft