Дополнительная настройка диагностических правил C и C++ анализатора
- Принудительное включение диагностических правил для проектов на основе Unreal Engine
- Функция может / не может возвращать нулевой указатель
- Настройка обработки макроса assert()
- Псевдоним для системной функции
- Пользовательская функция форматного ввода/вывода
Пользовательские аннотации представляют собой комментарии специального вида, которые можно указать в исходном коде для дополнительной настройки диагностических правил. Аннотация может быть расположена в одном из следующих мест:
- В анализируемом файле (*.c, *.cpp, *.cxx, ....). Аннотация будет работать только в контексте этого файла.
- В заголовочном файле (*.h, *.hxx, ....). Аннотация будет работать на всех анализируемых файлах, включающих этот заголовочный файл.
- В файле конфигурации диагностических правил (.pvsconfig). Аннотация будет работать на всех анализируемых файлах проекта/решения.
Далее представлен список пользовательских аннотаций для изменения поведения диагностических правил. Этот функционал доступен только для С и C++ анализатора. При проверке проектов, написанных на других языках, описанные здесь аннотации будут игнорироваться.
Примечание. По умолчанию пользовательские аннотации не применяются к виртуальным функциям. О том, как включить данный функционал, вы можете прочитать здесь.
Принудительное включение диагностических правил для проектов на основе Unreal Engine
Если ваш проект основан на игровом движке Unreal Engine, то анализатор также применяет ряд диагностических правил (например, V1100, V1102), находящих характерные UE-проектам ошибки. Диагностические правила активируются лишь тогда, когда анализатор обнаруживает включение заголовочных файлов из директории с исходным кодом UE.
Если в проекте присутствуют компилируемые файлы, которые не включают такие заголовочные файлы, то диагностические правила не применяются даже если они включены. Это сделано для того, чтобы не генерировать нерелевантные предупреждения для проектов, не использующих UE.
Если вы хотите принудительно активировать набор диагностических правил для произвольного компилируемого файла или группы файлов, то добавьте следующий комментарий:
//V_TREAT_AS_UNREAL_ENGINE_FILE
Функция может / не может возвращать нулевой указатель
Существует множество системных функций, которые при определённых условиях возвращают нулевой указатель. Хорошими примерами являются такие функции, как 'malloc', 'realloc', 'calloc'. Эти функции возвращают нулевой указатель в случае, когда невозможно выделить буфер памяти указанного размера.
Можно изменить поведение анализатора и заставить его считать, что, например, функция 'malloc' не может вернуть нулевой указатель. Например, это может потребоваться, если пользователь использует системные библиотеки, в которых ситуации нехватки памяти обрабатываются особым образом.
Также возможна обратная ситуация. Пользователь может помочь анализатору, подсказав ему, что определённая системная или его собственная функция может вернуть нулевой указатель.
Для этого существует возможность с помощью пользовательских аннотаций указать анализатору, что функция может или, наоборот, не может вернуть нулевой указатель.
- V_RET_NULL - функция может вернуть нулевой указатель
- V_RET_NOT_NULL - функция не может вернуть нулевой указатель
Формат аннотации:
//V_RET_[NOT]_NULL, function: [namespace::][class::]functionName
- Ключ 'function' – после ':' нужно указать полное имя функции. Оно состоит из названия пространства имён, названия класса и названия функции. Пространство имён и/или класс могут отсутствовать.
Предположим, пользователь хочет подсказать анализатору, что функция 'Foo' класса 'Bar', лежащего в пространстве имён 'Space', не может возвращать нулевой указатель. Тогда аннотация будет выглядеть следующим образом:
//V_RET_NOT_NULL, function: Space::Bar::Foo
Аннотации поддерживают вложенные пространства имён и вложенные классы. Предположим, в пространстве имён 'Space1' лежит пространство имён 'Space2'. В пространстве имён 'Space2' лежит класс 'Bar1'. В классе 'Bar1' лежит класс 'Bar2'. У класса 'Bar2' есть функция 'Foo', которая не может возвращать нулевой указатель. Тогда можно проаннотировать эту функцию следующим образом:
//V_RET_NOT_NULL, function: Space1::Space2::Bar1::Bar2::Foo
Для системных функций аннотация может быть расположена в глобальном заголовочном файле (например, предкомпилированный заголовочный файл) или файле конфигурации диагностических правил.
Для наглядности рассмотрим два примера аннотации системных функций.
Функция не возвращает нулевой указатель:
//V_RET_NOT_NULL, function:malloc
Теперь анализатор считает, что функция 'malloc' не может вернуть нулевой указатель и не будет выдавать предупреждение V522 для следующего фрагмента кода:
int *p = (int *)malloc(sizeof(int) * 100);
p[0] = 12345; // ok
Функция возвращает потенциально нулевой указатель:
//V_RET_NULL, function: Memory::QuickAlloc
После добавления этого комментария анализатор начнёт выдавать предупреждение для следующего кода:
char *p = Memory::QuickAlloc(strlen(src) + 1);
strcpy(p, src); // Warning!
В проектах с особыми требованиями качества может понадобится найти все функции, возвращающие указатель. Для этого можно воспользоваться следующим комментарием:
//V_RET_NULL_ALL
Мы не рекомендуем использовать этот режим из-за выдачи очень большого количества предупреждений. Но если в вашем проекте это действительно необходимо, то вы можете воспользоваться этим специальным комментарием, чтобы добавить в код проверку возвращаемого указателя для всех таких функций.
Настройка обработки макроса assert()
По умолчанию анализатор одинаково проверяет код, в котором присутствует макрос 'assert', независимо от конфигурации проекта (Debug, Release, ...). А именно – не учитывает, что при ложном условии в макросе выполнение кода прерывается.
Чтобы задать иное поведение анализатора, используйте следующий комментарий в коде:
//V_ASSERT_CONTRACT
Обратите внимание, что в таком режиме результаты анализа могут отличаться в зависимости от того, как раскрывается макрос в проверяемой конфигурации проекта.
Для пояснения рассмотрим следующий код:
MyClass *p = dynamic_cast<MyClass *>(x);
assert(p);
p->foo();
Оператор 'dynamic_cast' может вернуть значение 'nullptr'. Поэтому в стандартном режиме анализатор выдаст предупреждение, что при вызове функции 'foo' может произойти разыменовывание нулевого указателя.
После добавления комментария '//V_ASSERT_CONTRACT' предупреждение исчезнет.
Можно указать имя макроса, который анализатор будет обрабатывать так же, как 'assert'. Для этого используйте следующую аннотацию:
//V_ASSERT_CONTRACT, assertMacro:[MACRO_NAME]
Ключ 'assetMacro' – имя макроса, который анализатор будет обрабатывать как 'assert'. Вместо '[MACRO_NAME]' необходимо подставить имя аннотируемого макроса.
Пример:
//V_ASSERT_CONTRACT, assertMacro:MY_CUSTOM_MACRO_NAME
Теперь анализатор будет обрабатывать макрос 'MY_CUSTOM_MACRO_NAME' как 'assert'.
Если необходимо указать несколько имен макросов, для каждого из них следует добавить отдельную директиву '//V_ASSERT_CONTRACT'.
Псевдоним для системной функции
Иногда в проектах используются собственные реализации разных системных функций, например, 'memcpy', 'malloc' и т.п. В этом случае анализатор не будет понимать, что поведение таких функций идентично стандартным аналогам. Существует возможность ставить имена своих функций в соответствие системным.
Формат записи:
//V_FUNC_ALIAS, implementation:imp, function:f, namespace:n, class:c
- Ключ 'implementation' – имя стандартной функции, для которой определяется псевдоним;
- Ключ 'function' – имя псевдонима. Сигнатура функции, имя которой указано в этом ключе, должна совпадать с сигнатурой функции, указанной в ключе 'implementation';
- Ключ 'class' – имя класса. Может отсутствовать;
- Ключ 'namespace' – имя пространства имен. Может отсутствовать.
Пример использования:
//V_FUNC_ALIAS, implementation:memcpy, function:MyMemCpy
Теперь анализатор будет обрабатывать вызовы функции 'MyMemCpy' так же, как вызовы 'memcpy'.
Пользовательская функция форматного ввода/вывода
Можно самостоятельно указать имена своих собственных функций, для которых следует выполнять проверку формата. Подразумевается, что принцип форматирования строк совпадает с функцией 'printf'.
Для этого используется специальная пользовательская аннотация. Пример использования:
//V_FORMATTED_IO_FUNC, function:Log, format_arg:1, ellipsis_arg:2
void Log(const char *fmt, ...);
Log("%f", time(NULL)); // <= V576
Формат аннотации:
- Ключ 'function' задаёт полное имя функции, состоящее из названия пространства имён, имени класса и имени функции. Поддерживаются вложенные пространства имён и вложенные классы.
- Ключ 'format_arg' задаёт номер аргумента функции, в котором будет находиться форматная строка. Номера также считаются с единицы и также не должны превышать число 14. Это обязательный аргумент.
- Ключ 'ellipsis_arg' задаёт номер аргумента функции с эллипсисом (то есть многоточием). Считается с единицы и также не должен быть больше 14. Более того, номер аргумента с эллипсисом должен быть больше номера аргумента с форматной строкой (всё-таки эллипсис – исключительно последний аргумент). Это тоже обязательный аргумент.
Наиболее полный пример использования:
namespace A
{
class B
{
void C(int, const char *fmt, ...);
};
}
//V_FORMATTED_IO_FUNC, function:A::B::C, format_arg:2, ellipsis_arg:3