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]:

  1. 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)
  2. When a throw-expression with no operand attempts to rethrow an exception and no exception is being handled ([except.throw], paragraph 9)
  3. When the exception handling mechanism cannot find a handler for a thrown exception ([except.handle], paragraph 9)
  4. 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)
  5. When the destruction of an object during stack unwinding terminates by throwing an exception ([except.ctor], paragraph 3)
  6. When initialization of a nonlocal variable with static or thread storage duration exits via an exception ([basic.start.init], paragraph 6)
  7. When destruction of an object with static or thread storage duration exits via an exception ([basic.start.term], paragraph 1)
  8. When execution of a function registered with std::atexit()or std::at_quick_exit() exits via an exception ([support.start.term], paragraphs 8 and 12)
  9. When the implementation's default unexpected exception handler is called ([except.unexpected], paragraph 2)
    Note that  std::unexpected() is currently deprecated.
  10. When std::unexpected() throws an exception that is not allowed by the previously violated dynamic-exception-specification, and std::bad_exception() is not included in that dynamic-exception-specification ([except.unexpected], paragraph 3)
  11. When the function std::nested_exception::rethrow_nested() is called for an object that has captured no exception ([except.nested], paragraph 4)
  12. When execution of the initial function of a thread exits via an exception ([thread.thread.constr], paragraph 5)
  13. When the destructor is invoked on an object of type std::thread that refers to a joinable thread ([thread.thread.destr], paragraph 1)
  14. When the copy assignment operator is invoked on an object of type  std::thread that refers to a joinable thread ([thread.thread.assign], paragraph 1)
  15. When calling  condition_variable::wait()condition_variable::wait_until(), or condition_variable::wait_for() results in a failure to meet the postcondition:  lock.owns_lock() == true or  lock.mutex() is not locked by the calling thread ([thread.condition.condvar], paragraphs 11, 16, 21, 28, 33, and 40)
  16. When calling  condition_variable_any::wait()condition_variable_any::wait_until(), or  condition_variable_any::wait_for() results in a failure to meet the postcondition:  lock is 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 before std::terminate() is called. In all other situations, the stack shall not be unwound before std::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)
Excerpt from SEI CERT C++ Coding Standard [https://cmu-sei.github.io/secure-coding-standards/sei-cert-cpp-coding-standard/rules/exceptions-and-error-handling-err/err50-cpp], Copyright (C) 1995-2026 Carnegie Mellon University. See section 9.4. "3rd-Party Licenses" in the documentation for full details.

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

allowed_exceptions

allowed_exceptions : set[str] = {'bad_alloc', 'bad_cast', 'failure', 'runtime_error', 'system_error'}

Exceptions that are allowed to escape from destructors.
 

blacklist

blacklist

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']
}
Dictionary of header globbing to (list of) function name globbing(s) of forbidden functions.
 

constructors

constructors : bool = False

Whether to consider constructors.
 

destructors

destructors : bool = True

Whether to consider destructors.
 

exclude_exception_base_classes

exclude_exception_base_classes : set[bauhaus.analysis.config.QualifiedName] = set()

Exclude issues for the exception types mentioned in this set of qualified names. Also excludes classes derived from those class names as well as pointers or references to any of these class types.
 

generate_violation_path

generate_violation_path : bool = True

Whether to compute a trace for the exception. This improves the usability of the violation description, but requires additional computing which might slow down the rule.
 

ignore_constructor_destructor

ignore_constructor_destructor : bool = False

Whether to ignore escaping exceptions from constructors and destructors.
 

ignore_unknown_routines

ignore_unknown_routines : bool = False

Whether to ignore extern or only declared routines.
 

inspect_at_exit_handlers

inspect_at_exit_handlers : bool = True

Whether to also inspect at_exit() handlers-functions.
 

inspect_atexit_entry_points

inspect_atexit_entry_points : bool = False

Whether to inspect routines set by the atexit/std::atexit function.
 

inspect_thread_main

inspect_thread_main : bool = True

Whether to also inspect thread main functions.
 

report_at_call

report_at_call : bool = False

If set to true, the error is reported at the call-sites of routines throwing exceptions rather than at the throw.
 

report_only_one_exception_per_function

report_only_one_exception_per_function : bool = False

Report at most one uncaught exception per function. This suppresses issues at sites where an uncaught exception is thrown to get a faster execution of the check. Setting this parameter to True will result in false negatives: Real issues may not be detected anymore.