Versions Compared

Key

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

...

It is equally bad to eliminate the call to abort() from f(). In this case, there will be is no indication back to the calling function that an error has occuredoccurred.

Compliant Solution (Return Value)

One way to indicate errors is to return a value indicating success or errors. The following example This compliant solution changes each function to return a value of type errno_t, where 0 indicates no error 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;
  if ((status = g()) != 0)
    return status;
  /* ... do the rest of f ... */
  return 0;
}

As you can see, calling A call to f() will return returns a status indicator which is zero upon success, and a nonzero non-zero value upon failure indicating what went wrong.

A return type of errno_t is encouraged by indicates that the function returns a status indicator DCL09-A. Declare functions that return an errno error code with a return type of errno_t.

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

  • Wiki Markup
    Source
    code becomes 30-40% larger
     and object code can significantly increase in size, perhaps by as much as 30-40% \[[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-A. Avoid in-band error indicators.)
  • A function Functions that allocates resources must still free them in spite of the errorfail must make sure the free any locally allocated resources.

Compliant Solution (Address Argument)

Instead of encoding status indicators in the return value, one can provide each function with can take a pointer as an argument, which the function is expected used to use to indicate errors. In the following example, each function uses a errno_t* argument to report errors.

Code Block
bgColor#ccccff
const errno_t ESOMETHINGREALLYBAD = 1;

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

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

As you can see, calling A call to f() will provide provides a status indicator which is zero upon success, and a nonzero value indicating what went wrongnon-zero value indicating failure, provided the user provided a valid pointer to an object of type errno_t.

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

  • A return status can only be returned 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.
  • A function Functions that allocates resources must still free them in spite of the error. This is a little easier than with return values, because one has more control over control flow.fail must make sure the free any locally allocated resources.
  • Unlike return values, static analyais analysis tools generally won't notify you if you fail do not diagnose a failure to check error indicators passed as argument pointers.

...

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

...

Code Block
bgColor#ccccff
errno_t my_errno; /* also declared in a .h file */
const errno_t ESOMETHINGREALLYBAD = 1;

void g(void) {
  /* ... */
  if (something_really_bad_happens) {
    my_errno = ESOMETHINGREALLYBAD;
    return;
  }
  /* ... */
}

void f(void) {
  g();
  if (my_errno != 0) {
    return;
  }
  /* ... do the rest of f ... */
}

As you can see, calling The call to f() will provide provides a status indicator which is zero upon success, and a nonzero non-zero value indicating what went wrongfailure.

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

  • Source code still becomes larger, though smaller than the previous examplessize is inflated, though not by as much as other approaches.
  • All error indicators must be checked after calling functions.
  • A function Functions that allocates resources must still free them in spite of the errorfail must make sure the free any locally allocated resources.
  • 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; as one must be precisely aware of when C library functions set and clear errno, and one must be aware of all valid errno values before adding new ones.

For these reasons, among others, this approach is generally discouraged.

Compliant Solution ( setjmp() and longjmp() )

C provides two functions setjmp() and longjmp(), that can be used to alter control flow. This might enable one allows a user of these functions to ignore error values, and entrust that control flow will be correctly diverted in the event of error.

The following example uses setjmp() and longjmp() to ensure that control flow is dirupted disrupted in the event of error, along with and also uses the my_errno indicator from the previous example.

Code Block
bgColor#ccccff
#include <setjmp.h>

const errno_t ESOMETHINGREALLYBAD = 1;

jmp_buf exception_env;

void g(void) {
  /* ... */
  if (something_really_bad_happens) {
    longjmp(exception_env, ESOMETHINGREALLYBAD);
  }
  /* ... */
}

void f(void) {
  g();
  /* ... do the rest of f ... */
}

/* ... */
errno_t err;
if ((err = setjmp(exception_env)) != 0) {
  /* if we get here, an error occurred
     and err indicates what went wrong */
}
/* ... */
f();
/* if we get here, no errors occurred */
/* ... */

As you can see, calling Calls to f() will either succeed, or divert control into an if clause designed to catch the error.

  • Source code will not become significantly larger, because 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.
  • Requires application to call setjmp() before invoking application-independent code.
  • Signals are not necessarily preserved through longjmp() calls.
  • The use setjmp()/longjmp() bypasses the normal function call and return discipline.

Summary

Method

Code Increase

Manages Allocated Resources

Automatically Enforceable

Return Value

Big (30-40%)

no

yes

Address Argument

Bigger

no

no

Global Indicator

Medium

no

yes

longjmp()

Small

no

n/a

...