Thrown exceptions that are not explicitly caught subject a running program to implementation-defined behavior culminating in abnormal termination. According to section 15.3 "Handling an Exception" of C++2003 [ISO/IEC 14882-2003]:
If no matching handler is found in a program, the function
std::terminate()
is called; whether or not the stack is unwound before this call tostd::terminate()
is implementation-defined (15.5.1).
The effects of std::terminate()
are to call the terminate_handler
function in effect immediately after evaluating the throw-expression. The default terminate_handler
calls std::abort()
, which has the effect of causing abnormal process termination to occur. Abnormal process termination is the typical vector for denial of service attacks. A user-defined terminate_handler
may be set by calling std::set_terminate()
. In either case, std::terminate()
must not return. Attempting to return from a user-defined terminate_handler
or from a SIGABRT
handler invoked as a result of calling std::abort()
leads to undefined behavior.
Consequently, programs should take steps to prevent std::terminate()
from being invoked for at least two reasons:
- If the stack is not unwound then destructors of local objects are not invoked, acquired system-wide or application-wide resources may not be released, file buffers are not flushed, database transactions are not committed or rolled back, etc.
- Since failing to catch an exception involves implementation-defined behavior, to comply MSC14-CPP. Do not introduce unnecessary platform dependencies.
Rather, programs should catch all exceptions and attempt to recover at the earliest opportunity. Under the rare circumstances when recovery is not feasible (for example, when a logic error is detected), programs should gracefully terminate after indicating the nature of the problem to the operator. See also ERR04-CPP. Choose an appropriate termination strategy.
Non-Compliant Code Example (main()
)
In this example, main()
does several useful work but does not catch any exceptions. Consequently, any exceptions thrown will call std::terminate()
, and might not destroy any objects owned by the program.
int main(int argc, char** argv) { Object object; // might not get destroyed if exception thrown // do useful work return 0; }
Compliant Solution (main()
)
In this code example, all exceptions are caught, allowing normal termination, even in the face of unexpected errors (albeit with an exit status indicating that an error occurred).
int main(int argc, char** argv) { Object object; int exit_status = EXIT_SUCCESS; try { // do useful work } catch (...) { exit_status = EXIT_FAILURE; } return exit_status; // object gets destroyed here }
Compliant Solution (main()
)
An alternative is to wrap all of main()
's functionality inside a try-catch block and catch and handle exceptions by exiting with a status indicating an error to the invoking process.
int main(int argc, char** argv) { try { Object object; // do useful work return 0; // object gets destroyed here } catch (...) { exit(EXIT_FAILURE); } }
Non-Compliant Code Example (throw()
Declaration)
A function that declares exception specifications must list all unrelated exception classes that might be thrown during its invocation. If an exception is thrown that is not related to any of those listed in its exception specification, control automatically reverts to std::unexpected()
, which does not return.
In the following code example, the function f()
claims to throw exception1
but actually throws exception2
. Consequently control flow is diverted to std::unexpected
, and the toplevel catch
clause may not be invoked. (It is not invoked on Linux with G++ 4.3).
using namespace std; class exception1 : public exception {}; class exception2 : public exception {}; void f(void) throw( exception1) { // ... throw (exception2()); } int main() { try { f(); return 0; } catch (...) { cerr << "F called" << endl; } return EXIT_FAILURE; }
Compliant Solution (throw()
Declaration)
The following code example declares the same exception it actually throws
using namespace std; class exception1 : public exception {}; class exception2 : public exception {}; void f(void) throw( exception1) { // ... throw (exception1()); } int main() { try { f(); return 0; } catch (...) { cerr << "F called" << endl; } return EXIT_FAILURE; }
Risk Assessment
Failing to handle exceptions can lead to resources not being freed, closed, etc.
Rule |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
ERR12-C |
1 (low) |
1 (unlikely) |
1 (low) |
P1 |
L3 |
Other Languages
This rule appears in the Java Secure Coding Standard as EXC08-J. Try to gracefully recover from system errors.
Bibliography
[ISO/IEC 14882-2003]
[MISRA 08] Rule 15-3-2, 15-3-4
ERR14-CPP. Do not allow an exception class's copy constructor to throw exceptions 12. Exceptions and Error Handling (ERR) ERR31-CPP. Don't redefine errno