Rule A15-0-5 (required, architecture / design /implementation, non-automated)
Checked exceptions shall be used to represent errors from which the caller can reasonably be expected to recover.
Rationale
All expected by the caller, but also reasonable to recover from, problems are represented with instances of checked exceptions. Such problems include input/output and other application’s runtime errors. It is possible to handle such errors in a meaningful way. “Overuse of checked exceptions can make an API far less pleasant to use. If a method throws one or more checked exceptions, the code that invokes the method must handle the exceptions in one or more catch blocks, or it must declare that it throws the exceptions and let them propagate outward. Either way, it places a nontrivial burden on the programmer. The burden is justified if the exceptional condition cannot be prevented by proper use of the API and the programmer using the API can take some useful action once confronted with the exception. Unless both of these conditions hold, an unchecked exception is more appropriate.” [Effective Java 2nd Edition [15]]
Example
//% $Id: A15-0-5.cpp 309502 2018-02-28 09:17:39Z michal.szczepankiewicz $
#include <cstdint>
#include <stdexcept>
#include <system_error>
// @checkedException
class CommunicationError
: public std::exception // Compliant - communication error is "checked"
{
public:
explicit CommunicationError(const char* message) : msg(message) {}
CommunicationError(CommunicationError const&) noexcept = default;
CommunicationError& operator=(CommunicationError const&) noexcept = default;
~CommunicationError() override = default;
const char* what() const noexcept override { return msg; }
private:
const char* msg;
};
// @checkedException
class BusError
: public CommunicationError // Compliant - bus error is "checked"
{
public:
using CommunicationError::CommunicationError;
};
// @checkedException
class Timeout : public std::runtime_error // Compliant - communication timeout
// is "checked"
{
public:
using std::runtime_error::runtime_error;
};
// @checkedException
class PreconditionsError : public std::exception // Non-compliant - error on
// preconditions
check should
// be "unchecked" but is
// defined to be
"checked"
{
// Implementation
};
void Fn1(std::uint8_t* buffer, std::uint8_t bufferLength) noexcept(false)
{
bool sentSuccessfully = true;
// ...
if (!sentSuccessfully)
{
throw CommunicationError(
"Could not send data");
}
}
// Checked exception thrown correctly
void Fn2(std::uint8_t* buffer, std::uint8_t bufferLength) noexcept(false)
{
bool initSuccessfully = true;
if (!initSuccessfully)
{
throw PreconditionsError(); // An exception thrown on preconditions
// check failure should be "Unchecked", but
// PreconditionsError is "Checked"
}
// ...
bool sentSuccessfully = true;
bool isTimeout = false;
// ...
if (!sentSuccessfully)
{
throw BusError(
"Could not send data");
}
// Checked exception thrown correctly
// ...
if (isTimeout)
{
throw Timeout("Timeout reached"); // Checked exception thrown correctly
}
}
void Fn3(std::uint8_t* buffer) noexcept(false)
{
bool isResourceBusy = false;
// ...
if (isResourceBusy)
{
throw std::runtime_error(
"Resource is busy now");
}
}
class Thread // Class which mimics the std::thread
{
public:
// Implementation
// Checked exception thrown correctly
Thread() noexcept(false)
{
bool resourcesAvailable = false;
// ...
if (!resourcesAvailable)
{
throw std::system_error(
static_cast<int>(std::errc::resource_unavailable_try_again),
std::generic_category()); // Compliant - correct usage of
// checked exception system_error
}
}
};
See also
Effective Java: Item 58 - Use checked exceptions for recoverable conditions and runtime exceptions for programming errors.
Rule A15-0-6 (required, verification / toolchain, non-automated)An analysis shall be performed to analyze the failure modes of exception handling. In particular, the following failure modes shall be analyzed: (a) worst time execution time not existing or cannot be determined, (b) stack not correctly unwound, (c) exception not thrown, other exception thrown, wrong catch activated, (d) memory not available while exception handling.
Rationale
Note that the worst-case execution time and behavior of exception handling can be hardware specific. This rule requires only that the exception handling is deterministic in the sense that it has a deterministic behavior. Note: this analysis can be performed by the compiler supplier or it can be done by the project.
## See also
none
Rule A15-0-7 (required, verification / toolchain, partially automated)Exception handling mechanism shall guarantee a deterministic worstcase time execution time.
## Rationale
Compilers, i.e. GCC or Clang, uses dynamic memory allocation in order to allocate
currently thrown exception in their exception handling mechanism implementations.
This causes a non-deterministic execution time and run-time allocation errors. A
possible working approach is to modify the memory allocator so that the dynamic
memory does not need to be obtained (from OS) when an exception is thrown.
A static code analysis can search for a use of dynamic memory in the implementation of
the try/catch mechanism of the compiler, to show if worst-case time cannot be ensured.
GCC compiler uses following gcc library’s functions to provide exception handling
mechanism routines:
__cxa_allocate_exception
__cxa_throw
__cxa_free_exception
__cxa_begin_catch
__cxa_end_catch
Specific stack unwinding functions, i.e. _Unwind_RaiseException,
_Unwind_Resume, _Unwind_DeleteException, etc.
## Example
```cpp
//% $Id: A15-0-7.cpp 289436 2017-10-04 10:45:23Z michal.szczepankiewicz $
#include <cstdlib>
#include <cstring>
struct CxaException
{
// Exception’s structure implementation
};
extern "C" void FatalError(const char* msg)
{
// Reports an error and terminates the program
}
extern "C" void* CxaAllocateExceptionDynamically(size_t thrownSize)
{
size_t size = thrownSize + sizeof(CxaException);
CxaException* buffer = static_cast<CxaException*>(
malloc(size)); // Non-compliant - dynamic memory allocation used
if (!buffer)
{
FatalError("Not enough memory to allocate exception!");
}
memset(buffer, 0, sizeof(CxaException));
return buffer + 1;
}
extern "C" void* StaticMalloc(size_t size)
{
void* mem = NULL;
// Allocates memory using static memory pool
return mem;
}
extern "C" void* CxaAllocateExceptionStatically(size_t thrownSize)
{
size_t size = thrownSize + sizeof(CxaException);
CxaException* buffer = static_cast<CxaException*>(StaticMalloc(
size)); // Compliant - memory allocation on static memory pool used
if (!buffer)
{
FatalError("Not enough memory to allocate exception!");
}
memset(buffer, 0, sizeof(CxaException));
return buffer + 1;
}
See also
none
Rule A15-0-8 (required, verification / toolchain, non-automated)A worst-case execution time (WCET) analysis shall be performed to determine maximum execution time constraints of the software, covering in particular the exceptions processing.
Rationale
Some systems require a guarantee that an action will be performed within predictable time constraints. Such real-time systems are allowed to use exception handling mechanism only if there is a tool support for accurate predicting such maximum time boundaries. “Before deciding that you cannot afford or don’t like exception-based error handling, have a look at the alternatives; they have their own complexities and problems. Also, as far as possible, measure before making claims about efficiency.” [C++ Core Guidelines]
## See also
MISRA C++ 2008 [7]: 15-0-1 (Document) Exceptions shall only be used for
error handling.
open-std.org [18]: ISO/IEC TR 18015:2006(E). Technical Report on C++
Performance