Дополнительная настройка диагностик
- Как указать анализатору, что функция может или не может возвращать nullptr
- Как задать свой уровень для конкретной диагностики
- Изменение текста выводимого сообщения
- Настройка обработки макроса assert()
- Как задать псевдоним для системной функции
Как указать анализатору, что функция может или не может возвращать nullptr
Существует множество системных функций, которые при определённых условиях возвращают нулевой указатель. Хорошими примерами являются такие функции, как malloc, realloc, calloc. Эти функции возвращают NULL в случае, когда невозможно выделить буфер памяти указанного размера.
Иногда возникает желание изменить поведение анализатора и заставить его считать, что, например, функция malloc не может вернуть NULL. Например, пользователь может использовать системные библиотеки, в которых ситуации нехватки памяти обрабатываются особым образом.
Также возможна обратная ситуация. Пользователь может помочь анализатору, подсказав ему, что определённая системная или его собственная функция может вернуть нулевой указатель.
Поэтому существует возможность с помощью специальных комментариев указать анализатору, что функция может или наоборот не может вернуть нулевой указатель.
- V_RET_NULL - функция может вернуть нулевой указатель
- V_RET_NOT_NULL - функция не может вернуть нулевой указатель
Формат комментария:
//V_RET_[NOT]_NULL, namespace:Space, class:Memory, function:my_malloc
- Ключ function - Задаёт имя функции, которая может/не может возвратить нулевой указатель.
- Ключ class - Имя класса. Может отсутствовать.
- Ключ namespace - Имя пространства имён. Может отсутствовать.
Управляющий комментарий может быть написан рядом с объявлением функции.
В случае таких функций, как malloc, это невозможно. Плохая идея вносить изменения в системные заголовочные файлы.
Одним из решений является написание комментария в одном из глобальных заголовочных файлов, который используется во всех единицах трансляции. Для проекта, разрабатываемого с помощью Visual Studio, хорошим кандидатом на такую роль является stdafx.h.
Ещё один вариант - использование файла конфигурации диагностик pvsconfig. См. "Подавление ложных предупреждений" (раздел "Массовое подавление ложных предупреждений с помощью файлов конфигурации диагностик pvsconfig").
Для наглядности рассмотрим два примера.
Функция не возвращает нулевой указатель:
//V_RET_NOT_NULL, function:malloc
Теперь анализатор считает, что функция malloc не может вернуть NULL и не будет выдавать предупреждение V522 для следующего фрагмента кода:
int *p = (int *)malloc(sizeof(int) * 100);
p[0] = 12345; // ok
Функция возвращает потенциально нулевой указатель:
//V_RET_NULL, namesapce:Memory, function:QuickAlloc
После добавления этого комментария анализатор начнёт выдавать предупреждение для следующего кода:
char *p = Memory::QuickAlloc(strlen(src) + 1);
strcpy(p, src); // Warning!
В проектах с особыми требованиями качества может понадобится найти все функции, возвращающие указатель. Для этого можно воспользоваться следующим комментарием:
//V_RET_NULL_ALL
Мы не рекомендуем использовать этот режим из-за выдачи очень большого количества предупреждений. Но если в вашем проекте это действительно необходимо, то вы можете воспользоваться этим специальным комментарием, чтобы добавить в код проверку возвращаемого указателя для всех таких функций.
Как задать свой уровень для конкретной диагностики
Предупреждения анализатора имеют три уровня достоверности: High, Medium, Low. В зависимости от используемых в коде конструкций, анализатор оценивает достоверность предупреждений и присваивает им соответствующий уровень в отчёте. Некоторые предупреждения могут быть выданы одновременно на нескольких уровнях.
В некоторых проектах поиск определённых типов ошибок может быть очень важен, независимо от степени достоверности предупреждения. Бывает и обратная ситуация, когда сообщения малополезны, но совсем их отключать не хочется. В таких случаях для диагностик можно вручную задать уровень High/Medium/Low. Для этого следует использовать специальные комментарии, которые можно добавить в код или файл конфигурации диагностик. Примеры комментариев:
//V_LEVEL_1::501,502
//V_LEVEL_2::522,783,579
//V_LEVEL_3::773
Найдя подобные комментарии, анализатор будет выдавать предупреждения на указанном уровне.
Изменение текста выводимого сообщения
Можно указать, в каких сообщениях что и на что заменить. Это позволяет выдавать предупреждения с учётом специфики проекта. Формат управляющего комментария:
//+Vnnn:RENAME:{Aaaa:Bbbb},{<foo.h>:<myfoo.h>},{100:200},......
Во всех сообщениях с номером Vnnn будут произведены замены:
- Aaaa заменится на Bbbb.
- <foo.h> заменится на <myfoo.h>.
- Число 100 заменится на 200.
Проще всего использование этого механизма будет пояснить на примере.
Диагностика V624, встречая в коде число 3.1415, предлагает заменить его на M_PI из библиотеки <math.h>. Но в проекте используется специальная математическая библиотека и нужно использовать математические константы именно из неё. Поэтому программист может сделать в глобальном файле (например, в StdAfx.h) следующий комментарий:
//+V624:RENAME:{M_PI:OUR_PI},{<math.h>:"math/MMath.h"}
Теперь анализатор будет сообщать, что следует использовать константу OUR_PI из заголовочного файла "math/MMath.h".
Также можно расширить выдаваемое сообщение. Формат управляющего комментария:
//+Vnnn:ADD:{ Message}
В конец всех сообщений с номером Vnnn будет добавлена строка, заданная программистом.
Например, возьмем диагностику V2003. Выдаваемое сообщение будет выглядеть следующим образом: "V2003 - Explicit conversion from 'float/double' type to signed integer type.". Программист может учесть особенности проекта и расширить сообщение об ошибке, для чего можно сделать следующий комментарий:
//+V2003:ADD:{ Consider using boost::numeric_cast instead.}
Теперь анализатор будет выдавать модифицированное сообщение: "V2003 - Explicit conversion from 'float/double' type to signed integer type. Consider using boost::numeric_cast instead.".
Настройка обработки макроса assert()
По умолчанию анализатор одинаково проверяет код, в котором присутствует макрос assert(), независимо от конфигурации проекта (Debug, Release, ...). А именно – не учитывает, что при ложном условии в макросе выполнение кода прерывается.
Чтобы задать иное поведение анализатора, используйте следующий комментарий в коде:
//V_ASSERT_CONTRACT
Обратите внимание, что в таком режиме результаты анализа могут отличаться в зависимости от того, как раскрывается макрос в проверяемой конфигурации проекта.
Для пояснения, рассмотрим следующий код:
MyClass *p = dynamic_cast<MyClass *>(x);
assert(p);
p->foo();
Оператор dynamic_cast может вернуть значение nullptr. Поэтому, в стандартном режиме анализатор выдаст предупреждение, что при вызове функции foo() может произойти разыменовывание нулевого указателя.
Если же воспользоваться комментарием, то предупреждение исчезнет.
Дополнительно в комментарии с помощью ключа assertMacro можно указывать имя макроса, который анализатор будет обрабатывать так же, как assert:
//V_ASSERT_CONTRACT, assertMacro:MY_CUSTOM_MACRO_NAME
MyClass *p = dynamic_cast<MyClass *>(x);
MY_CUSTOM_MACRO_NAME(p);
p->foo();
Если необходимо указать несколько имен макросов, для каждого из них следует добавить отдельный комментарий V_ASSERT_CONTRACT.
Как задать псевдоним для системной функции
Иногда в проектах используются собственные реализации разных системных функций, например, memcpy, malloc и т.п. В этом случае анализатор не будет понимать, что поведение таких функций идентично стандартным аналогам. С помощью аннотации V_FUNC_ALIAS вы можете ставить имена своих функций в соответствие системным.
Формат комментария:
//V_FUNC_ALIAS, implementation:sysf, function:f, namespace:ns, class:c
- Ключ implementation - Задает имя стандартной функции, для которой определяется псевдоним.
- Ключ function - Задает имя псевдонима. Сигнатура функции, имя которой указано в этом ключе, должна совпадать с сигнатурой функции, указанной в ключе implementation.
- Ключ class - Имя класса. Может отсутствовать.
- Ключ namespace - Имя пространства имен. Может отсутствовать.
Рассмотрим пример:
//V_FUNC_ALIAS, implementation:memcpy, function:MyMemCpy
Теперь анализатор будет обрабатывать вызовы функции MyMemCpy так же, как вызовы memcpy. Например, на такой код будет выдаваться предупреждение V512:
int buf[] = { 1, 2, 3, 4 };
int out[2];
MyMemCpy (out, buf, 4 * sizeof(int)); // Warning!