iiwhich to find an interfaceiic_callbackiic_bodyiic_callback or
iic_body?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.
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++.
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:
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: }
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
As shown in the previous examples, interface descriptions have the following elements:
iic_ to describe the
behavior of the routine.These concepts are common to all interface descriptions.
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:
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.

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.
iiwhich to find an interfaceThe 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.
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/insure/standard.tqs
2: [./lib.c:450]
3:
4: char *malloc(size_t size) {
5: char *a;
6:
7: a = malloc(size);
8: if (a)
9: iic_alloc(a, size);
10: else
11: iic_error(RETURN_FAILURE,
12: "malloc(%u) returned null", size);
13: return a;
14: }
15:
16: memcpy: Interface in /usr/local/insure/standard.tqs
17: [./lib.c:204]
18:
19: char *memcpy(void *d, void *s, int len) {
20: if (len < 0) {
21: iic_error(USER_ERROR,
22: "Negative length passwd to memcpy: %d",
23: len);
24: }
25: else (
26: if (((char *) s < (char *) d &&
27: (char *) d < (char *) s + len) ||
28: ((char *) d < (char *) s &&
29: (char *) s < (char *) d + len))
30: iic_error(USER_ERROR,
31: "Memory blocks passed to memcpy overlap.");
32: iic_copy(d, s, len);
33: }
34: return memcpy(d, s, len);
35: }
malloc and
memcpyThe 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:
iic_, are detected by the
iic command and turned into sequences of
error checking calls. They are not real function calls in
themselves.
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.
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.
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:
iic.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
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:
interface_library statements in
configuration (.psrc) files are processed
next, potentially overriding standard library definitions..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.
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.
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).
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);
void iic_source(void *ptr, unsigned
long size);
void iic_sourcei(void *ptr, unsigned
long size);
void iic_dest(void *ptr, unsigned
long size);
void iic_copy(void *to, void *from,
unsigned long size);
void iic_error(int code, char
*format, ...);
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);
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);
calloc.
void iic_allocs(void *ptr, unsigned long size);
ctime and getenv are
examples of system calls that do this.
void iic_unalloc(void *ptr);
free.
A complete list of available functions is given in " Interface Functions".
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.
A particularly powerful application of the technique described in the previous section is to make two different versions of your application.
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.
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:
insure and link it in the
normal manner.
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.
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".
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:
qsort and
scandir take function pointer arguments which
are called-back from within the system function.
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.
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:
iic_callback) indicating
how to process their arguments when the callbacks are invoked.
iic_body to their
definition.
These two options are discussed in the next sections.
iic_callbackThe 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.
iic_bodyThe 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.)
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.
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.
XtAddCallback function are expected to survive for
the remainder of the application (or until cancelled 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:
iic_body method
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