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 | ||
|---|---|---|
| ||
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 | ||
|---|---|---|
| ||
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 isNULL, 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
errnois difficult and bug-prone because one the programmer must be precisely aware of when C library functions set and clearerrno, and one also must be aware of all validerrnovalues before adding new ones. - There are major limitations on calling
f()from other application-independent code. Becausef()setsmy_errnoto 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 |
| Small | no | n/a |
Risk Analysis
Lack of an error-detection mechanism prevents applications from knowing when an error has disrupted normal program behavior.
...