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

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

![0303_PVS-Studio-Hostile-Environment_ru/image1.png](https://import.viva64.com/docx/blog/0303_PVS-Studio-Hostile-Environment_ru/image1.png)

Я считаю PVS\-Studio очень качественным продуктом\. Мы можем почти в любой день сделать и выложить дистрибутив\. У нас используется очень большое количество автоматизированных тестов различного уровня и типов\. Вот описание некоторых из них: "[Как мы тестируем анализатор кода](https://pvs-studio.ru/ru/blog/posts/a0047/)"\. Сейчас их стало больше\. Например, теперь для статического анализа мы используем не только свой собственный анализатор, но и Clang\.  Если исправленная версия прошла все тесты, значит ее можно смело выдавать пользователям\.

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

* Сторонний add\-in что\-то портит в окружении Visual Studio\. В результате приходится создавать костыль для обхода проблемы или смириться и ответить "извините, ничего не можем сделать"\. Один из таких примеров \- "[Описание ошибки интеграции Intel Parallel Studio Service Pack 1 в Visual Studio 2005/2008](https://pvs-studio.ru/ru/blog/posts/0056/)"\.
* COM\-интерфейсы Visual Studio для получения информации о проекте неожиданно могут кинуть исключение\. Видимо, среда в этот неудачный момент занята чем\-то еще\. Вызовы приходится заворачивать в цикл для многократного их повторения\. Танцы с бубном, которые не всегда помогают\.
* У разработчика стоит антивирус X и, согласно корпоративной политике, у него нет прав менять его настройки\. Этот антивирус "держит" какое\-то время некоторые временные файлы, и анализатор не может их удалить\. В результате получается, что анализатор "гадит" в папку проекта\.
* Разное можно вспоминать\. Некоторые примеры можно посмотреть [здесь](https://pvs-studio.ru/ru/blog/posts/0181/), [здесь](https://pvs-studio.ru/ru/blog/posts/0086/) и [здесь](https://pvs-studio.ru/ru/blog/posts/0031/)\.

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

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

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

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

_filePath и cachePath отмечены как неиспользуемые \(предупреждение [V808](https://pvs-studio.ru/ru/docs/warnings/v808/)\), хотя видно, что они используются буквально в следующей строке после объявления\._

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

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

```cpp
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](https://pvs-studio.ru/ru/blog/posts/0186/)"\.

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

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

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

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

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



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

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

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

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

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

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

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

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

Макрос \_\_SLINE\_\_ остался нераскрытым\. 

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

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

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

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

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

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

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

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



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

[Пробуйте](https://pvs-studio.ru/ru/pvs-studio/download/) наш статический анализатор кода на своих проектах и, если что\-то не ладится, [пишите нам](https://pvs-studio.ru/ru/about-feedback/)\. Часто мы превращаем негативный настрой в положительный\.