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
errno
is 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 validerrno
values before adding new ones. - There are major limitations on calling
f()
from other application-independent code. Becausef()
setsmy_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 |
| Small | no | n/a |
Risk Analysis
Lack of an error-detection mechanism prevents applications from knowing when an error has disrupted normal program behavior.
...