Main Content

MISRA C++:2008 Rule 15-3-4

Each exception explicitly thrown in the code shall have a handler of a compatible type in all call paths that could lead to that point

Since R2020b

Description

Rule Definition

Each exception explicitly thrown in the code shall have a handler of a compatible type in all call paths that could lead to that point.

Rationale

In C++, when an operation raises an exception, the compiler tries to match the exception with a compatible exception handler in the current and adjacent scopes. If no compatible exception handler for a raised exception exists, the compiler invokes the function std::terminate() implicitly. The function std::terminate() terminates the program execution in an implementation-defined manner. That is, the exact process of program termination depends on the particular set of software and hardware that you use. For instance, std::terminate() might invoke std::abort() to abnormally abort the execution without unwinding the stack. If the stack is not unwound before program termination, then the destructors of the variables in the stack are not invoked, leading to resource leak and security vulnerabilities.

Consider this code where multiple exceptions are raised in the try block of code.

class General{/*...  */};
class Specific : public General{/*...*/};
class Different{}
void foo() noexcept
{
	try{
		//...
		throw(General e);
		//..
		throw( Specific e);
		// ...
		throw(Different e);
	}
	catch (General& b){

	}
}
The catch block of code accepts references to the base class General. This catch block is compatible with exceptions of the base class General and the derived class Specific. The exception of class Different does not have a compatible handler. This unhandled exception violates this rule and might result in resource leaks and security vulnerabilities.

Because unhandled exceptions can lead to resource leak and security vulnerabilities, match the explicitly raised exceptions in your code with a compatible handler.

Polyspace Implementation

  • Polyspace® flags a throw statement in a function if a compatible catch statement is absent in the call path of the function. If the function is not specified as noexcept, Polyspace ignores it if its call path lacks an entry point like main().

  • Polyspace flags a throw statement that uses a catch(…) statement to handle the raised exceptions.

  • Polyspace does not flag rethrow statements, that is, throw statements within catch blocks.

  • You might have compatible catch blocks for the throw statements in your function in a nested try-catch block Polyspace ignores nested try-catch blocks. Justify throw statements that have compatible catch blocks in a nested structure by using comments. Alternatively, use a single level of try-catch in your functions.

Troubleshooting

If you expect a rule violation but Polyspace does not report it, see Diagnose Why Coding Standard Violations Do Not Appear as Expected.

Examples

expand all

This example shows how Polyspace flags operations that raise exceptions without any compatible handler. Consider this code.

#include <stdexcept>

class MyException : public std::runtime_error {
public:
	MyException() : std::runtime_error("MyException") {}
};

void ThrowingFunc() {
	throw MyException(); //Noncompliant
}

void CompliantCaller() {
	try {
		ThrowingFunc();
	} catch (std::exception& e) {
		/* ... */
	}
}

void NoncompliantCaller() {
	ThrowingFunc(); 
}

int main(void) {
	CompliantCaller();
	NoncompliantCaller(); 
}

void GenericHandler() {
	try {
		throw MyException(); //Noncompliant
	} catch (...) { 
		/* ... */
	}
}

void TrueNoexcept() noexcept {
	try {
		throw MyException();//Compliant
	} catch (std::exception& e) {
		/* ... */
	}
}

void NotNoexcept() noexcept {
	try {
		throw MyException(); //Noncompliant
	} catch (std::logic_error& e) {
		/* ... */
	}
} 
  • The function ThrowingFunc() raises an exception. This function has multiple call paths:

    • main()->CompliantCaller()->ThrowingFunc(): In this call path, the function CompliantCaller() has a catch block that is compatible with the exception raised by ThrowingFunc(). This call path is compliant with the rule.

    • main()->NoncompliantCaller()->ThrowingFunc(): In this call path, there are no compatible handlers for the exception raised by ThrowingFunc(). Polyspace flags the throw statement in ThrowingFunc() and highlights the call path in the code.

    The function main() is the entry point for both of these call paths. If main() is commented out, Polyspace ignores both of these call paths. If you want to analyze a call path that lacks an entry point, specify the top most calling function as noexcept.

  • The function GenericHandler() raises an exception by using a throw statement and handles the raised exception by using a generic catch-all block. Because Polyspace considers such catch-all handler to be incompatible with exceptions that are raised by explicit throw statements, Polyspace flags the throw statement in GenericHandler().

  • The noexcept function TrueNoexcept() contains an explicit throwstatement and a catch block of compatible type. Because this throw statement is matched with a compatible catch block, it is compliant with the rule.

  • The noexcept function NotNoexcept() contains an explicit throw statement, but the catch block is not compatible with the raised exception. Because this throw statement is not matched with a compatible catch block, Polyspace flags the throw statement in NotNoexcept().

Check Information

Group: Exception Handling
Category: Required

Version History

Introduced in R2020b