Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Application-independent code includes code that is:

  • shipped with the compiler or operating system.
  • from a third-party library.
  • developed in-house.

When application-specific code detects an error, it can respond on the spot with a specific action, as in:

Code Block
if (something_really_bad_happens) {
  take_me_some_place_safe();
}

This is because the application must both detect errors and provide a mechanism for handling errors. But because application-independent code is not associated with any application, it cannot handle errors. However, it must still detect errors , and report them to an application , so that the application may handle them.

...

  • a return value (especially of type errno_t)
  • an argument passed by address
  • a global object (e.g., errno)
  • longjmp()
  • some combination of the above

...

One way to indicate errors is to return a value indicating success or errors. This compliant solution changes ensures each function to return returns a value of type errno_t, where 0 indicates that no error has occurred.

Code Block
bgColor#ccccff
const errno_t ESOMETHINGREALLYBAD = 1;

errno_t g(void) {
  /* ... */
  if (something_really_bad_happens) {
    return ESOMETHINGREALLYBAD;
  }
  /* ... */
  return 0;
}

errno_t f(void) {
  errno_t status = g();
  if (status != 0)
    return status;

  /* ... do the rest of f ... */

  return 0;
}

A call to f() returns a status indicator which is zero upon success, and a non-zero value upon failure indicating what went wrong.

A return type of errno_t indicates that the function returns a status indicator (see DCL09-C. Declare functions that return an errno error code with a return type of errno_t).

While this solution error handling approach is secure, it has the following drawbacks:

  • Wiki Markup
    Source and object code can significantly increase in size, perhaps by as much as 30-40% to 40 percent \[[Saks 07b|AA. C References#Saks 07b]\].
  • All function return values must be checked (see MEM32-C. Detect and handle memory allocation errors, among many others.)
  • Functions should not return other values if they return error indicators (see ERR02-C. Avoid in-band error indicators.)
  • Any function that allocates resources must ensure they are freed incases in_cases where errors occur.

Compliant Solution (Address Argument)

...

Code Block
bgColor#ccccff
const errno_t ESOMETHINGREALLYBAD = 1;

void g(errno_t* err) {
  if (err == NULL) {
    /* handleHandle null pointer */
  }
  /* ... */
  if (something_really_bad_happens) {
    *err = ESOMETHINGREALLYBAD;
  } else {
    /* ... */
    *err = 0;
  }
}

void f(errno_t* err) {
  if (err == NULL) {
    /* handleHandle null pointer */
  }
  g(err);
  if (*err == 0) {
    /* ... do the rest of f ... */
  }
  return 0;
}

...

While this solution is secure, it has the following drawbacks:

  • A return status can only be returned only if the caller provides a valid pointer to an object of type errno_t. If this argument is NULL, there is no way to indicate this error.
  • Source code becomes even larger due to the possibilities of receiving a null pointer.
  • All error indicators must be checked after calling functions.
  • Any function that allocates resources must ensure they are freed in cases where errors occur.
  • Unlike return values, static analysis tools generally do not diagnose a failure to check error indicators passed as argument pointers.

...

Instead of encoding error indicators in the return value or arguments, a functions function can indicate its status by assigning a value to a global variable. In the following example, each function uses a static indicator called my_errno.

...

The call to f() provides a status indicator that is zero upon success and a non-zero nonzero value upon failure.

This solution has many of the same properties as those observed with errno, including advantages and drawbacks.

  • Source code size is inflated, though not by as much as in other approaches.
  • All error indicators must be checked after calling functions.
  • Nesting of function calls that all use this mechanism is problematic.
  • Any function that allocates resources must ensure they are freed in cases where errors occur.
  • In general, combining registries of different sets of errors is difficult. For example, changing the above code to use errno is difficult and bug-prone because one the programmer must be precisely aware of when C library functions set and clear errno, and one also must be aware of all valid errno values before adding new ones.
  • There are major limitations on calling f() from other application-independent code. Because f() sets my_errno to 0, it may potentially be overwriting a nonzero error value set by another application-independent calling function.

...

Calls to f() will either succeed or divert control into an if clause designed to catch the error.

  • Source The source code will is not become significantly larger because the function signatures do not change, and neither do functions that neither detect nor handle the error.
  • Allocated resources must still be freed despite the error.
  • The application must call setjmp() before invoking application-independent code.
  • Signals are not necessarily preserved through longjmp() calls.
  • The use of setjmp()/longjmp() bypasses the normal function call and return discipline.
  • Any function that allocates resources must ensure they are freed in cases where errors occur.

Summary

The table below summarizes the characteristics of error reporting and detection mechanisms.

Method

Code Increase

Manages Allocated Resources

Automatically Enforceable

Return Value value

Big (30-40%30—40%)

no

yes

Address Argument argument

Bigger

no

no

Global Indicator indicator

Medium

no

yes

longjmp()

Small

no

n/a

Risk Analysis

Lack of an error-detection mechanism prevents applications from knowing when an error has disrupted normal program behavior.

...