Новий механізм придушення непотрібних повідомлень аналізатора

PVS-Studio
На даний момент аналізатор PVS-Studio вже має механізм для придушення помилкових спрацьовувань (False Positive). Цей механізм повністю влаштовує нас з функціональної точки зору, тобто у нас немає претензій до надійності його роботи. Однак, у деяких наших користувачів і клієнтів виникало бажання мати можливість працювати з повідомленнями аналізатора тільки на «новому», тобто знову написаному коді. Це бажання цілком можна зрозуміти, враховуючи, що у великому проекті аналізатор може згенерувати тисячі або навіть десятки тисяч повідомлень на існуючий код, правити які, звичайно, ніхто не стане.



Можливість розмітки повідомлень, як «помилкових» в якомусь сенсі перетинається з бажанням працювати тільки з «новими» повідомленнями, т. к. ніщо, теоретично, не заважає розмітити всі знайдені повідомлення, як «помилкові», і надалі працювати тільки з повідомленнями знову написаному коді.

Однак, у існуючому механізмі для розмітки «помилкових повідомлень» існує принципова usability проблема (про що ми поговоримо далі), яка може стати перешкодою при його використання на реальних проектах для вирішення даної задачі. Зокрема, цей існуючий механізм не передбачає використання для «масової» розмітки, що неминуче при обробці тисяч повідомлень аналізатора.

У зв'язку з тим, що описана вище проблема є фундаментальною для існуючої методики, її неможливо усунути із збереженням цієї методики. Тому має сенс розглянути можливість реалізації альтернативного способу для вирішення даної задачі.

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

Механізм зіставлення вихідного коду і діагностик може бути використаний для вирішення 2-х завдань:
  1. Завдання придушення помилкових спрацьовувань. Користувачеві надається можливість позначити повідомлення аналізатора, які той вважає помилковими, спеціальним чином, що надалі дозволить йому здійснювати фільтрацію таких повідомлень (наприклад, приховувати їх). Ця розмітка повинна зберегтися і при наступних запусках аналізатора, в тому числі і після того, як вихідний код змінено. Даний функціонал вже досить давно доступний в PVS-Studio.
  2. Завдання придушення результатів попередніх запусків полягає в наданні користувачеві можливості бачити тільки «свіжі» результати запуску аналізатора (тобто результати, які вже були знайдені на попередніх запусках, не повинні відображатися). Дана задача не була раніше реалізована в PVS-Studio, і саме про неї піде мова в наступному розділі статті.
PVS-Studio має механізмом зіставлення вихідного коду і діагностик, заснованому на маркерах (коментарях спеціального виду) у вихідному коді. Механізм реалізований на рівні ядра аналізатора (PVS-Studio.exe) і IDE плагінів. IDE плагін здійснює початкову розстановку маркерів в коді, а також дозволяє фільтрувати результати аналізу з цього маркеру. Ядро аналізатора може «підхоплювати» вже присутні в коді маркери і розмічати свій висновок, тим самим зберігаючи розмітку коду від попередніх запусків.

Розглянемо переваги і недоліки існуючого механізму.

Переваги:
  • Простота реалізації на рівні ядра і плагінів
  • Інтуїтивність використання для користувачів інструменту, можливість ручного розмітки.
  • Гарантія збереження зв'язку «код — діагностика» при будь-яких подальших модифікаціях коду користувачем.
  • «Безкоштовно» підтримка командного розробки — т. к. маркери зберігаються у вихідних файлах, для їх синхронізації може використовуватися та ж система, що і для синхронізації самих файлів (наприклад, система контролю версій)
Недоліки
  • Засмічення коду коментарями спеціального виду, що не належать до логіки роботи цього коду.
  • Проблема при використанні систем керування версіями, необхідність закладати коментарі спеціального виду в репозиторій.
  • Потенційна можливість псування вихідного коду при глобальної масової розмітці.
Описані вище проблеми роблять неможливим з практичної точки зору використання існуючого механізму зіставлення для реалізації завдання придушення результатів попередніх запусків, тобто для «масової» розмітки повідомлень на існуючій кодової базі.

Іншими словами, ніхто не хоче не дивлячись додати в код 20000 коментарів, що пригнічують наявні повідомлення, і закласти всі ці зміни в систему контролю версій.

Новий механізм зіставлення діагностик з вихідним кодом заснований на файлах баз повідомлень.
Як було показано раніше, головною проблемою існуючого механізму є його завязанность на модифікацію вихідного коду користувача. Із цього факту випливають як властиві такому підходу безсумнівні плюси, так і мінуси. Очевидним стає, що для реалізації альтернативного підходу необхідно відмовитися від модифікації коду користувача, і зберігати інформацію про зв'язці «діагностика аналізатора — код користувача» в деякому зовнішньому сховищі, а не в самих вихідних файлах.

Довгострокове зберігання такої розмітки зіставлень ставить принципове завдання обліку змін на великому часовому проміжку в діагностиках самого аналізатора, так і у вихідному коді користувача. Зникнення діагностики у видачі аналізатора не є принциповою проблемою, так як повідомлення з такою діагностикою вже було розмічено, як помилкове\непотрібне. А от зміни в коді користувача можуть призвести до «другого пришестя» повідомлень, які раніше вже були розмічені.

Дана проблема не страшна при використанні механізму розмітки коду. Як би сильно не змінився ділянку коду, маркер залишиться в ньому до тих пір, поки користувач сам (вольовим рішенням або через незнання не видалить його, що здається малоймовірним. Більш того, досвідчений користувач може сам додати такий маркер на новий (або змінений) ділянку коду, якщо він знає, що тут аналізатор буде лаятися.

Що саме потрібно для ідентифікації діагностичного повідомлення аналізатора? Саме повідомлення аналізатора містить ім'я файлу, проект, номер рядка у файлі і контрольні суми попередньої, поточної і подальшої рядків коду, на яких ці діагностики були знайдені. Для зіставлення діагностики при зміні у вихідному коді однозначно необхідно буде не враховувати номер рядка, т. к. він змінюється непередбачувано і при найменшій модифікації документа.

Для реалізації описаного вище сховища «зв'язків» діагностик з кодом користувача ми пішли по шляху створення локальних файлів баз». Такі файли (файли з розширенням suppress) створюються поруч з проектними файлами (vcproj\vcxproj) і містять в собі списки розмічених «непотрібних» діагностик. Діагностики зберігаються без урахування номерів рядків, шляхи до файлів, в яких ці діагностики були ідентифіковані, зберігаються у відносному форматі — щодо проектних файлів. Це дозволяє переносити файли між машинами розробників навіть якщо проекти у них розгорнуті в різних місцях (з точки зору файлової системи). Ці файли можна закладати в системи контролю версій, адже в більшості випадків проектні файли самі по собі зберігають шляху до вихідних файлів в такому ж відносному форматі. Винятком тут є генеруються файли проектів, як у випадку з CMake, наприклад, де «дерево исходников» може бути розміщене незалежно від «дерева проектів».

Ми використовували наступні поля для ідентифікації повідомлення у suppress файлі:
  • Текст діагностичного повідомлення аналізатора;
  • Код помилки повідомлення;
  • Відносне ім'я файлу, в якому повідомлення було знайдено;
  • Хеш суми рядка c кодом, на якій повідомлення було знайдено, а також попереднього та наступного рядків коду.
Як видно, саме за рахунок зберігання хеш сум рядків вихідного коду ми хотіли б співвідносити повідомлення аналізатора з кодом користувача. При цьому, якщо код користувача «зсувається», то зрушиться і повідомлення аналізатора, однак «контекст» цього повідомлення (тобто код, який його оточує) залишиться незмінним. Якщо ж користувач править свій код в місці, де повідомлення було згенеровано, то цілком логічно вважати такий код «новим», і показати повідомлення аналізатора на цей код. При цьому, якщо користувач реально «виправив» помилку, на яку вказував своїм повідомленням аналізатор, то повідомлення просто «зникне». Інакше, якщо підозріле місце не виправлено — користувач знову побачить повідомлення аналізатора.

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

IDE плагіни PVS-Studio автоматично створюють suppress файли при початковій розмітці повідомлень, і надалі зіставляють все знову згенеровані діагностики з тими, що містяться в suppress базах. І, якщо після повторної перевірки знову згенероване повідомлення буде ідентифіковано в базі, воно не буде показано користувачеві.

Статистика по використанню нового механізму придушення
Після реалізації першого працездатного прототипу нового механізму ми, природно, захотіли подивитися, як цей механізм покаже себе при роботі з реальними проектами. Ми не стали чекати кілька місяців\років, поки у таких проектах накопичаться достатню кількість змін, а просто взяли кілька минулих ревізій в декількох великих open source проектах.

Що ми хотіли побачити? Ми брали якусь досить стару ревізію проекту (в залежності від активності розробників, це могла бути і тиждень, і цілий рік), перевіряли її нашим аналізатором, закладали всі отримані повідомлень у suppress бази. Потім оновлювали проект до його останньої head ревізії і перевіряли аналізатором знову. В ідеалі ми повинні були б побачити повідомлень, знайдені тільки на «новому» коді, тобто код, який був написаний у розглянутий нами проміжок часу.

При перевірці першого проекту ми зіткнулися з низкою проблем і обмежень нашої методики. Розглянемо їх детальніше.

По-перше, що, в принципі, було очікувано, повідомлення «знову з'являлися» у разі, якщо модифікувався код в місці видачі самого повідомлення, або на попередній\наступному рядку. При цьому, якщо модифікація рядки самого повідомлення цілком очікувано призводила до «воскресіння» такого повідомлення, то модифікація оточуючих рядків, як може здатися, до цього приводити не має. Це, зокрема, і є одним з основних обмежень обраної нами методики — ми прив'язуємося до тексту вихідного файлу на цих 3-х рядках. Далі, прив'язуватися тільки до однієї рядку здається недоцільним — надто багато повідомлень потенційно можуть бути «переплутані». У статистиці за проектами, яка буде наведено далі, ми позначимо такі повідомлення «парними» — тобто повідомлення, які як би вже є в suppress базах, але спливли знову.

По-друге, з'ясувалася ще одна особливість (а точніше — чергове обмеження) нашого нового механізму — «воскресіння» повідомлень h (заголовних) файли у разі, коли ці файли включалися в інші вихідні файли, в інших проектах. Це обмеження пов'язано з тим, що бази створюються на рівні IDE проекту. Аналогічна ситуація виникає і в разі появи нових проектів у рішенні, переиспользующих відмінності\вихідні файли.

Далі, виявилося не дуже хорошою ідеєю орієнтуватися на текст повідомлення аналізатора для ідентифікації такого повідомлення у базах. Іноді текст повідомлення аналізатора може містити в собі номери рядків у вихідному коді (вони змінюються у разі зсуву) і імена змінних, які фігурують у коді користувача. Ми вирішили цю проблему, зберігаючи в базі не повне повідомлення аналізатора — з нього вирізаються всі цифрові символи. А ось «воскресіння» повідомлення при зміні імені змінної ми вирішили порахувати коректним, адже могло змінитися не тільки її ім'я, але й визначення — ми вважаємо це вже «новим» кодом.

Нарешті, деякі повідомлення «мігрували» — то код з ними був скопійований у інші файли, то файли включалися в інші проекти, що, в принципі, перетинається з самої першої описаною проблемою нашої методики.

Перерахуємо статистику по декількох проектах, на яких ми тестували нову систему. Велика кількість діагностичних повідомлень викликано тим, що враховувалися взагалі всі повідомлення. У тому числі діагностика 64-бітових помилок, яка на жаль, взагалі генерує багато помилкових спрацьовувань і з цим нічого не можна вдіяти.
  1. LLVM -великий проект для універсальної системи аналізу, трансформації та оптимізації програм. Проект вже не один рік активно розвивається, відповідно, достатньо було взяти динаміку змін всього за 1.5 місяці, щоб отримати велику кількість модифікацій коду. Добре відомий компілятор Clang є частиною цього проекту. Для 1600-1800 файлів проекту було позначено 52 000 повідомлень як непотрібні. Через 1.5 місяці було виявлено 18 000 нових повідомлень аналізатора, з них 500 парних і 500 повідомлень, які мігрували в інші файли;
  2. Miranda — всім відома програма обміну повідомленнями для ОС Windows. Miranda налічує 11 версій з часів свого першого випуску. Ми взяли останню з них: Miranda NG. На жаль, із-за конфліктів у команді розробників Miranda, дана версія не змінювалася дуже часто: довелося брати зміни з інтервалом аж в 2 роки. Для 700 файлів було позначено 51 000 повідомлення як непотрібні. Через два роки з'явилося всього 41 нове повідомлення;
  3. ffdShow — медіа декодер, який зазвичай використовується для швидкого і високоточного декодування відеопотоків. ffdShow досить закінчений проект, на момент написання статті, останній реліз був в Квітні 2013 року. Ми взяли динаміку змін за 1 рік. З 570 файлів 21 000 повідомлень було позначено як непотрібні. Через рік з'явилося 120 нових повідомлень;
  4. Torque3D — ігровий движок. Зараз проект практично не розвивається, але по початку все було інакше. Останній реліз, на момент написання статті, був 1 Травня 2007 року. На момент активних розробок динаміка змін з інтервалом у тиждень видала 43259 повідомлень. За цей проміжок часу з'явилося 222 нових;
  5. OpenCV — бібліотека алгоритмів комп'ютерного зору, обробки зображень і чисельних алгоритмів загального призначення. Досить динамічно розвивається проект. Ми взяли динаміку змін за 2 місяці і за рік. Було помічено 50948 непотрібних повідомлень. З них 1174 нових повідомлень, після закінчення 2-х місяців і 19471 через рік;
Які ж висновки ми можемо зробити з отриманих нами результатів?

Цілком очікувано, що на проектах, що не розвиваються, активно, ми не побачили великої кількості «нових» повідомлень, навіть на такому великому відрізку часу, як цілий рік. Зауважимо, що для таких проектів ми не стали вважати кількість «парних» і мігрували повідомлень.

Але найбільший інтерес, безумовно, для нас представляють «живі проекти». Зокрема, на прикладі LLVM ми бачимо, що кількість нових повідомлень склало 34% від позначених на версії, що відстає по часу всього на 1.5 місяця! Тим не менш, з цих 18 000 нових повідомлень тільки 1000 (500 мігрували + 500 парних) відносяться до обмежень нашої методики, тобто всього 5% від загального числа нових повідомлень.

На наш погляд, ці цифри дуже добре продемонстрували життєздатність нового механізму. Звичайно, варто пам'ятати, що новий механізм придушення ні в якому разі не є «панацеєю», але ніщо не скасовує можливість використовувати численні вже існуючі раніше методи придушення\фільтрації. Наприклад, якщо якесь повідомлення в h файлі починає дуже часто «спливати», не буде нічого поганого в тому, щоб «убити» його назавжди, додавши до рядку коментар виду //-Vxxx.

Незважаючи на те, що новий механізм вже досить налагоджений, і ми готові його показати нашим користувачам в наступному релізі, ми вирішили продовжувати його тестувати, організувавши регулярну (кожну ніч) перевірку проекту LLVM/Clang. Новий механізм дозволить нам дивитися тільки на повідомлення з «свіжого» коду проекту — теоретично ми зможемо знаходити помилки ще до того, як їх виявлять у себе розробники. Це дуже добре покаже реальну користь від регулярного використання статичного аналізу — і це було б неможливо без нашої нової системи придушення, адже нереально переглядати по 50 000 кожен день. Чекайте звітів про знайдених свіжих баги в Clang в нашому twitter.

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

0 коментарів

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