CERT C++: ERR57-CPP

Не пропускайте ресурсы при обрабатывании исключений

Описание

Управляйте определением

Не пропускайте ресурсы при обрабатывании исключений.[1]

Реализация Polyspace

Это средство проверки проверяет на эти проблемы:

  • Утечка ресурсов вызывается исключением

  • Объект оставлен в частично инициализированном состоянии

  • Плохое выделение в конструкторе

Примеры

развернуть все

Проблема

Утечка ресурсов, вызванная исключением, происходит, когда функция повышает необработанное исключение при помощи throw оператор, но не освобождает ресурсы, которые были выделены перед исключением.

Риск

Когда функция повышает необработанное исключение, она сразу выходит из осциллографа. Если функция управляет ресурсами, и они не освобождены до повышения исключения, ресурс пропущен. Рассмотрите этот код:

FILE* FilePtr; 
//...
void foo(){
	FilePtr = fopen("some_file.txt", "r");
//...
	if(/*error condition*/)
	throw ERROR_CODE;
	//...
	fclose(FilePtr);
}
Выделенный указатель файла предназначается, чтобы быть освобожденным перед функциональным выполнением концов. Когда исключение происходит, функциональные выходы, не удаляя указатель, который приводит к утечке ресурсов.

Исправление

Чтобы зафиксировать этот дефект, функция должна установить все ресурсы, которые это выделяет допустимому состоянию, прежде чем это выйдет из осциллографа. В предыдущем примере кода функция должна удалить указатель FilePtr перед throw оператор.

Вместо того, чтобы вручную отследить выделение и освобождение ресурсов, лучшая практика должна следовать или за Захватом ресурса является инициализацией (RAII) или за Конструктором, Получает, Релизы Деструктора (КАДРЫ) шаблоны разработки. Распределение ресурсов выполняется в конструкторах, и освобождение ресурса выполняется в деструкторах. Жизненным циклом ресурсов управляют ограниченные осциллографом объекты. Когда функции достигают конца своего осциллографа, полученные средства правильно высвобождены. Рассмотрите этот код:

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;
}
Уникальный указатель FilePTR вызывает функциональный releaseFile удалить выделенный ресурс однажды функциональный foo достигает конца его осциллографа. Выходит ли функция обычно с необработанным исключением, выделенные ресурсы освобождены.

Интеллектуальные указатели C++, такие как std::unique_ptr и std::shared_ptr следуйте за шаблоном RAII. Они упрощают управление жизненный цикл ресурсов во время обработки исключений. Каждый раз, когда возможно, избегайте использования необработанных указателей.

Пример — утечка ресурсов, вызванная исключением
#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;
	}
}

В этом примере, функциональном Noncompliant::func() управляет необработанным указателем inPtr. Функция выделяет память для него, и затем выпускает память после некоторых операций. Функция выходит за исключением когда data -1. В этом случае, функциональные выходы прежде, чем выпустить выделенную память, приводя к утечке памяти. Polyspace® отмечает throw оператор.

Коррекция — освобождает ресурсы перед throw Операторы

Чтобы предотвратить утечку памяти, выделенная память должна быть выпущена прежде, чем повысить исключение, как показано в Compliant::func.

Лучшая практика должна следовать шаблону разработки RAII. Например, когда C++ 14 доступен, используйте unique_ptr вместо необработанного указателя. BestPractice::func показывает реализацию func это следует за шаблоном RAII. Жизненный цикл памяти управляем самим объектом. Таким образом, однажды func вне осциллографа, интеллектуальный указатель intPtr удаляет себя и выпускает память. Поскольку управление памятью выполняется правильно интеллектуальным указателем, BestPractice::func более просто и более безопасен.

#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
		}
		//...
		
	}
}
Проблема

Объект, оставленный в частично инициализированном состоянии, происходит когда noexcept(false) конструктор повышает необработанное исключение, но не освобождает ресурсы, которые были выделены перед исключением. Эта проблема обнаруживается только в классах, которые использует ваш код.

Риск

Конструктор выходит из осциллографа, когда он повышает необработанное исключение. Если конструктор управляет ресурсами, и они не освобождены до повышения исключения, объект оставляют в частично инициализированном состоянии. Это поведение не определено и может привести к неожиданным результатам.

Исправление

Чтобы зафиксировать этот дефект, отслеживайте выделенные ресурсы и освободите их прежде, чем повысить исключение.

Вместо того, чтобы вручную отследить выделение и освобождение ресурсов, лучшая практика должна следовать или за Захватом ресурса является инициализацией (RAII) или за Конструктором, Получает, Релизы Деструктора (КАДРЫ) шаблоны разработки. Распределение ресурсов выполняется в конструкторах, и освобождение ресурса выполняется в деструкторах. Жизненным циклом ресурсов управляют ограниченные осциллографом объекты. Когда функции достигают конца своего осциллографа, полученные средства правильно высвобождены. Рассмотрите этот код:

class complex_ptr{
	complex_ptr() = default;
	~complex_ptr() = default;
	private:
	std::unique_ptr<std::complex<double> > z;
	
};
Класс complex_ptr использует неявного конструктора по умолчанию, потому что управление ресурсами выполняется классом интеллектуального указателя unique_ptr. Конструктор по умолчанию не повышает исключения, и объект не оставляют в частично инициализированном состоянии.

Интеллектуальные указатели C++, такие как std::unique_ptr и std::shared_ptr следуйте за шаблоном RAII. Они упрощают управление жизненный цикл ресурсов во время обработки исключений. Каждый раз, когда возможно, избегайте использования необработанных указателей.

Пример — частично созданный объект, вызванный исключениями
#include<cstdlib>
 class complex_ptr{
	
	complex_ptr(){
		real = (double*)malloc(sizeof(double));
		imag = (double*)malloc(sizeof(double));
		if(real==nullptr || imag==nullptr){
			throw; //Noncompliant
		}
	}
	~complex_ptr(){
		free(real);
		free(imag);
	}
	private:
	double* real;
	double* imag;
	
};void foo(void){
	complex_ptr Z;
	//...
}

В этом примере, класс complex_ptr ответственно за выделение и освобождение двух необработанных указателей на double. Конструктор complex_ptr::complex_ptr() завершает работу за исключением, когда операция выделения памяти перестала работать. Конструктор выходит, не освобождая выделенные ресурсы, приводя к частично созданному объекту. Polyspace отмечает throw оператор в конструкторе.

Коррекция — Освобождает Ресурсы Прежде, чем повысить Исключения в Конструкторах

Чтобы откорректировать этот дефект, освободите выделенные ресурсы прежде, чем повысить исключения в конструкторе. В этом коде, прежде, чем повысить исключение, конструктор освобождает выделенную память путем вызова deallocate(). Этот конструктор совместим с этим правилом.

#include<cstdlib>
 class complex_ptr{
	
	complex_ptr(){
		real = (double*)malloc(sizeof(double));
		imag = (double*)malloc(sizeof(double));
		if(real==nullptr || imag==nullptr){
			deallocate();
			throw; //Compliant
		}
	}
	void deallocate(){
		free(real);
		free(imag);
	}
	~complex_ptr(){
		deallocate();
	}
	private:
	double* real;
	double* imag;
	
};void foo(void){
	complex_ptr Z;
	//...
}
Проблема

Плохое выделение в конструкторе происходит когда new операция выполняется в конструкторе, не используя аргумент std::nothrow или вне блоков обработки исключений, таких как try или function-try.

Риск

new операции могут привести к сбою и повысить std::bad_alloc исключение. Если эти операторы не заключены в try или function-try блокируйтесь, исключение может вызвать резкое завершение конструктора. Такое резкое завершение может оставить объект в частично созданном состоянии, которое является неопределенным поведением в стандарте C++.

Исправление

При использовании new оператор, заключите его в try или function-try блок.

Пример — плохое выделение в конструкторах
#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;
	//...
}

В этом примере, конструкторе complex_ptr выполняет new операции, которые могут повысить исключения. Поскольку у конструктора нет механизма для того, чтобы обработать эти исключения, они могут заставить конструктора резко завершать работу. Такое завершение может оставить объект в частично заданном состоянии, потому что выделенные ресурсы не освобождены. Polyspace отмечает конструктора.

Коррекция — обрабатывает исключения, являющиеся результатом new Операции в конструкторах

Чтобы откорректировать этот дефект, выполните новую операцию в try или function-try блок.

#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;
	//...
}

Проверяйте информацию

Группа: правило 08. Исключения и обработка ошибок (ERR)
Введенный в R2021a

[1] Это программное обеспечение было создано MathWorks, включающим фрагменты: “Веб-сайт SEI CERT-C”, © 2017 Carnegie Mellon University, веб-сайт SEI CERT-C © 2017 Carnegie Mellon University”, CERT SEI C Кодирование Стандарта – Правил для Разработки безопасных, Надежных и Защищенных систем – 2 016 Выпусков”, © 2016 Carnegie Mellon University, and “CERT SEI Стандарт Кодирования C++ – Правил для Разработки безопасных, Надежных и Защищенных систем на C++ – 2 016 Выпусков” © 2016 Carnegie Mellon University, со специальным разрешением от его Института программной инженерии.

ЛЮБОЙ МАТЕРИАЛ УНИВЕРСИТЕТА КАРНЕГИ-МЕЛЛОН И/ИЛИ ЕГО ИНСТИТУТА ПРОГРАММНОЙ ИНЖЕНЕРИИ СОДЕРЖАЛ, ЗДЕСЬ ПРЕДОСТАВЛЯЕТСЯ НА БАЗИСЕ "ASIS". УНИВЕРСИТЕТ КАРНЕГИ-МЕЛЛОН НЕ ДАЕТ ГАРАНТИЙ НИКАКОГО ВИДА, ИЛИ ОПИСАЛ ИЛИ ПОДРАЗУМЕВАЛ, ОТНОСИТЕЛЬНО ЛЮБОГО ВОПРОСА ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИЛ, ГАРАНТИЯ ПРИГОДНОСТИ ДЛЯ ЦЕЛИ ИЛИ ВЫСОКОГО СПРОСА, ИСКЛЮЧИТЕЛЬНОСТИ, ИЛИ ЗАКАНЧИВАЕТСЯ ПОЛУЧЕННЫЙ ИЗ ИСПОЛЬЗОВАНИЯ МАТЕРИАЛА. УНИВЕРСИТЕТ КАРНЕГИ-МЕЛЛОН НЕ ДАЕТ ГАРАНТИИ НИКАКОГО ВИДА ОТНОСИТЕЛЬНО СВОБОДЫ ОТ ПАТЕНТА, ТОВАРНОГО ЗНАКА ИЛИ НАРУШЕНИЯ АВТОРСКОГО ПРАВА.

Это программное обеспечение и сопоставленная документация не были рассмотрены, ни являются подтвержденным Университетом Карнеги-Меллон или его Институтом программной инженерии.