CertC++-ERR50¶
Do not abruptly terminate the program
Required inputs: IR, StaticSemanticAnalysis
The
std::abort(),
std::quick_exit(), and
std::_Exit() functions are used to terminate the program in an
immediate fashion. They do so without calling exit handlers registered
with
std::atexit() and without executing destructors for objects with
automatic, thread, or static storage duration. How a system manages open
streams when a program ends is
implementation-defined
[
ISO/IEC
9899:1999]. Open streams with unwritten buffered data may or may not
be flushed, open streams may or may not be closed, and temporary files may or
may not be removed. Because these functions can leave external resources, such
as files and network communications, in an indeterminate state, they should be
called explicitly only in direct response to a critical error in the
application. (See ERR50-CPP-EX1 for more information.)
The
std::terminate() function calls the current
terminate_handler function, which defaults to calling
std::abort().
The C++ Standard defines several ways in which
std::terminate() may be called implicitly by an
implementation
[
ISO/IEC
14882-2014]:
- When the exception handling mechanism, after completing the initialization of
the exception object but before activation of a handler for the exception,
calls a function that exits via an exception ([except.throw], paragraph 7)
- See ERR60-CPP. Exception objects must be nothrow copy constructible for more information.
- When a throw-expression with no operand attempts to rethrow an exception and no exception is being handled ([except.throw], paragraph 9)
- When the exception handling mechanism cannot find a handler for a thrown
exception ([except.handle], paragraph 9)
- See ERR51-CPP. Handle all exceptions for more information.
- When the search for a handler encounters the outermost block of a function with
a noexcept-specification that does not allow the exception
([except.spec], paragraph 9)
- See ERR55-CPP. Honor exception specifications for more information.
- When the destruction of an object during stack unwinding terminates by throwing
an exception ([except.ctor], paragraph 3)
- See DCL57-CPP. Do not let exceptions escape from destructors or deallocation functions for more information.
- When initialization of a nonlocal variable with static or thread storage
duration exits via an exception ([basic.start.init], paragraph
6)
- See ERR58-CPP. Handle all exceptions thrown before main() begins executing for more information.
- When destruction of an object with static or thread storage duration exits via
an exception ([basic.start.term], paragraph 1)
- See DCL57-CPP. Do not let exceptions escape from destructors or deallocation functions for more information.
- When execution of a function registered with
std::atexit()orstd::at_quick_exit()exits via an exception ([support.start.term], paragraphs 8 and 12) - When the implementation's default unexpected exception handler is called
([except.unexpected], paragraph 2)
Note thatstd::unexpected()is currently deprecated. - When
std::unexpected()throws an exception that is not allowed by the previously violated dynamic-exception-specification, andstd::bad_exception()is not included in that dynamic-exception-specification ([except.unexpected], paragraph 3)
- When the function
std::nested_exception::rethrow_nested()is called for an object that has captured no exception ([except.nested], paragraph 4)
- When execution of the initial function of a thread exits via an exception
([thread.thread.constr], paragraph 5)
- See ERR51-CPP. Handle all exceptions for more information.
- When the destructor is invoked on an object of type
std::threadthat refers to a joinable thread ([thread.thread.destr], paragraph 1) - When the copy assignment operator is invoked on an object of type
std::threadthat refers to a joinable thread ([thread.thread.assign], paragraph 1)
- When calling
condition_variable::wait(),condition_variable::wait_until(), orcondition_variable::wait_for()results in a failure to meet the postcondition:lock.owns_lock() == trueorlock.mutex()is not locked by the calling thread ([thread.condition.condvar], paragraphs 11, 16, 21, 28, 33, and 40) - When calling
condition_variable_any::wait(),condition_variable_any::wait_until(), orcondition_variable_any::wait_for()results in a failure to meet the postcondition:lockis not locked by the calling thread ([thread.condition.condvarany], paragraphs 11, 16, and 22)
In many circumstances, the call stack will not be unwound in response to an
implicit call to
std::terminate(), and in a few cases, it is implementation-defined
whether or not stack unwinding will occur. The C++
Standard, [except.terminate], paragraph 2 [
ISO/IEC
14882-2014], in part, states the following:
In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before
std::terminate()is called. In the situation where the search for a handler encounters the outermost block of a function with a noexcept-specification that does not allow the exception, it is implementation-defined whether the stack is unwound, unwound partially, or not unwound at all beforestd::terminate()is called. In all other situations, the stack shall not be unwound beforestd::terminate()is called.
Do not explicitly or implicitly call
std::quick_exit(),
std::abort(), or
std::_Exit(). When the default
terminate_handler is installed or the current
terminate_handler responds by calling
std::abort() or
std::_Exit(), do not explicitly or implicitly call
std::terminate().
Abnormal
process termination is the typical vector for
denial-of-service
attacks.
The
std::exit() function is more complex. The C++ Standard,
[basic.start.main], paragraph 4, states:
Terminating the program without leaving the current block (e.g., by calling the function std::exit(int) (17.5)) does not destroy any objects with automatic storage duration (11.4.6). If std::exit is called to end a program during the destruction of an object with static or thread storage duration, the program has undefined behavior.
You may call
std::exit() only in a program that has not yet initialized any
objects with automatic storage duration.
Noncompliant Code Example
In this noncompliant code example, the call to
f(), which was registered as an exit handler with
std::at_exit(), may result in a call to
std::terminate() because
throwing_func() may throw an exception.
#include <cstdlib>
void throwing_func() noexcept(false);
void f() { // Not invoked by the program except as an exit handler.
throwing_func();
}
int main() {
if (0 != std::atexit(f)) {
// Handle error
}
// ...
}
Compliant Solution
In this compliant solution,
f() handles all exceptions thrown by
throwing_func() and does not rethrow.
#include <cstdlib>
void throwing_func() noexcept(false);
void f() { // Not invoked by the program except as an exit handler.
try {
throwing_func();
} catch (...) {
// Handle error
}
}
int main() {
if (0 != std::atexit(f)) {
// Handle error
}
// ...
}
Exceptions
ERR50-CPP-EX1: It is acceptable, after indicating the
nature of the problem to the operator, to explicitly call
std::abort(),
std::_Exit(), or
std::terminate() in response to
a critical program error for which no recovery is possible, as in this example.
#include <exception>
void report(const char *msg) noexcept;
[[noreturn]] void fast_fail(const char *msg) {
// Report error message to operator
report(msg);
// Terminate
std::terminate();
}
void critical_function_that_fails() noexcept(false);
void f() {
try {
critical_function_that_fails();
} catch (...) {
fast_fail("Critical function failure");
}
}
The
assert() macro is permissible under this exception because failed
assertions will notify the operator on the standard error stream in an
implementation-defined
manner before calling
std::abort().
Risk Assessment
Allowing the application to abnormally terminate can lead to resources not being freed, closed, and so on. It is frequently a vector for denial-of-service attacks.
| Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| ERR50-CPP | Low | Probable | Medium | P4 | L3 |
Related Guidelines
| SEI CERT C++ Coding Standard |
ERR51-CPP.
Handle all exceptions ERR55-CPP. Honor exception specifications DCL57-CPP. Do not let exceptions escape from destructors or deallocation functions |
| MITRE CWE | CWE-754, Improper Check for Unusual or Exceptional Conditions |
Bibliography
| [ ISO/IEC 9899-2011] | Subclause 7.20.4.1, "The
abort Function"Subclause 7.20.4.4, "The _Exit Function"
|
| [ ISO/IEC 14882-2014] | Subclause 15.5.1, "The
std::terminate() Function"Subclause 18.5, "Start and Termination" |
| [ MISRA 2008] | Rule 15-3-2 (Advisory) Rule 15-3-4 (Required) |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
exception_escaping_constructor |
Escaping exception from constructor. |
None |
False |
exception_escaping_destructor |
Escaping exception from destructor. |
None |
False |
exception_escaping_initialization |
Uncaught exception raised in initialization or finalization |
None |
False |
exception_escaping_main |
Uncaught exception escaping from main or additional entry point |
None |
False |
exception_specification_violation |
Exception violates function’s exception-specification. |
None |
False |
exception_specification_violation_from_call |
Exceptions propagated from this call violate function’s exception-specification. |
None |
False |
forbidden_libfunc_call |
Call to forbidden function. |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
allowed_exceptions¶
allowed_exceptions : set[str] = {'bad_alloc', 'bad_cast', 'failure', 'runtime_error', 'system_error'}
blacklist¶
blacklist
Dictionary of header globbing to (list of) function name globbing(s) of forbidden functions.Type: dict[bauhaus.analysis.config.FileGlobPattern, list[bauhaus.analysis.config.GlobPattern]]
Default:
{ 'cstdlib': ['_Exit', 'quick_exit'], 'stdlib.h': ['_Exit', 'quick_exit', 'abort'], 'unistd.h': ['_exit'] }
constructors¶
constructors : bool = False
destructors¶
destructors : bool = True
exclude_exception_base_classes¶
exclude_exception_base_classes : set[bauhaus.analysis.config.QualifiedName] = set()
generate_violation_path¶
generate_violation_path : bool = True
ignore_constructor_destructor¶
ignore_constructor_destructor : bool = False
ignore_unknown_routines¶
ignore_unknown_routines : bool = False
inspect_at_exit_handlers¶
inspect_at_exit_handlers : bool = True
at_exit() handlers-functions.
inspect_atexit_entry_points¶
inspect_atexit_entry_points : bool = False
inspect_thread_main¶
inspect_thread_main : bool = True
report_at_call¶
report_at_call : bool = False
report_only_one_exception_per_function¶
report_only_one_exception_per_function : bool = False