criminal will exploit your code later
on. The one true ideal might appear
to be, “Keep consistent and carry on,”
but in the real world of connected and
interacting programs, you must make
a careful determination as to whether
it is better to abort the program right
away or to soldier on through adversity, only to meet certain ruin later.
Realizing that “I have only a very
small head and must live with it,” 1 sensible compromises must be made—
for example, a trade-off between the
probability of the failure and the effort
of writing code to deal with it. There
is also a real and valid concern about
code readability—handling unlikely
exceptions should not dominate the
source code.
Figure 1. mini objects.
In Varnish the resulting compromise typically looks like this:
AN(vd);
AZ(close(vd->vsm _ fd));
AN is a macro that means Assert Non-zero and AZ means Assert Zero, and
if the condition does not hold, the
program core-dumps right then and
there.
Yes, the red-haired stepchild I want
to sell you is the good old assert, which
I feel should be used a lot more in today’s complicated programs. Where I
judge the probability of failure is relevant, I use two other variants of those
macros, XXXAN and XXXAZ, to signal,
“This can actually happen, and if it
struct lru {
unsigned magic;
#define LRU_MAGIC 0x3fec7bb0
...
};
...
struct lru *l;
ALLOC_OBJ(l, LRU_MAGIC);
XXXAN(l);
...
FREE_OBJ(l);
The ALLOC _ OBJ and FREE _ OBJ macros ensure that the MAGiC field is set to the randomly
chosen nonce when that piece of memory contains a struct lru and is set to zero when it does not.
in code that gets called with an lru pointer, another macro checks asserts the pointer points
to what we think it does:
int
foo(struct lru *l)
{
CHECK_OBJ_NOTNULL(l, LRU_MAGIC);
static void
vwp_main(void *priv)
{
struct vwp *vwp;
Figure 2. Compile time asserts.
#define CTASSERT(x,z) _CTASSERT(x, __LINE__, z)
#define _CTASSERT(x, y, z) __CTASSERT(x, y, z)
#define __CTASSERT(x, y, z) \
typedef char __ct_assert y __ z [(x) 1 : - 1]
...
CTASSERT(sizeof(struct wfrtc_proto) == 32, \
Struct_wfrtc_proto_has_wrong_size);
happens too much, we should handle
it better.”
retval = strdup(of);
XXXAN(retval);
return (retval);
This distinction is also made in the
dump message, which for AZ() is “As-
sert error” vs. XXXAZ()’s “Missing er-
ror-handling code.”
Where I want to ignore a return val-
ue explicitly, I explicitly do so:
(void)close(fd);
Of course, I also use “naked” asserts
to make sure there are no buffer overruns:
assert(size < sma->sz);
or to document important assumptions in the code:
assert(sizeof (unsigned short)
== 2);
But we are not done yet. One very typical issue in C programs is messed-up
lifetime control of allocated memory,
typically accessing a struct after it has
been freed back to the memory pool.
Passing objects through void*
pointers, as one is forced to do when
simulating object-oriented programming in C, opens another can of
worms. Figure 1 illustrates my brute-force approach to these problems.
In terms of numbers, 10% of the
non-comment source lines in Varnish
are protected with one of the asserts
just shown, and that is not counting
what gets instantiated via macros and
inline functions.
a method to the madness
All this checking is theoretically redundant, particularly the cases where
function A will check a pointer before
calling function B with it, only to have
function B check it again.
Though it may look like madness,
there is reason for it: these asserts
also document the assumptions of the
code. Traditionally, that documentation appears in comments: “Must be
called with a valid pointer to a foobar
larger than 16 frobozz” and so on.
The problem with comments is the