Main Content

Argument expression of throw statement might raise unexpected exception

The argument expression in a throw statement raises unexpected exceptions, leading to resource leaks and security vulnerabilities

Since R2020b

Description

This defect occurs when the argument expression of a throw statement might raise an exception. Expressions that can raise exceptions include:

  • Functions that are specified as noexcept(false)

  • Functions that contain one or more explicit throw statements

  • Constructors that perform memory allocation operations

  • Expressions that involve dynamic casting

Risk

When raising an exception explicitly by using throw statements, the compiler first creates the expected exception by evaluating the argument of the throw statement, and then raises the expected exception. If an unexpected exception is raised when the compiler is creating the expected exception in a throw statement, the unexpected exception is propagated instead of the expected one. This unexpected exception might become an unhandled exception. Depending on your environment, the compiler might call std::abort to abnormally terminate the program execution without unwinding the stack when exceptions become unhandled, leading to resource leak and security vulnerabilities. Consider this code where a throw statement raises an explicit exception of class myException.

class myException{
	myException(){
		msg = new char[10];
		//...
	}
	//...
};

foo(){
	try{
		//..
		throw myException();
	}
	catch(myException& e){
		//...
	}
}
During construction of the temporary myException object, the new operator can raise a bad_alloc exception. In such a case, the throw statement raises a bad_alloc exception, instead of myException. Because myException was the expected exception, the catch block is incompatible with bad_alloc. The bad_alloc exception becomes an unhandled exception. It might cause the program to abort abnormally without unwinding the stack, leading to resource leak and security vulnerabilities.

Fix

Avoid using expressions that might raise exceptions as argument in a throw statement.

Examples

expand all

int f_throw() noexcept(false);
int foo(){
	try{
		//...
		throw f_throw();
	}
	catch(...){
		//...
	}
}

In this example, the function f_throw() is specified as noexcept(false). If an exception is raised in f_throw(), it can cause the program to terminate without unwinding the stack, resulting in resource leak and security vulnerabilities.

Correction – Use Functions Specified as noexcept(true) as Argument Expression of throw Statements

One possible correction is to use functions that do not raise exceptions in argument expression of a throw statement. These functions are specified as noexcept(true).

int f_throw() noexcept(true);
int foo(){
	try{
		//...
		throw f_throw();
	}
	catch(...){
		//...
	}
}
class WithDynamicAlloc {
public:
	WithDynamicAlloc(int n) {
		m_data = new int[n];   
	}
	~WithDynamicAlloc() {
		delete[] m_data;
	}
private:
	int* m_data;
};
int foo(){
	try{
		//...
		throw WithDynamicAlloc(10); 
	}
	catch(WithDynamicAlloc& e){
		//... 
	}
}

In this example, the constructor of the object WithDynamicAlloc performs a dynamic memory allocation operation. This constructor might raise an exception such as bad_alloc that might cause the program to terminate without unwinding the stack, resulting in resource leak and security vulnerabilities.

Correction – Use Constructors that Does Not Allocate Memory as Argument Expression of throw Statements

One possible correction is to use objects that do not require dynamic memory allocation as exception objects.

class WithoutDynamicAlloc {
public:
	WithoutDynamicAlloc(int n) : m_data(n){   
	}
	~WithoutDynamicAlloc() {
	}
private:
	int m_data;
};
int foo(){
	try{
		//...
		throw WithoutDynamicAlloc(10); 
	}
	catch(WithoutDynamicAlloc& e){
		//... 
	}
}
int MightThrow(bool b) {
	if (b) {
		throw 2.1;
	}
	return 42;
}
int foo(){
	try{
		//...
		throw MightThrow(false);
		throw MightThrow(true);
	}
	catch(int e){
		//... 
	}
}

In this example, the function MightThrow() raises an exception depending on the input b. Because Polyspace® analyzes functions statically, it assumes that MightThrow() raises exceptions regardless of input, and raises a violation of this checker on both throw MightThrow(true) and throw MightThrow(false).

Correction – Use Comments to Justify Result

Because MightThrow(false) does not raise an exception, a possible correction is to use comments to justify the statement throw MightThrow(false). See Address Results in Polyspace User Interface Through Bug Fixes or Justifications

int MightThrow(bool b) {
	if (b) {
		throw 2.1;
	}
	return 42;
}
int foo(){
	try{
		//...
		throw MightThrow(false);// polyspace DEFECT:THROW_THROWS [Justified:Low] "Does not Throw"
		throw MightThrow(true);
	}
	catch(int e){
		//... 
	}
}

Result Information

Group: C++ Exception
Language: C++
Default: On for handwritten code, off for generated code
Command-Line Syntax: THROW_ARGUMENT_EXPRESSION_THROWS
Impact: High

Version History

Introduced in R2020b