CertC++-ERR58

Handle all exceptions thrown before main() begins executing

Required inputs: IR, StaticSemanticAnalysis

Not all exceptions can be caught, even with careful use of function-try-blocks. The C++ Standard, [except.handle], paragraph 13 [ ISO/IEC 14882-2014], states the following:

Exceptions thrown in destructors of objects with static storage duration or in constructors of namespace scope objects with static storage duration are not caught by a function-try-block on main() . Exceptions thrown in destructors of objects with thread storage duration or in constructors of namespace-scope objects with thread storage duration are not caught by a function-try-block on the initial function of the thread.

When declaring an object with static or thread storage duration, and that object is not declared within a function block scope, the type's constructor must be declared  noexcept and must comply with  ERR55-CPP. Honor exception specifications. Additionally, the initializer for such a declaration, if any, must not throw an uncaught exception (including from any implicitly constructed objects that are created as a part of the initialization). If an uncaught exception is thrown before main() is executed, or if an uncaught exception is thrown after main() has finished executing, there are no further opportunities to handle the exception and it results in implementation-defined behavior. (See  ERR50-CPP. Do not abruptly terminate the program for further details.)

For more information on exception specifications of destructors, see DCL57-CPP. Do not let exceptions escape from destructors or deallocation functions.

Noncompliant Code Example

In this noncompliant example, the constructor for S may throw an exception that is not caught when  globalS is constructed during program startup.

struct S {
  S() noexcept(false);
};
 
static S globalS;
Compliant Solution

This compliant solution makes  globalS into a local variable with static storage duration, allowing any exceptions thrown during object construction to be caught because the constructor for  S will be executed the first time the function  globalS() is called rather than at program startup. This solution does require the programmer to modify source code so that previous uses of  globalS are replaced by a function call to  globalS().

struct S {
  S() noexcept(false);
};
 
S &globalS() {
  try {
    static S s;
    return s;
  } catch (...) {
    // Handle error, perhaps by logging it and gracefully terminating the application.
  }
  // Unreachable.
}
Noncompliant Code Example

In this noncompliant example, the constructor of global may throw an exception during program startup. (The std::string constructor, which accepts a const char * and a default allocator object, is not marked noexcept and consequently allows all exceptions.) This exception is not caught by the function-try-block on main(), resulting in a call to std::terminate() and abnormal program termination.

#include <string>

static const std::string global("...");

int main()
try {
  // ...
} catch(...) {
  // IMPORTANT: Will not catch exceptions thrown
  // from the constructor of global
}
Compliant Solution

Compliant code must prevent exceptions from escaping during program startup and termination. This compliant solution avoids defining a std::string at global namespace scope and instead uses a static const char *.

static const char *global = "...";

int main() {
  // ...
}
Compliant Solution

This compliant solution introduces a class derived from  std::string with a constructor that catches all exceptions with a function try block and terminates the application in accordance with ERR50-CPP-EX1 in  ERR50-CPP. Do not abruptly terminate the program in the event any exceptions are thrown. Because no exceptions can escape the constructor, it is marked  noexcept and the class type is permissible to use in the declaration or initialization of a static global variable.

For brevity, the full interface for such a type is not described.

#include <exception>
#include <string>

namespace my {
struct string : std::string {
  explicit string(const char *msg,
                  const std::string::allocator_type &alloc = std::string::allocator_type{}) noexcept
  try : std::string(msg, alloc) {} catch(...) {
    extern void log_message(const char *) noexcept;
    log_message("std::string constructor threw an exception");
    std::terminate();
  }
  // ...
};
}
 
static const my::string global("...");

int main() {
  // ...
}
Noncompliant Code Example

In this noncompliant example, an exception may be thrown by the initializer for the static global variable  i.

extern int f() noexcept(false);
int i = f();
 
int main() {
  // ...
}
Compliant Solution

This compliant solution wraps the call to  f() with a helper function that catches all exceptions and terminates the program in conformance with ERR50-CPP-EX1 of  ERR50-CPP. Do not abruptly terminate the program.

#include <exception>
 
int f_helper() noexcept {
  try {
    extern int f() noexcept(false);
    return f();
  } catch (...) {
    extern void log_message(const char *) noexcept;
    log_message("f() threw an exception");
    std::terminate();
  }
  // Unreachable.
}
 
int i = f_helper();

int main() {
  // ...
}
Risk Assessment

Throwing an exception that cannot be caught results in  abnormal program termination and can lead to denial-of-service attacks.

Rule Severity Likelihood Remediation Cost Priority Level
ERR58-CPP Low Likely Low P9 L2
Related Guidelines

This rule is a subset of  ERR50-CPP. Do not abruptly terminate the program

  SEI CERT C++ Coding Standard DCL57-CPP. Do not let exceptions escape from destructors or deallocation functions
ERR55-CPP. Honor exception specifications
Bibliography
[ ISO/IEC 14882-2014] Subclause 15.4, "Exception Specifications"
[ Sutter 2000] Item 8, "Writing Exception-Safe Code-Part 1"
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/err58-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_initialization

Uncaught exception raised in initialization or finalization

None

False

exception_raised_in_initialization

Exception raised in initialization or finalization

None

False

Options

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.
 

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.
 

report_only_uncaught

report_only_uncaught : bool = False

Whether the check shall report all throws or just those not caught.