Posts Tagged testing

Bullet-Proofing C and C++ Without Draconian Verbosity

Many, many bugs can be found through static analysis, however, the present state of analysis tools is pretty poor. Often we have two options. Either we shut off many of the warnings or we add so much verbosity to the code that readability suffers. The verbosity is ok when you are working small projects but as soon as you pass the 15 k SLOC mark you need to consider a different strategy. Blanket safety netting is common but again it tends to obscure the semantics of the code. Also things like over zealous casting can be very very dangerous. I have developed over a long period of time a set of techniques that allow static checking while still minimizing the added verbosity. I will list a few here.

The most well known and perhaps oldest (by heritage) static checker is probably SPLint. SPLint really does find quite a few issues and I recommend using it. The problem is that it is also a fire hose of false positives. I have found a number of options to make the false positives more manageable:

splint +unixlib -D__USE_POSIX -exportlocal -nullassign \
  -fcn-macros -onlytrans -boolops +ptrnegate -paramuse -retvalint \
  +boolint +floatdouble -macroredef -nullret -elseif-complete \
  -aliasunique -allimponly -predboolptr -retvalother -globstate \
  +matchanyintegral -nullpass

The other method to appease SPLint is to insert tags in your code attributing symbols in statements. For example /*@fallthrough@*/ tells splint you did indeed intend to let a case statement fall through. This kind of annotation is great for communicating to your teammates as well. However an abundance of these annotations will quickly pollute your code and there is no way to customize (i.e. #define) these annotations. Check out the following example of the prototype of free from the SPLint manual:

void free( /*@only@*/ /*@out@*/ /*@null@*/ void *ptr );

So SPLint is asking that you attribute everything at the expense of readability. Fortunately, there is another way that has many additional benefits. I have found that GCC can work effectively hand and hand with SPLint.

The good folks at GCC have provided the ability to add functional attributions to declarations. The syntax is __attribute__(( attribute_info )) where “attribute_info” is replaced by the attribute name and potentially an argument list. These attributes are used by GCC for its own static analysis but also to optimize your code. Much like const and volitile these attributes tell GCC that your code will be constrained such that the optimizer can be more or less aggressive. SPLint seems to respect these attributes even though I have found no such language in the documentation. The following are some convenience macros I frequently use:

#ifdef __GNUC__
#define UNUSED __attribute__ ((unused))
#define DEPRECATED __attribute__ ((deprecated))
#ifndef PURE // means - produces no side effects (by modifying shared globals)
#define PURE __attribute__ ((pure))
#endif
#ifndef NORETURN // means - exit is always called from this function
#define NORETURN __attribute__ ((noreturn))
#endif
// 'archetype' is one of printf, scanf, strftime or strfmon
#define FMT_FUNC( archetype, fmt_str_idx, first_varg_idx ) \
  __attribute__ ((format( archetype, fmt_str_idx, first_varg_idx )))
#define NONNULL_ARGS(arg_indexes...) __attribute__((nonnull( ##arg_indexes )));
#else
#define UNUSED
#define DEPRECATED
#define PURE
#define NORETURN
#define FMT_FUNC( archetype, fmt_str_idx, first_varg_idx )
#define NONNULL_ARGS(arg_indexes...)
#endif

GCC actually is capable of a great deal of static analysis if you enable the right options. The warnings are a lot more reasonable, so I have gotten into the practice of disabling a check in SPLint if it is enabled in GCC (see listing above). The following is a list of GCC arguments that enable full analysis and checking.

-g -Wall -Wdeclaration-after-statement -Wnested-externs -Wextra -O2

The O2 (optimization level 2) is not a mistake. Level 2 needs to be on in order to enable flow analysis within GCC. The “declaration-after-statement” restriction is because SPLint chokes hard on any declarations after the beginning of a block body code (even though it is in C99).

Ok I think this post is long enough. There are more techniques but they will have to come in subsequent posts.

, , , , , , , , , , ,

No Comments

Bad Behavior has blocked 150 access attempts in the last 7 days.