Application-independent code includes code that is:
- shipped with the compiler or operating system
- from a third-party library
- developed in-house
...
If something_really_bad_happens
in g()
, the function prints an error message to stderr
and then calls abort()
. The problem is that this application-independent code does not know the context in which it is being called, so it is erroneous to handle the error.
Wiki Markup |
---|
Practice 23 from the Miller, et. al paper on Software Quality Engineering (SQE) practices \[[Miller 04|AA. C References#Miller 04]\] Practice 23 says: |
When a library aborts due to some kind of anomaly, it is saying there is no hope for execution to proceed normally beyond the point where the anomaly is detected. Nonetheless, it is dictatorially making this decision on behalf of the client. Even if the anomaly turns out to be some kind of internal bug in the library, which obviously cannot be resolved in the current execution, aborting is a bad thing to do. The fact is, a library developer cannot possibly know the fault-tolerant context in which his/her library is being used. The client may indeed be able to recover from the situation even if the library cannot.
...
One way to indicate errors is to return a value indicating success or errors. This compliant solution changes each function to return a value of type errno_t
, where 0 indicates that no error has occurred.
Code Block | ||
---|---|---|
| ||
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; } |
A call to f()
returns a status indicator which is zero upon success, and a non-zero value upon failure indicating what went wrong.
Wiki Markup |
---|
A return type of {{errno_t}} 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 and object code can significantly increase in size, perhaps by as much as 30-40% \[[Saks 07b|AA. C References#Saks 07b]\]
Wiki Markup 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].)
- Functions that fail must make sure the free any locally allocated resources.
...
A call to f()
provides a status indicator which is zero upon success, and a non-zero value indicating failure, provided assuming the user provided a valid pointer to an object of type errno_t
.
...
- 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.
- Functions that fail must make sure the to free any locally allocated resources.
- Unlike return values, static analysis tools generally do not diagnose a failure to check error indicators passed as argument pointers.
...
- Source code size is inflated, though not by as much as other approaches.
- All error indicators must be checked after calling functions.
- Nesting of function calls that all use this mechanism is problematic.
- Functions that fail must make sure the to 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 clearerrno
, and one must be aware of all validerrno
values before adding new ones.
...
- 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 of
setjmp()
/longjmp()
bypasses the normal function call and return discipline.
...