- PVSM.RU - https://www.pvsm.ru -
Нас часто спрашивают, умеет ли статический анализатор кода PVS-Studio выявлять утечки памяти (memory leaks). Чтобы много раз не писать похожие тексты в письмах, мы решили дать подробный ответ в блоге. Да, PVS-Studio умеет выявлять утечки памяти и других ресурсов. Для этого в PVS-Studio реализовано несколько диагностик и в статье будут продемонстрированы примеры обнаружения ошибок в реальных проектах.
Утечка памяти (memory leak [1]) — это процесс неконтролируемого уменьшения объёма свободной оперативной или виртуальной памяти компьютера, связанный с ошибками в работающих программах, вовремя не освобождающих ненужные уже участки памяти. Согласно CWE утечки памяти классифицируются как дефект CWE-401 [2].
Утечки памяти являются одной из разновидностей ошибок утечек ресурсов (resource leak [3]). Примером утечки другого вида ресурса может служить ошибка, именуемая утечкой файлового дескриптора: файл открывается, но не закрывается, и дескриптор не возвращается операционной системе. Согласно CWE такие ошибки можно классифицировать как CWE-404 [4].
Утечки памяти или других ресурсов могут приводить к ошибкам отказа обслуживания (Denial of Service [5]).
Для выявления утечек памяти и других ресурсов используются инструменты динамическ [6]ого и статическ [7]ого анализа кода. К числу таких инструментов принадлежит и анализатор PVS-Studio.
Для выявления рассматриваемого класса ошибок в PVS-Studio реализованы следующие диагностики:
Давайте рассмотрим несколько примеров обнаружения анализатором PVS-Studio утечек памяти в коде открытых проектов.
Пример N1.
Проект NetDefender. Предупреждение PVS-Studio: V773 The 'm_pColumns' pointer was not released in destructor. A memory leak is possible. fireview.cpp 95
Обратите внимание, что в конструкторе создаются два объекта:
CFireView::CFireView() : CFormView(CFireView::IDD)
{
m_pBrush = new CBrush;
ASSERT(m_pBrush);
m_clrBk = RGB(148, 210, 252);
m_clrText = RGB(0, 0, 0);
m_pBrush->CreateSolidBrush(m_clrBk);
m_pColumns = new CStringList;
ASSERT(m_pColumns);
_rows = 1;
start = TRUE;
block = TRUE;
allow = TRUE;
ping = TRUE;
m_style=StyleTile;
}
В деструкторе же, происходит уничтожение только одного объекта, адрес которого хранится в переменной m_pBrush:
CFireView::~CFireView()
{
if(m_pBrush)
{
delete m_pBrush;
}
}
Про переменную m_pColumns видимо просто забыли. В результате возникает утечка памяти.
Пример N2.
Проект Far2l (Linux port of FAR v2). Рассматриваема ошибка интересна тем, что она обнаруживается сразу двумя различными диагностиками PVS-Studio:
BOOL WINAPI _export SEVENZ_OpenArchive(const char *Name,
int *Type)
{
Traverser *t = new Traverser(Name);
if (!t->Valid())
{
return FALSE;
delete t;
}
delete s_selected_traverser;
s_selected_traverser = t;
return TRUE;
}
Местами перепутаны оператор return и оператор delete. В результате оператор delete никогда не выполняется. Анализатор предупреждает об этом, выдавая сообщение о недостижимом коде и сообщение об утечке памяти.
Пример N3.
Проект Firebird. Предупреждение PVS-Studio: V701 realloc() possible leak: when realloc() fails in allocating memory, original pointer 's->base' is lost. Consider assigning realloc() to a temporary pointer. mstring.c 42
int mputchar(struct mstring *s, int ch)
{
if (!s || !s->base) return ch;
if (s->ptr == s->end) {
int len = s->end - s->base;
if ((s->base = realloc(s->base, len+len+TAIL))) {
s->ptr = s->base + len;
s->end = s->base + len+len+TAIL; }
else {
s->ptr = s->end = 0;
return ch;
}
}
*s->ptr++ = ch;
return ch;
}
Рассматриваемая функция предназначена для добавления к строке символа. Буфер, который используется для хранения строки, увеличивается с помощью вызова функции realloc. Ошибка заключается в том, что если функция realloc не может увеличить размер буфера памяти, то произойдёт утечка памяти. Причина в том, что если нет блока памяти достаточного размера, то функция realloc возвращает NULL и при этом она не освобождает предыдущий буфер памяти. Поскольку результат работы функции сразу записывается в переменную s->base, то нет никакой возможности освободить ранее выделенный блок.
Ошибку можно исправить, добавив временную переменную и вызов функции free:
int mputchar(struct mstring *s, int ch)
{
if (!s || !s->base) return ch;
if (s->ptr == s->end) {
void *old = s->base;
int len = s->end - s->base;
if ((s->base = realloc(s->base, len+len+TAIL))) {
s->ptr = s->base + len;
s->end = s->base + len+len+TAIL; }
else {
free(old);
s->ptr = s->end = 0;
return ch;
}
}
*s->ptr++ = ch;
return ch;
}
На примере PVS-Studio видно, что статические анализаторы умеют выявлять различные виды утечек ресурсов. Однако, ради справедливости следует отметить, что в целом статические анализаторы проигрывают в сфере поиска утечек динамическим анализаторам кода.
Чтобы найти ошибку, статические анализаторы должны отследить как используются указатели и это очень сложная задача. Указатели могут хитрым образом передаваться между функциями и анализатору, изучая исходный код, сложно отследить произойдёт утечка памяти или нет. В некоторых случаях это вообще невозможно, так как анализатор не знает, с какими входными данными будет работать программа.
Динамическим анализаторам найти утечки памяти или ресурсов намного проще. Им не надо ничего отслеживать. Им надо только запомнить место в программе, где какой-то ресурс выделен и проверить, освободится ли он до окончания программы. Если нет, то найдена ошибка. Таким образом, динамические анализаторы точнее и надёжнее обнаруживают различные виды утечек.
Из вышесказанного вовсе не следует, что динамический анализ мощнее, чем статический. У методологии динамического анализа, как и у методологии статического анализа, есть как свои преимущества, так и свои недостатки. Конкретно в сфере утечек динамические анализаторы мощнее. В других областях, например в поиске опечаток или недостижимого кода, они малоэффективны или вовсе бесполезны.
Не следует противопоставлять статический и динамический анализ. Эти методики не конкурируют, а дополняют друг друга. Решая вопросы повышения качества и надёжности кода, следует использовать инструменты обоих типов. Про это я много раз писал и мне не хочется повторяться. Тем, кто хочет разобраться в этом вопросе подробнее, предлагаю несколько ссылок:
Статический анализатор кода PVS-Studio способен выявить широкий спектр ошибок, связанных с утечками памяти и других ресурсов. Используйте его регулярно, чтобы устранять ошибки ещё на этапе написаний кода или во время ночных сборок проекта:
Команда PVS-Studio желает Вам безбажного кода.
Автор: Андрей Карпов
Источник [23]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/pvs-studio/269452
Ссылки в тексте:
[1] memory leak: https://en.wikipedia.org/wiki/Memory_leak
[2] CWE-401: https://cwe.mitre.org/data/definitions/401.html
[3] resource leak: https://en.wikipedia.org/wiki/Resource_leak
[4] CWE-404: https://cwe.mitre.org/data/definitions/404.html
[5] Denial of Service: https://cwe.mitre.org/data/definitions/730.html
[6] динамическ: https://www.viva64.com/ru/t/0070/
[7] статическ: https://www.viva64.com/ru/t/0046/
[8] V599: https://www.viva64.com/ru/w/v599/
[9] V680: https://www.viva64.com/ru/w/v680/
[10] V689: https://www.viva64.com/ru/w/v689/
[11] V701: https://www.viva64.com/ru/w/v701/
[12] V772: https://www.viva64.com/ru/w/v772/
[13] V773: https://www.viva64.com/ru/w/v773/
[14] V779: https://www.viva64.com/ru/w/v779/
[15] V1002: https://www.viva64.com/ru/w/v1002/
[16] V1005: https://www.viva64.com/ru/w/v1005/
[17] Статический и динамический анализ кода: https://www.viva64.com/ru/b/0248/
[18] Мифы о статическом анализе. Миф третий – динамический анализ лучше чем статический: https://www.viva64.com/ru/b/0117/
[19] Valgrind — это хорошо, но недостаточно: https://www.viva64.com/ru/b/0278/
[20] Проверяем код динамического анализатора Valgrind с помощью статического анализатора: https://www.viva64.com/ru/b/0504/
[21] Режим инкрементального анализа PVS-Studio: https://www.viva64.com/ru/m/0024/
[22] Прямая интеграция анализатора в системы автоматизации сборки (C/C++): https://www.viva64.com/ru/m/0006/
[23] Источник: https://habrahabr.ru/post/343508/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.