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. Цікаво, що стане причиною написати наступну подібну статтю про зовнішніх помилки.

Ось так і живемо. Дякую за увагу.

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

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.