Пользовательские аннотации представляют собой комментарии специального вида, которые можно указать в исходном коде для дополнительной настройки диагностических правил. Аннотация может быть расположена в одном из следующих мест:
Далее представлен список пользовательских аннотаций для изменения поведения диагностических правил. Этот функционал доступен только для С и C++ анализатора. При проверке проектов, написанных на других языках, описанные здесь аннотации будут игнорироваться.
Примечание. По умолчанию пользовательские аннотации не применяются к виртуальным функциям. О том, как включить данный функционал, вы можете прочитать здесь.
Если ваш проект основан на игровом движке Unreal Engine, то анализатор также применяет ряд диагностических правил (например, V1100, V1102), находящих характерные UE-проектам ошибки. Диагностические правила активируются лишь тогда, когда анализатор обнаруживает включение заголовочных файлов из директории с исходным кодом UE.
Если в проекте присутствуют компилируемые файлы, которые не включают такие заголовочные файлы, то диагностические правила не применяются даже если они включены. Это сделано для того, чтобы не генерировать нерелевантные предупреждения для проектов, не использующих UE.
Если вы хотите принудительно активировать набор диагностических правил для произвольного компилируемого файла или группы файлов, то добавьте следующий комментарий:
//V_TREAT_AS_UNREAL_ENGINE_FILE
Существует множество системных функций, которые при определённых условиях возвращают нулевой указатель. Хорошими примерами являются такие функции, как 'malloc', 'realloc', 'calloc'. Эти функции возвращают нулевой указатель в случае, когда невозможно выделить буфер памяти указанного размера.
Можно изменить поведение анализатора и заставить его считать, что, например, функция 'malloc' не может вернуть нулевой указатель. Например, это может потребоваться, если пользователь использует системные библиотеки, в которых ситуации нехватки памяти обрабатываются особым образом.
Также возможна обратная ситуация. Пользователь может помочь анализатору, подсказав ему, что определённая системная или его собственная функция может вернуть нулевой указатель.
Для этого существует возможность с помощью пользовательских аннотаций указать анализатору, что функция может или, наоборот, не может вернуть нулевой указатель.
Формат аннотации:
//V_RET_[NOT]_NULL, function: [namespace::][class::]functionName
Предположим, пользователь хочет подсказать анализатору, что функция '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', независимо от конфигурации проекта (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
Пример использования:
//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
Формат аннотации:
Наиболее полный пример использования:
namespace A
{
class B
{
void C(int, const char *fmt, ...);
};
}
//V_FORMATTED_IO_FUNC, function:A::B::C, format_arg:2, ellipsis_arg:3