Подавление ложных предупреждений

04.06.2013

Аннотация

В статье приведены описание и пример использования функции, появившейся в PVS-Studio 3.40, "Mark as False Alarm" ("Пометить как ложное срабатывание"). Эта функция позволяет разметить те диагностические сообщения анализатора PVS-Studio, которые являются "ложными срабатываниями", чтобы не видеть эти сообщения при следующем запуске анализатора.

Механизм подавления отдельных ложных срабатываний

Любой анализатор кода всегда выдает помимо полезных сообщений об ошибках еще множество так называемых "ложных срабатываний". Это ситуации, когда программисту совершенно очевидно, что в коде нет ошибки, а анализатору это не очевидно. Такие ложные срабатывания называют False Alarm. Рассмотрим пример кода:

ptrdiff_t value;
fread(&value, 4, 1, f);
char RGBA[4];

Здесь будет выдано два предупреждения V112, так как используется магическая константа 4. В первом случае это ошибка, так как размер переменной типа ptrdiff_t не будет равен четырем байтам в 64-битной системе. Во втором случае число 4 обозначает количество компонент цвета и является безопасным. В PVS-Studio, начиная с версии 3.40, появилась возможность пометить сообщение об ошибке, полученное от PVS-Studio, как ложное срабатывание. Это можно делать либо вручную, либо с помощью команды контекстного меню. Появление в PVS-Studio возможности "Mark as False Alarm" значительно расширяет потенциал внедрения анализатора кода в процесс разработки программного обеспечения на этапе ежедневного постоянного использования, позволяя не только выполнять миграцию приложений на 64-битную платформу, но и быть уверенным в отсутствии опасных дефектов в новом только что разработанном коде.

Чтобы подавить ложное срабатывание, в код можно добавить специальный комментарий:

char RGBA[4]; //-V112

Теперь анализатор не будет выдавать предупреждение V112 на эту строку.

Комментарий, подавляющий предупреждения можно вписать в код самостоятельно. Также можно воспользоваться специальной командной для этого, представляемой PVS-Studio. Пользователю предоставляется две команды, доступные из контекстного меню PVS-Studio (смотри рисунок 1).

Рисунок 1 - Команды для работы с механизмом подавления ложных предупреждений

Рисунок 1 - Команды для работы с механизмом подавления ложных предупреждений

Рассмотрим доступные команды, относящиеся к подавлению ложных предупреждений:

1. Mark selected errors as False Alarm. Вы можете выбрать одно или несколько предупреждений в списке (смотри рисунок 2) и воспользоваться этой командой для пометки соответствующего кода, как безопасного.

Рисунок 2 - Выбор предупреждений перед выполнением команды Mark selected errors as False Alarms

Рисунок 2 - Выбор предупреждений перед выполнением команды Mark selected errors as False Alarms

2. Remove False Alarm marks from selected errors. Убирает комментарий, помечающий код как безопасный. Функция, например, может быть полезна, если вы поспешили и ошибочно отметили код как безопасный. Как и в предыдущем случае, вы должны выбрать сообщения из списка, которые планируете обработать.

Групповое подавление ложных предупреждений с помощью фильтров

Возможны ситуации, в которых определённый тип диагностик не актуален для анализируемого проекта (Например, если ошибки, связанные с явным приведением типа — коды V201, V202, V203 — вас не интересуют, и т.п.), или какая-либо из диагностик анализатора выдаёт предупреждения на код, в корректности которого вы уверены. В таком случае можно воспользоваться системой группового подавления сообщений, основанной на фильтрации полученных результатов анализа. Список доступных режимов фильтрации можно открыть кнопкой "Suppressions", либо через общее меню PVS-Studio -> Options. (смотри рисунок 3)

Рисунок 3 - Режимы групповой фильтрации сообщений

Рисунок 3 - Режимы групповой фильтрации сообщений

Режимы фильтрации включают в себя группы Detectable Errors, Don't Check Files и Message Suppression.

С помощью команды контекстного меню "Hide all Vxxx errors" (смотри рисунок 1) возможно отключить отображение всех ошибок, относящихся к одному коду. Для того чтобы вновь показать отключенные таким образом ошибки необходимо выбрать на странице Suppressions режим Detectable Errors и отметить соответствующий код ошибки как True.

Применение фильтров для группового подавления ложных срабатываний не требует перезапуска анализа, результаты фильтрации будут сразу отображены в окне вывода PVS-Studio.

Демонстрация функции Mark as False Alarm на примере проекта OmniSample

Покажем, как пользоваться функцией Mark as False Alarm на демонстрационном примере, который идет в дистрибутиве вместе с PVS-Studio.

Сначала необходимо распаковать демонстрационный проект в удобную для вас директорию из меню Start\PVS-Studio\. (Project Samples, файл Samples.zip) или через пункт меню среды 'PVS-Studio\Open Samples.zip examples' и открыть его в среде Microsoft Visual Studio/RAD Studio.

Открыв проект, запустим анализ решения с помощью команды "Check Solution / Check Project Group" (в зависимости от IDE). По завершении анализа будет выведен список сообщений об обнаруженных проблемах (рисунок 4).

Рисунок 4 - Список проблем, обнаруженных с помощью PVS-Studio

Рисунок 4 - Список проблем, обнаруженных с помощью PVS-Studio

Этот список сообщений нужно просмотреть и проанализировать.

Далее, рассмотрим следующее сообщение анализатора:

V547 Expression 'N >= 0' is always true. Unsigned type value is always >= 0. sample1.cpp 18

Этому сообщению (и этой строке) соответствует код:

if (TEST(N))
{
  data->num = N;
}

Конечно же, можно просто исправить код на корректный вариант и сообщение исчезнет. Проблема в том, что здесь используется макрос, который может быть корректен на некоторых платформах. И если вы точно знаете, что код корректен, то можно "попросить" анализатор кода не выдавать данное сообщение (V547) в конкретно этой строке (18) этого файла (sample1.cpp). Для этого нужно выделить в окне PVS-Studio сообщение об ошибке и выбрать команду "Mark selected errors as False Alarms" в контекстном меню PVS-Studio (рисунок 5).

Рисунок 5 - Команда "Mark Selected Errors as False Alarm"

Рисунок 5 - Команда "Mark Selected Errors as False Alarm"

После этого в код автоматически добавится комментарий " //-V547":

if (TEST(N))//-V547

Этот комментарий сообщает анализатору кода, что в следующий раз при анализе проекта выдавать сообщение об этой ошибке в этой строке не надо.

Комментарий можно добавить и вручную без использования команды "Mark selected errors as False Alarms", но важно полностью соблюдать формат записи: две косые черты, минус (без пробела), код ошибки.

После пометки сообщения как ложное срабатывание можно воспользоваться кнопкой-переключателем "False Alarms" (рисунок 6) для того, чтобы обновить список сообщений об ошибках и скрыть ненужное сообщение. В окне PVS-Studio станет на одно сообщение меньше.

Рисунок 6 - Кнопка FA, предназначенная для управления отображением размеченных сообщений

Рисунок 6 - Кнопка FA, предназначенная для управления отображением размеченных сообщений

Удалить комментарий можно с помощью команды "Remove False Alarm marks from selected Errors", предварительно выделив сообщение об ошибке в окне PVS-Studio. Также комментарий можно удалить и вручную.

Рисунок 7 - Команда "Remove False Alarm Marks from selected errors"

Рисунок 7 - Команда "Remove False Alarm Marks from selected errors"

Итак, мы пометили одну ошибку как "ложное срабатывание". Перезапустим анализ; мы получим на одно сообщение меньше. При этом в списке сообщений уже нет того сообщения, которое было помечено как "ложное срабатывание".

Если необходимо в окне PVS-Studio получить все-таки все сообщения, включая и помеченные как False Alarm, то можно опять включить их отображение с помощью кнопки-переключателя "FA" (рисунок 8).

Рисунок 8 — Включение отображения размеченных сообщений

Рисунок 8 — Включение отображения размеченных сообщений

Можно пометить сразу несколько сообщений. Для этого нужно выбрать их в окне PVS-Studio (рисунок 9).

Рисунок 9 — Выбор нескольких сообщений для разметки в окне PVS-Studio

Рисунок 9 — Выбор нескольких сообщений для разметки в окне PVS-Studio

Мы не рекомендуем использовать разметку сообщений как ложных предупреждений без предварительного просмотра соответствующего кода, так как это противоречит идеологии статического анализа. Только программист может определить, является ли сообщение об ошибке ложным.

Реализация функции подавления ложных предупреждений

Обычно в компиляторах для подавления отдельных сообщений об ошибках используют #pragma-директивы. Приведем пример кода:

unsigned arraySize = n * sizeof(float);

Компилятор выдает сообщение:

warning C4267: 'initializing' : conversion from 'size_t' to 'unsigned
int', possible loss of data x64Sample.cpp 151

Это сообщение можно подавить с помощью следующей конструкции:

#pragma warning (disable:4267)

Точнее, чтобы подавить конкретно это сообщение, лучше оформить код так:

#pragma warning(push)
#pragma warning (disable:4267) 
  unsigned arraySize = n * sizeof(float);
#pragma warning(pop)

Анализатор PVS-Studio в качестве разметки использует комментарии специального вида. Для той же строчки кода подавление сообщения PVS-Studio будет выглядеть так:

unsigned arraySize = n * sizeof(INT_PTR); //-V103

Такой подход был выбран для повышения наглядности конечного кода. Дело в том, что PVS-Studio может сообщать о проблемах в середине многострочных выражений, как, например, здесь:

  size_t n = 100;
  for (unsigned i = 0;
       i < n; // анализатор сообщит о проблеме здесь
       i++)
  {
      // ...
  }

Для того чтобы подавить это сообщение при использовании комментария, достаточно написать:

  size_t n = 100;
  for (unsigned i = 0;
       i < n; //-V104
       i++)
  {
      // ...
  }

Если же в это выражение пришлось бы добавлять #pragma-директиву, то код выглядел бы значительно менее наглядно.

Хранение разметки в исходном коде позволяет вносить в него модификации без опасения потерять информацию о строках с ошибками.

Теоретически можно также использовать отдельную базу, в которой хранить информацию примерно так: код ошибки, имя файла, номер строки. Недостатком такого подхода является опасность потери информации о номерах строк, в которых содержаться ошибки, при любой модификации исходных файлов извне. Хранение разметки в исходном коде позволяет вносить в него модификации без опасения потерять информацию о строках с ошибками.

Подавление ложных предупреждений в макросах (#define)

В макросах (#define) анализатор также, разумеется, может находить потенциальные проблемы и выдавать на них диагностические сообщения. Но при этом анализатор будет выдавать сообщения в тех местах, где макрос используется, то есть где фактически происходит подстановка тела макроса в код. Пример:

#define TEST_MACRO \
  int a = 0;       \
  size_t b = 0;    \
  b = a; 

void func1()
{
  TEST_MACRO // V101 here
}

void func2()
{
  TEST_MACRO // V101 here
}

Для того чтобы подавить это сообщения можно, использовать команду "Mark as False Alarm". Тогда код с расставленными командами подавления будет выглядеть так:

#define TEST_MACRO \
  int a = 0;       \
  size_t b = 0;    \
  b = a; 

void func1()
{
  TEST_MACRO //-V101
}

void func2()
{
  TEST_MACRO //-V101
}

Однако если макрос используется очень активно, то везде размечать его как False Alarm не очень удобно. Есть возможность в коде сделать вручную специальную пометку, чтобы анализатор автоматически размечал диагностики в этом макросе как False Alarm. С этой пометкой код будет выглядеть так:

//-V:TEST_MACRO:101

#define TEST_MACRO \
  int a = 0;       \
  size_t b = 0;    \
  b = a; 

void func1()
{
  TEST_MACRO
}

void func2()
{
  TEST_MACRO
}

При проверке такого кода сообщения о проблемах в макросе уже сразу будут помечены как False Alarm. Причем можно указывать несколько диагностик сразу, через запятую:

//-V:TEST_MACRO:101, 105, 201

Обратите внимание, что если макрос содержит вложенные макросы, то для автоматической пометки нужно указывать все равно имя макроса самого верхнего уровня:

#define NO_ERROR 0
#define VB_NODATA ((long)(77))
size_t stat;

#define CHECK_ERROR_STAT                        \
    if( stat != NO_ERROR &&  stat != VB_NODATA ) \
      return stat;

size_t testFunc()
{
    {
      CHECK_ERROR_STAT // #1
    }

    {
      CHECK_ERROR_STAT // #2
    }

    return VB_NODATA; // #3
}

В указанном примере диагностика V126 появляется в трех местах. Для того, чтобы автоматически помечать ее как False Alarm в местах #1 и #2 нужно добавить такой код:

//-V:CHECK_ERROR_STAT:126

А для того, чтобы и в #3 это сработало, необходимо указать еще:

//-V:VB_NODATA:126

К сожалению, просто указать "сразу помечать V126 в макросе VB_NODATA" и не указывать про макрос CHECK_ERROR_STAT нельзя из-за технических особенностей механизма препроцессирования.

Массовое подавление ложных предупреждений

Предположим, есть следующая структура:

struct EXRGBA
{
unsigned data;
};

И ряд функций, которые ее используют:

void f1(const struct EXRGBA aaa)
{
}

long int f2(int b, const struct EXRGBA aaa)
{
  return int();
}

long int f3(float b, const struct EXRGBA aaa,  char c)
{
  return int();
}

На все эти функции анализатор три сообщения V801: Decreased performance. It is better to redefine the N function argument as a reference. Сообщение в подобном коде будет ложным, так как компилятор сам оптимизирует код, и проблемы не будет.

Можно конечно каждое сообщение пометить как False Alarm с помощью функции Mark As False Alarm. Однако есть способ лучше. Достаточно добавить в код строку:

//-V:EXRGBA:801 

Мы рекомендуем добавлять такую строку в .h-файл рядом с объявлением структуры, но если это невозможно (например, структура в системном .h-файле), то можно прописать это в stdafx.h.

И тогда, после перепроверки, все три сообщения V801 будут автоматически помечены как False Alarm.

Описанный механизм может применяться для подавления предупреждений не только для отдельных слов. Иногда, это очень удобно.

Рассмотрим несколько примеров:

//-V:<<:128

Подавит предупреждения V128 в строках, где имеется оператор <<.

buf << my_vector.size();

Если вы хотите подавлять предупреждение V128 только при записи данных в объект с именем 'log', то можно написать так:

//-V:log<<:128
buf << my_vector.size(); // Есть предупреждение
log << my_vector.size(); // Нет предупреждения

Примечание. Обратите внимание, что строка для поиска не должна содержать пробелов.

Правильно: //-V:log<<:128
Неправильно: //-V:log <<:128

При поиске подстроки пробелы игнорируются. Но не беспокойтесь, следующая ситуация обработается корректно:

//-V:ABC<<:501
AB C = x == x; // Есть предупреждение
AB y = ABC == ABC; // Нет предупреждения

Другие способы фильтрации сообщений в анализаторе PVS-Studio

В анализаторе также существуют ещё три способа фильтрации сообщений об ошибках.

Во-первых, можно отключить диагностику тех или иных ошибок по их коду. Это делается с помощью вкладки "Настройки: Detectable Errors". На вкладке обнаруживаемых ошибок можно указать номера ошибок, какие не надо показывать в отчете по анализу. Иногда бывает целесообразно убрать в отчете ошибки с определенными кодами. Например, если вы уверены, что ошибки, связанные с явным приведением типа (коды V201, V202, V203) вас не интересуют, то вы можете скрыть их показ.

Во-вторых, можно отключить анализ некоторых частей проекта (некоторых папок или файлов проекта). Раздел "Настройки: Don't Check Files". На этой можно ввести информацию о библиотеках, включения (через директиву #include) из файлов которых анализировать не надо. Это может потребоваться для уменьшения количества лишних диагностических сообщений. Например, в проекте используется библиотека Boost. И хотя на какой-то код из этой библиотеки анализатор выдает диагностические сообщения, вы считаете, что эта библиотека является достаточно надежной и написана хорошо. Поэтому, возможно, не имеет смысла получать диагностические сообщения по поводу кода в этой библиотеке. В этом случае можно отключить анализ файлов из этой библиотеки, указав путь к ней на странице настроек. Кроме того возможно ввести файловые маски для исключения некоторых файлов из анализа. Анализатор не будет проверять файлы, удовлетворяющие условиям маски. Например, подобным образом можно исключить из анализа автогенерируемые файлы.

Маски путей для файлов, сообщения из которых попали в текущий сгенерированный отчёт можно автоматически добавить в список Don't Check Files с помощью команды контекстного меню "Don't check files and hide all messages from..." для выделенного в окне PVS-Studio Output сообщения (Рисунок 10).

Рисунок 10 — Добавление масок путей через контекстное меню

Рисунок 10 — Добавление масок путей через контекстное меню

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

В-третьих, можно подавлять отдельные сообщения по тексту. На вкладке "Настройки: Message Suppression" можно настроить фильтрацию ошибок по содержащемуся в них тексту, а не по коду. При необходимости можно скрыть из отчета сообщения о диагностированных ошибках, содержащих определенные слова или фразы. Например, если в отчете есть ошибки, в которых указаны названия функций printf и scanf, а вы считаете, что ошибок, связанных с ними, быть не может, то просто добавьте эти два слова с помощью редактора подавляемых сообщений.

Возможные проблемы

При использовании возможности "Mark as False Alarm" существуют несколько потенциальных проблем. Иногда анализатор кода "промахивается" с номером строки содержащей ошибку. Например, анализатор говорит, что ошибка в строке 57, а строка 57 - вообще пустая строка. При этом исходный код, который вызывает ошибку, находится строчкой выше, например, в строке 56.

Дело в том, что анализатор кода использует препроцессор из Visual C++, имеющий проблемы при работе с многострочными макросами (#define). В Visual Studio 2005 Service Pack 1 и более поздних версиях эти проблемы были устранены.

Другая проблема препроцессора связана с многострочными #pragma-директивами определенного типа, из-за которых также сбивается нумерация строк. К сожалению, эта ошибка пока не исправлена ни в каких версиях Visual Studio.

Таким образом, автоматически расставленные разметки могут быть поставлены не в том месте, где должны быть. И тогда анализатор вновь выдаст эти же сообщения об ошибках, так как маркер не будет найден. Решением проблемы является пометка сообщений, на которых заметен сбой, вручную. PVS-Studio всегда сообщает о подобных ошибках сообщением "V002. Some diagnostic messages may contain incorrect line number".

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