BeeOnRope
BeeOnRope

Reputation: 65016

Make valgrind fail fast on a uninitalized value

Valgrind memcheck uses a bunch of heuristics to avoid false positives on "harmless" uses uninitialized values, since such uses are common both in correct and incorrect-but-otherwise functioning code.

In particular, it doesn't barf until you actually use such a value in a serious, perhaps "irreversible" way, e.g, jumping based on its value.

This means that sometimes the error occurs very far from the origin of the problem and it is not even possible to determine which value is involved. Is there some way to "check" a value at runtime, like use(x) which will make Valgrind emit an error at that spot if x is uninitialized?

Upvotes: 4

Views: 272

Answers (2)

Hasturkun
Hasturkun

Reputation: 36412

You can make your use(x) macro use the Valgrind VALGRIND_CHECK_VALUE_IS_DEFINED Client Request to get an error on the spot.

For this, include valgrind/memcheck.h and define your macro as

#define use(x) VALGRIND_CHECK_VALUE_IS_DEFINED(x)

And be sure to always pass an lvalue.

You can also run memcheck with the --track-origins=yes for heavier tracking that should show where the uninitialized data originated.

See also the Valgrind FAQ on uninitialised value errors, which explains both, as well as why Valgrind doesn't complain on copying uninitialised values.

Upvotes: 1

Morten Jensen
Morten Jensen

Reputation: 5946

Usually, stuff like this requires instrumentation of the code (either done automatically by a tool, or inserted manually in the source).

As noted in my comment, if you can work with having to insert the use(x) statements yourself, you could do something like this:

static FILE* dev_null = 0;
static void use_var(char* var_addr, size_t var_size)
{
  if (dev_null == 0) /* make sure we only open FILE* dev_null once */
  {
    dev_null = fopen("/dev/null", "wb");
    assert(dev_null != 0); /* opening /dev/null CAN actually fail */
  }
  size_t i;
  for (i = 0; i < var_size; ++i)
  {
    fputc(var_addr[i], dev_null); /* read every byte in the variable, write to dev_null */
  }
}

#define use(x) use_var((char*)&x, sizeof(x))

/* Example of usage */
int main()
{
  long x = 80;
  struct { double d; char c[123]; } y;
  memset(&y, 0, sizeof(y) - 1); /* initialize all bytes in y, except the last */
  double z[2] = {3.14, 42.0};

  use(x);
  use(y);
  use(z);

  return 0;
}

However there is a problem with using structs that contain alignment-padding. The padding is never used for anything, so this can be a legal reason for passing around uninitialized data. Valgrind can cause spurious errors regarding uninitialized reads in this case exactly.

These two posts discuss this issue specifically: Is there anyway a valgrind message "Conditional jump or move depends on uninitialized value" can be a so called 'false positive'

Should I worry about "Conditional jump or move depends on uninitialised value(s)"?

Upvotes: 0

Related Questions