Reactis for C User's Guide   Contents  |  Index
 Chapters:  1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17

Chapter 14  Errors Detected by Reactis for C

14.1  Integer Overflows

An integer overflow occurs when an operation produces an integer result which is too large or too small to be represented by the destination type of the operation. When an integer overflow occurs, the course of action taken by Reactis for C depends on the setting for the operation which overflowed: Wrap Over or Error.

14.1.1  Wrapping over

When overflow behavior is set to Wrap Over, Reactis for C emulates the behavior of typical hardware, which is to truncate the most significant bits of the value until the value is small enough to be represented. The effect of wrapping over is different for unsigned and signed integers.

In the case of unsigned integers, wrapping over a value x is typically equal to x   mod   2n, where n is the width (in bits) of the container type.

When signed values are wrapped over, the sign bit may change, producing results whose sign is the opposite of its value prior to wrapping over. For example, in most 32-bit environments, adding the maximum signed int value (0x7fffffff) to itself produces the value 0xfffffffe, wrapped-over result −2.

14.1.2  Error diagnosis

When overflow behavior is set to Error, Reactis for C stops execution as close to the statement where the overflow occurred as possible and displays an error dialog. You can hover on variables at this point to see their values and step backward to inspect earlier values if desired.

14.1.3  Controlling overflow behavior

The action taken when integer overflows occur is controlled by the four parameters in the C Code tab of the Global Settings dialog. (See Section 4.3.2 for details on how to set these parameters.) The following operations are controlled by each parameter:

On signed integer overflow
Signed integer arithmetic, signed integer shift, conversion from floating point to signed integer, conversion from signed integer to a smaller signed integer type.
On unsigned integer overflow
Unsigned integer arithmetic, unsigned integer shift, conversion from signed to unsigned integer, conversion from unsigned integer to a smaller unsigned integer type.
On overflow during conversion from unsigned to signed integer
Conversion from unsigned to signed integer. (Conversions from other types to signed integer are controlled by On signed integer overflow.)
On overflow during conversion from floating-point to unsigned integer
Conversion from floating-point to unsigned integer. (Conversions from other types to unsigned integer are controlled by On unsigned integer overflow.)

Note that Bit-level operations, such as bitwise-and (&), bitwise-or (|), etc. never produce overflows.

You should also be aware that, according to the C language specification, overflows during unsigned computations are wrapped over. This is different than the specification for signed integer computations, which states that the results for an overflow are undefined. Many C programs are designed to exploit the wrapping over of unsigned values, and setting On unsigned integer overflow to Error for such programs will result in superfluous errors during testing.

14.1.4  Determining the type of an expression

Reactis for C uses C99 rules for determining the type (signed or unsigned) of integer expressions, which affects when overflows may occur. For integer constants, the type of the constant is determined by radix of the constant and the presence of a U or u suffix, as determined by the following rules:

  1. Any integer constant with a U or u suffix is unsigned.
  2. Any decimal constant without a U or u suffix is signed.
  3. Any non-decimal constant X without a U or u suffix will be assigned the first type that can represent X from the following list: int, unsigned int, long long int, unsigned long long int. This means that 0xffffffff will be assigned type unsigned int and 4294967295 will be assigned type long long int even though the two values are equivalent. In the latter case, if the literal is changed to 4294967295U, it will be assigned type unsigned int.

The C99 rules for evaluating integer expressions state that values of any type smaller than int, including unsigned types, are first converted to type int before any subsequent evaluations are performed. This forces values of type unsigned short and unsigned char to become signed, so that, for example, adding two variables of type unsigned short will produce a signed int result.

When two integer values are combined in a binary expression, the width and signedness of the result are determined by two rules:

  1. If one type is larger than the other, then the larger type is chosen.
  2. If both types have the same size, then the result is unsigned if either type is unsigned.

Operator arguments are converted to the result type before the expression is evaluated. This means that the expression ((int)-1) > ((unsigned int)1) will evaluate to 0 (false).

14.2  Floating-Point Errors

In C and most other languages, floating-point calculations which overflow do not trigger an exception, but instead produce a distinct value which represents positive or negative infinity (+inf or -inf). Similarly, calculations whose result is indeterminate, such as 0.0/0.0, produce a special value called not-a-number (nan).

Reactis for C detects indeterminate and infinite values and will either raise an error, generate a warning message, or ignore the value, depending on the setting of When calculation results in NaN (Not-a-Number) or infinity (see Section 4.3.2). However, an error or warning is not immediately produced whenever a calculation generates a infinite or indeterminate result. Instead the error/warning is produced when a result of nan or inf is assigned to a storage location which does not already hold an infinite or nan value. This is done to minimize the number of redundant warning messages produced while generating or executing a test suite.

14.3  Memory Errors

Whenever a pointer is used to access memory, Reactis for C performs a safety check to ensure that pointer is valid. There are two steps in the validity check:

  1. Spatial validity. A pointer is not allowed to access memory outside of the bounds of the data object it originally pointed to (henceforth called the referent). For example, a pointer to an array can only be used to access memory within the bounds of the array. If A pointer is dereferenced and it points outside the bounds of its referent, then a spatial memory error occurs.
  2. Temporal validity. The referent of a pointer must not have been deallocated prior to an access attempt. Pointers to heap objects are no longer temporally valid once the referent has been deallocated by a call to free(). Pointers to local variables of a function are no longer valid once the function has returned. Data stored in static memory, such as variables declared outside of a function scope are never deallocated and are hence always temporally valid. Attempts to access an temporally invalid referent result in a temporal memory error.

int sum(int A[], int n)    
{
  int i = 0, x = 0;
  while(i <= n)
    x += A[i++];
  return x;
}
Figure 14.1: A function with a spatial memory error.

Note that it is possible that a program which appears to function correctly actually contains a memory error. For example, consider the function sum shown in Figure 14.3. This function sums the first n+1 element of A, which will include 1 element past the end of the array when n equals the number of elements in A. If the element which follows the end of A happens to always be zero, sum will function “correctly” in the sense that its return value is equal to what the programmer intended. At some point the future, however, a change to the program may cause the invalid memory access to return a non-zero value, which will cause the value returned by sum to be incorrect. Even worse, there may be rare runtime conditions under which the memory access returns a non-zero value, causing intermittent program malfunctions which are difficult to reproduce and diagnose. Hence it is best to detect and fix such errors even though they may not seem to be presently causing a problem.

14.3.1  Uninitialized Memory

In C, variables which are allocated in static memory (which includes all variables declared with the static keyword plus all variables declared outside the body of a function) are initialized to zero when there is no initial value given in the source code. Variables declared inside the body of a function without the static keyword, on the other hand, have no default initializer, and if they are not initialized in the source code will receive an value which is undefined by the C standard (which in practice is whatever value happens to be stored in the memory location where the variable is allocated when the function is called). Reading the contents of such a variable prior to the first write is another difficult to diagnose error which afflicts programs written in C.

Similarly, memory which is dynamically-allocated by malloc() is uninitialized, and it is the responsibility of the caller to initialize the memory before reading from it.

Reactis for C keeps tracks of which memory locations have been initialized and will raise an error when an attempt to read from uninitialized memory occurs.

14.3.2  Invalid Pointer Creation

Reactis for C can produce an error or warning whenever a pointer expression produces an invalid pointer. This can help determine the source of a memory error. However, there are cases where invalid pointers are produced which are never dereferenced, such as the following program fragment:

int A[10], B[10], i, *a = A, *b = B;
while (i < 10) {
  *(a++) = *(b++);
}

During the last iteration of the above loop, the pointers a and b are both assigned invalid pointer values. This is not a problem because the invalid pointer values are never used. For such programs, the handling of out-of-bounds pointers should be set to ignore or warning.

14.4  Other Runtime Errors

In addition to memory errors and overflows, Reactis for C also detects the errors listed below. The C standard states that the results of these operations are undefined.

  • Divide by zero. An attempt to compute the quotient or remainder of a fraction in which the divisor is zero.
  • Invalid shift. An attempt to shift a value by an amount which is either (a) greater than or equal to the width of the value, or (b) a negative amount.