Main Content

CERT C++: ERR57-CPP

Do not leak resources when handling exceptions

Since R2021a

Description

Rule Definition

Do not leak resources when handling exceptions.1

Polyspace Implementation

The rule checker checks for these issues:

  • Resource leak caused by exception

  • Object left in partially initialized state

  • Bad allocation in constructor

Examples

expand all

Issue

Resource leak caused by exception occurs when a function raises an unhandled exception by using a throw statement but does not deallocate the resources that were allocated before the exception.

Risk

When a function raises an unhandled exception it immediately goes out of scope. If the function manages resources and they are not deallocated prior to raising the exception, the resource is leaked. Consider this code:

FILE* FilePtr; 
//...
void foo(){
	FilePtr = fopen("some_file.txt", "r");
//...
	if(/*error condition*/)
	throw ERROR_CODE;
	//...
	fclose(FilePtr);
}
The allocated file pointer is intended to be deallocated before the function finishes execution. When an exception takes place, the function exits without deleting the pointer, which results in a resource leak.

Fix

To fix this defect, a function must set all resources that it allocates to a valid state before it goes out of scope. In the preceding code example, the function must delete the pointer FilePtr before the throw statement.

Instead of manually tracking the allocation and deallocation of resources, the best practice is to follow either the Resource Acquisition Is Initialization (RAII) or the Constructor Acquires, Destructor Releases (CADre) design patterns. Resource allocation is performed in constructors and resource deallocation is performed in destructors. The lifecycle of resources are controlled by scope-bound objects. When functions reach the end of their scope, the acquired resources are properly released. Consider this code:

void releaseFile(std::FILE* fp) { std::fclose(fp); }
std::unique_ptr<std::FILE, decltype(&releaseFile)> FilePtr;
//...
void foo(){
	FilePtr(std::fopen("some_file.txt"),&releaseFile);
//...
	if(/*error condition*/)
	throw ERROR_CODE;
}
The unique pointer FilePTR invokes the function releaseFile to delete the allocated resource once the function foo reaches the end of its scope. Whether the function exits normally with an unhandled exception, the allocated resources are deallocated.

C++ smart pointers such as std::unique_ptr and std::shared_ptr follow the RAII pattern. They simplify managing the lifecycle of resources during exception handling. Whenever possible, avoid using raw pointers.

Example — Resource Leak Caused by Exception
#include <cstdint>
#include <memory>
#include <stdexcept>
extern int sensorFlag() noexcept;
namespace Noncompliant{
	void func(){
		int* intPtr = new int;
		int data = sensorFlag();
		if(data==-1)//Error
		throw std::runtime_error("Unexpected value");//Noncompliant
		//...
		delete intPtr;
	}
}

In this example, the function Noncompliant::func() manages the raw pointer inPtr. The function allocates memory for it, and then releases the memory after some operations. The function exits with an exception when data is -1. In this case, the function exits before releasing the allocated memory, resulting in a memory leak. Polyspace® flags the throw statement.

Correction — Deallocate Resources Before throw Statements

To prevent memory leak, the allocated memory must be released before raising the exception, as shown in Compliant::func.

The best practice is to follow the RAII design pattern. For instance, when C++14 is available, use unique_ptr instead of a raw pointer. BestPractice::func shows an implementation of func that follows the RAII pattern. The memory lifecycle is managed by the object itself. That is, once func is out of scope, the smart pointer intPtr deletes itself and releases the memory. Because the memory management is performed correctly by the smart pointer, BestPractice::func is simpler and safer.

#include <cstdint>
#include <memory>
#include <stdexcept>

extern int sensorFlag() noexcept;
 namespace Compliant{
	void func(){
		int* intPtr = new int;
		int data = sensorFlag();
		if(data==-1){//Error
			delete intPtr;
			throw std::runtime_error("Unexpected value");//Compliant
		}
		//...
		delete intPtr;
	}
}
namespace BestPractice{// C++14
	void func(){
		std::unique_ptr<int> intPtr = std::make_unique<int>();
		int data = sensorFlag();
		if(data==-1){//Error
			throw std::runtime_error("Unexpected value");//Compliant
		}
		//...
		
	}
}
Issue

Object left in partially initialized state occurs when a noexcept(false) constructor raises an unhandled exception but does not deallocate the resources that were allocated before the exception. This issue is detected only in classes that your code uses.

Risk

A constructor goes out of scope when it raises an unhandled exception. If the constructor manages resources and they are not deallocated prior to raising the exception, the object is left in a partially initialized state. This behavior is undefined and can produce unexpected results.

Fix

To fix this defect, keep track of the allocated resources and deallocate them before raising exception.

Instead of manually tracking the allocation and deallocation of resources, the best practice is to follow either the Resource Acquisition Is Initialization (RAII) or the Constructor Acquires, Destructor Releases (CADre) design patterns. Resource allocation is performed in constructors and resource deallocation is performed in destructors. The lifecycle of resources are controlled by scope-bound objects. When functions reach the end of their scope, the acquired resources are properly released. Consider this code:

class complex_ptr{
	complex_ptr() = default;
	~complex_ptr() = default;
	private:
	std::unique_ptr<std::complex<double> > z;
	
};
The class complex_ptr uses the implicit default constructor because the resource management is performed by the smart pointer class unique_ptr. The default constructor does not raise exceptions and the object is not left in a partially initialized state.

C++ smart pointers such as std::unique_ptr and std::shared_ptr follow the RAII pattern. They simplify managing the lifecycle of resources during exception handling. Whenever possible, avoid using raw pointers.

Example — Partially Constructed Object Caused by Exceptions
##include<cstdlib>
#include<exception>

class complex_ptr{
	
	complex_ptr(){
		real = (double*)malloc(sizeof(double));
		imag = (double*)malloc(sizeof(double));
		if(real==nullptr || imag==nullptr){
			throw std::exception(); //Noncompliant
		}
	}
	~complex_ptr(){
		free(real);
		free(imag);
	}
	private:
	double* real;
	double* imag;
	
};

void foo(void){
	complex_ptr Z;
	//...
}

In this example, the class complex_ptr is responsible for allocating and deallocating two raw pointers to double. The constructor complex_ptr::complex_ptr() terminates with an exception when a memory allocation operation fails. The constructor exits without deallocating the allocated resources, resulting in a partially constructed object. Polyspace flags the throw statement in the constructor.

Correction — Deallocate Resources Before raising Exceptions in Constructors

To correct this defect, deallocate the allocated resources before raising exceptions in constructor. In this code, before raising the exception, the constructor deallocates the allocated memory by calling deallocate(). This constructor is compliant with this rule.

#include<cstdlib>
#include<exception>

class complex_ptr{
	
	complex_ptr(){
		real = (double*)malloc(sizeof(double));
		imag = (double*)malloc(sizeof(double));
		if(real==nullptr || imag==nullptr){
			deallocate();
			throw std::exception();  //Compliant
		}
	}
	void deallocate(){
		free(real);
		free(imag);
	}
	~complex_ptr(){
		deallocate();
	}
	private:
	double* real;
	double* imag;
	
};

void foo(void){
	complex_ptr Z;
	//...
}
Issue

Bad allocation in constructor occurs when a new operation is performed in a constructor without using the argument std::nothrow or outside exception handling blocks such as try or function-try.

Risk

The new operations might fail and raise a std::bad_alloc exception. If these statements are not enclosed in a try or function-try block, the exception might cause an abrupt termination of a constructor. Such an abrupt termination might leave the object in a partially constructed state, which is undefined behavior in the C++ standard.

Fix

When using the new operator, enclose it in a try or function-try block.

Example — Bad Allocation in Constructors
#include<cstdlib>
#include <stdexcept>
#include <new>
 class complex_ptr{
	
	complex_ptr(): real(new double), imag(new double){ //Noncompliant		
		
	}
	~complex_ptr(){
		delete real;
		delete imag;
	}
	private:
	double* real;
	double* imag;
	
};
void foo(void){
	complex_ptr Z;
	//...
}

In this example, the constructor of complex_ptr performs new operations that might raise exceptions. Because the constructor has no mechanism for handling these exceptions, they might cause the constructor to abruptly terminate. Such termination might leave the object in partially defined state because the allocated resources are not deallocated. Polyspace flags the constructor.

Correction — Handle Exceptions Arising from new Operations in Constructors

To correct this defect, perform the new operation in a try or function-try block.

#include<cstdlib>
#include <stdexcept>
#include <new>
 class complex_ptr{
	
	complex_ptr()try: real(new double), imag(new double){	//Compliant	
		
	}catch(std::bad_alloc){
		//...
	}
	~complex_ptr(){
		delete real;
		delete imag;
	}
	private:
	double* real;
	double* imag;
	
};
void foo(void){
	complex_ptr Z;
	//...
}

Check Information

Group: Rule 08. Exceptions and Error Handling (ERR)

Version History

Introduced in R2021a


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.