...

среда, 28 января 2015 г.

PVS-Studio и враждебная среда обитания

враждебная среда обитания

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

Я считаю PVS-Studio очень качественным продуктов. Мы можем почти в любой день сделать и выложить дистрибутив. У нас используется очень большое количество автоматизированных тестов различного уровня и типов. Вот описание некоторых из них: "Как мы тестируем анализатор кода". Сейчас их стало больше. Например, теперь для статического анализа мы используем не только свой собственный анализатор, но и Clang. Если исправленная версия прошла все тесты, значит ее можно смело выдавать пользователям.


К сожалению, вся красота и надежность внутреннего кода иногда разваливается из-за воздействий враждебной окружающей среды. В результате все впечатление от продукта портится. Вроде и не мы виноваты, но не работает-то именно наш продукт. Я могу привести большое количество примеров. Первое что вспоминается:



  • Сторонний add-in что-то портит в окружении Visual Studio. В результате приходится создавать костыль для обхода проблемы или смириться и ответить «извините, ничего не можем сделать». Один из таких примеров — "Описание ошибки интеграции Intel Parallel Studio Service Pack 1 в Visual Studio 2005/2008".

  • COM-интерфейсы Visual Studio для получения информации о проекте неожиданно могут кинуть исключение. Видимо, среда в этот неудачный момент занята чем-то еще. Вызовы приходится заворачивать в цикл для многократного их повторения. Танцы с бубном, которые не всегда помогают.

  • У разработчика стоит антивирус X и, согласно корпоративной политике, у него нет прав менять его настройки. Этот антивирус «держит» какое-то время некоторые временные файлы, и анализатор не может их удалить. В результате получается, что анализатор «гадит» в папку проекта.

  • Разное можно вспоминать. Некоторые примеры можно посмотреть здесь, здесь и здесь.




Сейчас я расскажу очередную такую историю, как легко испортить впечатление о нашем продукте, будучи невиновными.

Один из потенциальных пользователей прислал вопрос о странном поведении PVS-Studio:


Мы сейчас гоняем триальную версию и раздумываем о покупке полной. Однако при анализе наткнулись на один нюанс, ставящий справедливость выводов под сомнение.


Ниже – скриншот, на котором видно ошибку.


filePath и cachePath отмечены как неиспользуемые (предупреждение V808 ), хотя видно, что они используются буквально в следующей строке после объявления.


Могли бы Вы объяснить подобное поведение анализатора?


На скриншоте можно видеть код следующего вида (код изменён):



std::string Foo()
{
std::string filePath(MAX_PATH + 1, 0);
std::string cachePath = "d:\\tmp";
if (!GetTempFileName(cachePath.c_str(), "tmp", 0,
&filePath.front()))
throw MakeSystemError("...", GetLastError(), __SOURCE__);
return std::move(filePath);
}




Ну что сказать. Позор анализатору. Ведь действительно сообщает глупость. Переменные filePath и cachePath явно используются. Для предупреждения нет никаких причин. И ладно бы функция была на 1000 строк. Нет, функция проста для безобразия.

Всё, первое впечатление испорчено. Теперь расскажу о результатах расследования.


Анализатор PVS-Studio использует для препроцессирования файлов компилятор Visual C++ (CL.exe) или Clang. Подробнее, о том, как мы используем Clang рассказано в заметке: "Немного о взаимодействии PVS-Studio и Clang".


Препроцессор компилятора Visual C++ работает качественно, но зато крайне медленно. Clang работает быстро, но зато многое не поддерживает или работает неправильно. Разработчики Clang заявляют, что очень хорошо совместимы с Visual C++, но это неправда. Есть множество мелочей, которые они не поддерживают или делают не так как Visual C++. Для анализатора эти мелочи бывают фатальны, как и произошло в этот раз.


По умолчанию анализатор PVS-Studio в начале пытается препроцессировать файл с помощью Clang. Однако он знает: Clang далеко не всегда может препроцессировать то, что может Visual C++. Если возникает ошибка препроцессирования, то запускается CL.exe. Так теряется немного времени на бесполезный запуск Clang, но в целом такой подход очень экономит время на получении *.i файлов.


В данном случае это не помогло. Clang «успешно» препроцессировал файл, хотя на выходе получилась абракадабра.


Причиной неправильного поведения стал макрос __SOURCE__, объявленный в коде следующим образом:



#define __SLINE_0__(_line) #_line
#define __SLINE__(_line) __SLINE_0__(_line)
#define __SOURCE__ __FILE__":"__SLINE__(__LINE__)




При препроцессировании строки:

throw MakeSystemError(_T("GetTempFileName"), GetLastError(),
__SOURCE__);




Она должна превратиться в:

MakeSystemError("GetTempFileName", GetLastError(),
"..path.."":""37");




Именно так и поступает компилятор Visual C++. Все отлично. Анализатор корректно обработает этот код.

Если явно задать в настройках PVS-Studio всегда использовать CL.exe, то ложные срабатывания исчезнут. Однако, если будет запущен Clang, то анализатор будет иметь дело с некорректным кодом.


Clang не может осилить макросы и на выходе мы имеем:



throw MakeSystemError("GetTempFileName", GetLastError(),
"..path.."":"__SLINE__(37));




Макрос __SLINE__ остался нераскрытым.

Получилась некорректная конструкция, недопустимая с точки зрения языка C++. Встретив её, анализатор PVS-Studio пытается как-то обойти некорректный код, чтобы продолжить анализ далее. Лучше что-то пропустить, чем полностью не обработать файл. Часто такие пропуски не оказывают никакого влияние на результаты анализа.


Но в этот раз обойти некорректное место безболезненно не получилось. Был выброшен весь блок:



if (!GetTempFileName(cachePath.c_str(), "tmp", 0, &filePath.front()))
throw MakeSystemError("....", GetLastError(), __SOURCE__);
return std::move(filePath);




Так уж вышло… Анализатор сделал всё, что смог.

Раз этого фрагмента для анализатора не существует, то переменные не инициализированы, никак не используются. Это и приводит к выдаче ложного срабатывания.


Один из вариантов решения проблемы — это всегда использовать препроцессор от Visual C++. Но есть недостаток — медленный анализ.


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


Итак, в этот раз нас подвёл препроцессор, реализованный в Clang. Интересно, что станет причиной написать следующую подобную статью о внешних ошибках.


Вот так и живём. Спасибо за внимание.


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


Recommended article: Chomsky: We Are All – Fill in the Blank.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий