Анализатор обнаружил проблему, из-за которой позиции предупреждений могут указывать на некорректные строки кода. Это может происходить либо из-за некорректной работы внешнего препроцессора, либо из-за вставленных разработчиком директив '#line' в исходный код.
Анализатор PVS-Studio для языков C и C++ работает только с препроцессированными файлами, т.е. с файлами, в которых раскрыты все макросы ('#define') и подставлены все включаемые файлы ('#include'). При этом в препроцессированном файле содержится информация о том, какие файлы куда подставились и в какие позиции. Осуществляется это как раз с помощью директив '#line', имеющих следующий формат:
#line linenum "filename" // MSVC
# linenum "filename" // GCC-like
Следующая строка после этой директивы в препроцессированном файле интерпретируется как пришедшая из файла 'filename' и имеющая номер 'linenum'. Таким образом, в препроцессированных файлах помимо готового к анализу кода содержится также информация о том, из какого файла поступил тот или иной участок.
Препроцессирование выполняется в любом случае. Для пользователя эта процедура происходит незаметно. Иногда препроцессор является частью анализатора кода, а иногда (как в случае с PVS-Studio) используется внешний препроцессор. Утилита анализа запускает компилятор, которым собирается проверяемый проект, для каждого проверяемого файла на языке C или C++. С его помощью генерируется препроцессированный файл с расширением '*.PVS-Studio.i'.
Рассмотрим ситуацию, при которой происходит сбой в позиционировании предупреждений анализатора. Речь пойдёт о написании директив '#pragma' в несколько строк с применением лексемы продолжения предыдущей строки ('\'):
#pragma \
warning(push)
void test()
{
int a;
if (a == 1) // V614 должно быть выдано здесь,
return; // а выдастся здесь
}
Компилятор MSVC неверно формирует директивы '#line' в препроцессированном файле при написании такого кода, при этом GCC и Clang делают всё верно. Однако если немного изменить пример, то все внешние препроцессоры отрабатывают корректно:
#pragma warning \
(push)
void test()
{
int a;
if (a == 1) // V614 теперь выдаётся корректно
return;
}
Наша рекомендация — это либо не использовать многострочные директивы '#pragma', либо писать их так, чтобы они корректно обрабатывались внешним препроцессором.
Анализатор пытается обнаружить сдвиг строк в обрабатываемом файле и сообщить об этом пользователю предупреждением V002. При этом он не пытается скорректировать позиции выданных предупреждений в коде программы. Алгоритм нахождения сдвига строк работает следующим образом.
Шаг N1. Анализатор открывает исходный файл и ищет самую последнюю лексему. Выбираются только те лексемы, которые не короче трех символов. Например, для следующего кода последней лексемой будет считаться 'return':
1 #include "stdafx.h"
2
3 int foo(int a)
4 {
5 assert( a >= 0
6 && a <= 1000);
7 int b = a + 1;
8 return b;
9 }
Шаг N2. Найдя последнюю лексему, анализатор определит номер строки, в которой она находится. В примере это строка под номером 8. Далее анализатор ищет последнюю лексему в уже препроцессированном файле. Если последние лексемы не совпадают, то, видимо, в конце файла раскрылся макрос. В такой ситуации анализатор не сможет понять, корректно ли расположены строки. Но подобное происходит крайне редко, и почти во всех случаях последние лексемы в исходном и препроцессированном файле совпадают. Если это так, определяется номер строки, в которой расположена лексема в препроцессированном файле.
Шаг N3. После двух предыдущих шагов имеются номера строк, где расположена последняя лексема в исходном файле и в препроцессированном файле соответственно. Если эти номера строк не совпадают, то произошёл сдвиг в нумерации строк. И в данном случае анализатор сгенерирует предупреждение V002.
Примечание N1. Следует учитывать, что если некорректная директива '#line' будет находиться в файле ниже всех найденных подозрительных участков кода, то все позиции выданных предупреждений будут корректны. И хотя анализатор сгенерирует дополнительно предупреждение V002, это не помешает вам работать с результатами анализа.
Примечание N2. Хоть это и не ошибка непосредственно анализатора кода PVS-Studio, тем не менее, это приводит к его некорректной работе.
Если пользователь хочет попытаться самостоятельно найти строку в исходном файле, из-за которой возник сдвиг, то можно попробовать воспользоваться следующим алгоритмом:
Шаг N1. Перезапустите анализ решения/проекта/файла, сохранив промежуточные файлы анализа (выключив настройку "Remove Intermediate Files").
Шаг N2. Откройте отчёт в одном из плагинов для IDE.
Шаг N3. Отфильтруйте предупреждения согласно файлу, в котором произошёл сдвиг позиций. Если производился анализ единичного файла, то фильтровать ничего не требуется.
Шаг N4. Отсортируйте предупреждения по номерам строк или позиций (колонка 'Line' или 'Positions').
Шаг N5. Найдите первое предупреждение, позиция которого смещена.
Шаг N6. Откройте препроцессированный файл, соответствующий исходному, с расширением '*.PVS-Studio.i'.
Шаг N7. Найдите строку, полученную на шаге N5, в препроцессированном файле.
Шаг N8. Двигайтесь вверх по препроцессированному файлу, начиная от полученной на шаге N7 позиции, и найдите первую ближайшую директиву '#line'.
Шаг N9. В исходном файле перейдите на соответствующую строку, указанную в директиве '#line', полученную на шаге N8. Между этой строкой и строкой, на которой выдано предупреждение, находится код, который приводит к сдвигу. Это могут быть многострочные вызовы макросов, многострочные директивы компиляторов и т.д.
Схематически работу алгоритма можно отобразить следующим образом: