Справжня правда про порівняння CodeSonar і PC-lint

Відразу хочу звернути увагу читачів, що ця стаття є перекладом і я, як розробник конкуруючого продукту PVS-Studio, утримаюся від власних оцінок ситуації.

Будучи загальновизнаними лідерами і натхненниками індустрії статичного аналізу, ми задоволені тим, що інші компанії орієнтуються на наш продукт як на еталон якості при розробці своїх інструментів. Зазвичай ми не вважаємо потрібним як-небудь реагувати на публікації про результати порівнянь нашого аналізатора з іншими продуктами, проте «експертний звіт», випущений компанією Grammatech і присвячений порівнянні інструментів CodeSonar і PC-lint, виявився вкрай неприємним винятком. Замість того щоб зосередитися на перевагах свого продукту, автори цього документа вдалися до помилкових заяв на адресу PC-lint і спотворення фактів щодо його технічних можливостей, що, ймовірно, стало наслідком тиску з боку ринку, і ми вважаємо своїм обов'язком відповісти на цю брехню, розповівши про реальний стан справ.

ПРО PC-lint
PC-lint — це ефективний і шанований серед розробників інструмент статичного аналізу коду на C та C++. Він був створений компанією Gimpel Software в 1985 році і з тих пір безперервно розвивається. Протягом усіх цих 30 років PC-lint лідирував в даній галузі, пропонуючи користувачам інноваційні можливості, наприклад, механізм відстеження даних при їх переміщенні між функціями і модулями програми, сувору перевірку типів і аналіз розмірностей, а також підтримку користувацьких функцій. PC-lint користується довірою десятків тисяч розробників, експертів технічного контролю, тестерів і експертів-криміналістів; підтримує десятки компіляторів і платформ і пропонує цілий ряд додаткових опцій. PC-lint застосовується повсюдно практично в кожній галузі, включаючи області з підвищеними вимогами до безпеки, такі як медицина і автомобілебудування.

Про звіт Grammatech
Експертний звіт під заголовком «How CodeSonar Compares To PC-lint (And Similar Tools)» («Порівняння CodeSonar з PC-lint (та іншими подібними інструментами)»), доступний на сайті Grammatech. Цей документ претендує на достовірне виклад результатів порівняння інструментів CodeSonar і PC-lint (у ньому також згадуються інші аналізатори, але основний акцент робиться на PC-lint), проте насправді являє собою не більш ніж набір своєкорисливих, помилкових тверджень, не підкріплених реальними фактами і покликаних ввести читачів в оману. На підставі цих помилкових тверджень автори звіту намагаються представити продукт CodeSonar у вигідному світлі за рахунок PC-lint.

План спростування
У спробі очорнити PC-lint автори документа вдаються до певних тактичних прийомів, а саме:
  • Будують свої міркування на припущенні, що технічні засоби і функціональні можливості PC-lint практично не розвивалися за час його існування.
  • Навмисно неправильно інтерпретують завдання і можливості PC-lint.
  • Роблять помилкові твердження про діагностичні здібності PC-lint.
Розгляду і спростуванню цих тверджень будуть присвячені два розділи даної статті. В розділі «Звинувачення» ми вивчимо ключові положення звіту, більшість з яких не підкріплені жодними доказами, і надамо факти. В розділі «Перекручування фактів на прикладах» ми розглянемо використані у звіті приклади коду з помилками, які, за заявою авторів, не можуть бути виявлені за допомогою PC-lint, і покажемо справжні діагностичні повідомлення нашого інструменту з результатами аналізу цих фрагментів. У звіті також міститься ряд обгрунтованих критичних зауважень — їх ми розглянемо у відповідному розділі.

Звинувачення
У звіті Grammatech пропонується ряд досить розпливчастих і вкрай неточних тверджень, наприклад:
  • Інструменти статичного аналізу, призначені для пошуку програмних помилок у вихідному коді, існують вже декілька десятиліть. Аналізатори перших поколінь, як наприклад інструменти сімейства lint, сьогодні вже вважаються примітивними. До таких інструментів відносяться як комерційні продукти, наприклад, PC-lint, так і відкриті проекти, наприклад, Cppcheck. В останні роки вони були витіснені більш досконалими інструментами, до яких відноситься і CodeSonar.
Хоча ми згодні з тим, що оригінальний «lint» в Unix був досить примітивний, спроба приписати нашого продукту це ж якість тільки на підставі схожої назви виглядає в кращому випадку нещиро. Більше 30 років Gimpel Software лідирували в області статичного аналізу, а PC-lint сприяв багатьом технічним досягненням протягом цього часу.
  • Головна задача CodeSonar — пошук серйозних дефектів у великих базах коду, тоді як завдання інструментів перших поколінь набагато скромніше. Вони в основному призначені для пошуку в коді невідповідностей несуттєвим стандартам кодування і забезпечення більш строгого контролю типів.
Тут ми бачимо чергову спробу змішати PC-lint з тими самими «інструментами перших поколінь», а також зарозуміле заяву, ніби завдання PC-lint «набагато скромніше». Першочергове завдання PC-lint — це пошук програмних помилок як в малих, так і у великих проектах, включаючи «справжні» помилки начебто переповнення буфера, виходу за межі масиву, логічних помилок і невизначеного поведінки. Ми також ставимо своєю метою задоволення найрізноманітніших запитів наших клієнтів, чиї потреби перевищують можливості багатьох конкурентів. До таким запитам відносяться підтримка різних стандартів MISRA, строгий контроль типів, підтримка користувацького семантики і так далі. Розуміючи, що жоден інструмент в принципі не може ефективно знаходити всі типи помилок, ми ставимо перед собою більш високі цілі. PC-lint прагне не просто знаходити серйозні дефекти, а виявляти прийоми програмування, що призводять до їх появи. Неправильно вважати, що широкий спектр можливостей PC-lint означає нездатність виявляти складні помилки.
  • Хоча інструменти перших поколінь претендують на здатність знаходити деякі серйозні помилки, в дійсності вони можуть виявляти лише найбільш тривіальні проблеми.
І знову дане твердження може бути справедливим відносно оригінального lint, але ніяк не у відношенні PC-lint, і, маючи на увазі таку спадкоємність, автори звіту ведуть себе лицемірно і обманюють читачів.

Далі автори наводять кілька прикладів помилок, пов'язаних з передачею даних між функціями, і стверджують, що PC-lint не вміє знаходити ні одну з них (хоча насправді він знаходить майже всі ці помилки, а також деякі, не згадані у звіті), і заявляють наступне:
  • Всі розглянуті приклади надзвичайно прості. Реальний код, на жаль, набагато складніше: доводиться враховувати безліч одиниць компіляції, рівнів абстракції, а також вкрай заплутані відносини між змінними через використання псевдонімів. І якщо вже інструменти перших поколінь з неглибоким рівнем аналізу нездатні знайти прості дефекти в коротких прикладах, то для пошуку усього розмаїття серйозних помилок у реальних додатках вони виявляються і зовсім непридатні. CodeSonar, навпаки, використовує ряд просунутих методів для моделювання структури програм і тому здатний знаходити реальні помилки.
Дане твердження починається з помилкової передумови, ніби PC-lint під час випробувань не зміг виявити розглянуті у звіті помилки, після чого автори перераховують кілька унікальних «прийомів», які використовуються їх інструментом, хоча в дійсності PC-lint використовує ті ж механізми, причому деякі з них були реалізовані ще до появи CodeSonar, всупереч заявам про те, що їх не знайти ні в одному примітивному аналізаторі».

Говорячи про інтерфейсі, автори стверджують наступне:
  • Користувальницькі інтерфейси порівнюваних продуктів сильно розрізняються. Аналізатори перших поколінь, такі як PC-lint і Cppcheck, спочатку призначалися для запуску з командного рядка подібно компиляторам. Відповідно, звіт з результатами аналізу виводиться у вигляді тексту. Хоча існують рішення для інтеграції цих інструментів з користувальницьким інтерфейсом, що надають більш широкі можливості, такі рішення менш ефективні в порівнянні з інтерфейсом, спочатку розробленим під аналізатор і жорстко пов'язаним з ним. Приклади цього можна побачити нижче.
Насамперед, слід зазначити, що PC-lint має дуже гнучкі налаштування формату виводу і за замовчуванням підтримує текстовий, HTML і XML-формати. Що стосується графічних інтерфейсів, разом з самим аналізатором PC-lint користувачі отримують необхідні інструменти для його інтеграції з існуючими додатками. Так, ми надаємо конфігурації для багатьох популярних середовищ розробки; крім того, існує ряд сторонніх організацій, які розробляють серйозні, повноцінні рішення для інтеграції з такими середовищами розробки, як Visual Studio і Eclipse.

Звіт завершується таким висновком:
  • Технологія, застосовувана в статичних аналізаторах перших поколінь, наприклад, PC-lint і Cppcheck, практично не змінилася за минулі 30 років. З цієї причини вони здатні знаходити серйозні програмні помилки. Розробники, які застосовують ці інструменти, позбавляють себе можливості користуватися плодами десятиліть розвитку статичного аналізу.
Як і майже в усьому іншому тексті звіту, ні в одному з трьох тверджень процитованого уривка не міститься і частки правди. Це абсолютний вигадка, заснований на попередніх завідомо неправдивих тезах.

Спотворення фактів на прикладах
Примітка
Представлені в звіті приклади часто неповні, що, ймовірно, обумовлено їх демонстраційним характером, і тому не дозволяють провести об'єктивне порівняння продуктів або підтвердити заяви Grammartech. З цієї причини автори звіту «підправили» більшість використаних прикладів так, щоб вони виглядали як закінчені, самодостатні фрагменти коду (як, наприклад, на нашій демонстраційної сторінці Online Demo). Для цього вони поміщають блоки коду всередину функцій, оголошують спочатку не визначені типи і функції, виправляють помилки і так далі. У всіх випадках ці зміни ніяк не впливають на семантику коду або здатності PC-lint виявляти шукану помилку, але лише полегшують завдання по відтворенню тих же результатів іншими інструментами.

Переповнення буфера (статичного)
void test_buffer_overrun(int p[]) { 
p[4] = 1729;
}

void test_driver(void) { 
int test[4];
test_buffer_overrun(test);
}

Стосовно даного фрагмента у звіті говориться наступне:

PC-lint не може діагностувати цю помилку, оскільки не вміє відслідковувати шлях проходження даних через кордони процедур.

Це звичайнісінька брехня. Механізм відстеження даних при їх переміщенні між функціями був реалізований в PC-lint п'ятнадцять років тому, тоді як у звіті його наявність заперечується. Щоб користувач міг знайти баланс між потребами проекту і доступними апаратними ресурсами, PC-lint використовує «многопроходную» модель аналізу, що дозволяє задавати потрібну глибину пошуку даного виду помилок. За замовчуванням глибина пошуку дорівнює 1, у той час як для діагностики більшості проблем, пов'язаних з відносинами між функціями, потрібно глибина пошуку 2. Детальну інформацію про роботу даного механізму можна знайти в керівництві користувача PC-lint, а також на нашому офіційному сайті та демонстраційної сторінці Online Demo. Якщо запустити PC-lint з ключем -passes=2 (у прикладах на Online Demo він прописується автоматично), будуть отримані наступні результати:
During Specific Walk:
line 7: test_buffer_overrun([4]) #1
2 Warning 415: Likely access of out-of-bounds pointer 
(1 beyond end of data) by operator '[' 
[Reference: file ipa2.c: lines 2, 7]
2 Info 831: Reference cited in message prior
7 Info 831: Reference cited in prior message

Повідомлення N415 попереджає про шуканому переповненні, заодно вказуючи, як далеко за межами масиву воно виникло, а також виконання яких ділянок коду призвело до помилки. Останні два повідомлення (N831) факультативні і використовуються для виводу тексту попередження в стандартизованому форматі, який може бути розпізнаний середовищами розробки та іншими додатками. У наступних прикладах повідомлення N831 відключені (через параметр -e831) в цілях економії місця, оскільки та ж сама інформація вже включена в основне повідомлення.

Переповнення буфера (динамічного)
typedef unsigned long size_t;
void* malloc(size_t);

void test_buffer_overrun(int p[]) {
p[4] = 1729;
}

void test_driver(void) {
int *p = malloc(4);
test_buffer_overrun(p);
}

Як і в попередньому випадку, в цьому прикладі невірно стверджується, що PC-lint нездатний знайти помилку:

PC-lint не вміє знаходити такі помилки з тієї ж причини, з якої він не вміє знаходити переповнення статично виділяються буферів, як у попередньому прикладі.

Перевірка даного коду за допомогою PC-lint з параметром -passes=2 дає наступний результат:
During Specific Walk:
line 10: test_buffer_overrun([1]? | 0?) #1
5 Warning 662: Possible creation of out-of-bounds pointer 
(4 beyond end of data) by operator '[' 
[Reference: file ipa3.c: lines 5, 9, 10]

During Specific Walk:
line 10: test_buffer_overrun([1]? | 0?) #1
5 Warning 613: Possible use of null pointer 'p' in 
left argument to operator '[' 
[Reference: file ipa3.c: lines 9, 10]

During Specific Walk:
line 10: test_buffer_overrun([1]? | 0?) #1
5 Warning 661: Possible access of out-of-bounds pointer 
(4 beyond end of data) by operator '[' 
[Reference: file ipa3.c: lines 5, 9, 10]

PC-lint знаходить як місце створення покажчика, що вказує за кордону буфера, так і місце його використання, а також перевіряє цей покажчик на значення null (функція malloc може повернути null, а в прикладі ця можливість не перевіряється).

Детальний опис виклику test_buffer_overrun([1]? | 0?) #1, предваряющее текст попередження, показує шлях виконання коду перед появою повідомлення. В даному випадку ми розглядаємо виклик функції test_buffer_overrun, в якому передається покажчик, який вказує на масив з одного елемента ([1]?, наприклад, з одного значення типу int), або є нульовим покажчиком (0?). Знак питання означає, що невідомо, який з цих двох варіантів має місце насправді, оскільки значення не було перевірено на значення null. Таким чином, PC-lint не просто діагностує проблему — він пояснює, як саме прийшов до того або іншого висновку.

Разыменовывание нульового покажчика
#define NULL void *)0

void test_deref(int *p) {
*p = 55;
}

void test_driver(void) {
int *pi1 = NULL;
test_deref(pi1);
}

І знову автори звіту заявляють, ніби наведений приклад PC-lint не по силам:

Це, мабуть, найпростіший приклад разыменовывания нульового покажчика з використанням двох процедур. Тільки CodeSonar може знаходити такі помилки.

І знову це твердження можна спростувати, включивши параметр -passes=2:
During Specific Walk:
File ipa4.c line 9: test_deref(0) #1
4 Warning 413: Likely use of null pointer 'p' in 
argument to operator 'unary *' 
[Reference: file ipa4.c: lines 8, 9]

PC-lint видасть попередження про разыменовывании нульового покажчика і пояснить, як і чому воно відбувається.

Витік пам'яті
typedef unsigned long size_t;
void *malloc(size_t);
void free(void *);

void test_free(int *p, int x) {
if (p && x < 10)
free(p);
}
void test_driver(void) {
int *pi1 = malloc(20);
test_free(pi1, 20);
}

Стосовно цього прикладу у звіті стверджується наступне:

В даному прикладі буфер виділяється в одній процедурі, а звільняється в іншій — але тільки при виконанні певної умови. Ця помилка може бути знайдена тільки з допомогою CodeSonar.

Однак, запустивши PC-lint з параметром -passes=2, ми побачимо, що це не так:
During Specific Walk:
line 11: test_free([5]? | 0?, 20) #1
8 Warning 429: Custodial pointer 'p' (line 5) has not been freed or 
returned

PC-lint зміг виявити і цю помилку, а також видати докладну зведення про виклик, який спровокував попередження.

Обґрунтовані зауваження
У кожного інструменту є сильні і слабкі сторони, і, якщо знати про недоліки конкретного інструменту, не важко придумати штучні приклади, щоб підкреслити їх. У звіті можна знайти кілька дуже старанно сконструйованих прикладів коду, що експлуатують реальні, хоча і добре відомі, недоліки PC-lint. Більшість з цих прикладів виявляють обмеження здатності PC-lint відстежувати дані, пов'язані з покажчиками. Система відстеження даних була доопрацьована в PC-lint Plus (наступний крок у розвитку PC-lint; на даний момент програма знаходиться в стадії бета-тестування), і вказані обмеження були усунені. Приклади нижче не діагностуються PC-lint, однак ми наведемо результати їх аналізу за допомогою PC-lint Plus, щоб показати, що ми постійно працюємо над покращенням нашого продукту.

Неініціалізовані змінні
int foo() {
int, iret;
int *p = &iret;
return iret;
}

Повідомлення PC-Lint Plus:
4 warning 530: likely using an uninitialized value
return iret;
^
2 supplemental 891: allocated here
int, iret;
^

Звернення до визволеної пам'яті
typedef unsigned long size_t;
void *malloc(size_t);
void free(void *);

void foo() {
char *p = (char *)malloc(10);
char *q = p;
if (p) {
p[0] = 'X';
free(p);
q[0] = 'Y';
}
}

Повідомлення PC-lint Plus:
11 warning 449: memory was likely previously deallocated
q[0] = 'Y';
^
10 supplemental 891: deallocated here
free(p);
^
6 supplemental 891: allocated here
char *p = (char *)malloc(10);

^

Подвійне звільнення пам'яті
typedef unsigned long size_t;
void *malloc(size_t);
void free(void *);

void test_double_free(int *p) {
if (p)
free(p);
}

void test_driver(void) {
int *pi1 = (int *)malloc(sizeof(int));
if (pi1)
test_double_free(pi1);
if (pi1)
free(pi1); 
}

Повідомлення PC-lint Plus:
15 warning 2432: memory was potentially deallocated
free(pi1);
^
15 supplemental 894: during specific walk free([4]@0/1)
free(pi1);
^
7 supplemental 891: deallocated here
free(p);
^
11 supplemental 891: allocated here
int *pi1 = (int *)malloc(sizeof(int));
^

Переповнення буфера
void foo() {
char buffer[10];
char *pc;
pc = buffer;
for (int i = 0; i < = 10; i++)
*pc++ = 'X';
}

Даний приклад демонструє переповнення буфера в циклі for. Використовувані в PC-lint і PC-lint Plus моделі для відстеження станів значень (поки) не враховують відносини між i pc в цьому коді. На даний момент це об'єктивний недолік PC-lint. Ми могли б з легкістю придумати аналогічний приклад, щоб підкреслити слабкі сторони CodeSonar, але це нічого не дало. Як вже говорилося вище, ми визнаємо, що у кожного інструменту є свої переваги і недоліки і що кожен інструмент може діагностувати помилки, невидимі для інших. Замість того щоб вказувати на недоліки інших аналізаторів, ми вважаємо за краще працювати над достоїнствами PC-lint і безперервно покращувати наш продукт, щоб він відповідав потребам користувачів.

Резюме
У таблиці нижче ми зібрали ключові помилкові твердження Grammatech щодо PC-lint і спростовують їх факти з нашого боку.

Помилкове твердження справді
PC-lint — це примітивний інструмент з сімейства оригінального lint в Unix. PC-lint — це передовий статичний аналізатор, який безперервно і незалежно від інших інструментів розвивається і удосконалюється протягом останніх 30 років, пропонуючи інноваційні, відмічені нагородами засоби аналізу. Одне з них — система відстеження даних при їх передачі між функціями і модулями.
PC-lint може виявляти тільки найбільш очевидні помилки. PC-lint використовує цілий ряд передових технологій, що дозволяє йому виявляти складні помилки, у тому числі помилки прикладів у звіті Grammartech
PC-lint не призначений для пошуку серйозних помилок у великих проектах. PC-lint успішно застосовується на проектах будь-якого розміру, від сотень рядків коду до мільйонів.
PC-lint підтримує тільки текстовий формат виводу і не пристосований для використання з інструментами безперервної інтеграції. PC-lint підтримує практично будь-який формат звіту, включаючи простий текст, HTML і XML, і, як показує досвід наших клієнтів, може застосовуватися різними способами в зв'язці з іншими додатками, в тому числі інструментами безперервної інтеграції, такими як Hudson і Jenkins.
PC-lint не показує шлях виконання коду, який спровокував попередження, при пошуку складних помилок. PC-lint надає детальну інформацію, де це можливо, включаючи послідовність викликів і значень, що призвели до того або іншого висновку.
PC-lint призначений тільки для розробників. PC-lint, у відповідності зі своїм призначенням, використовується розробниками ПЗ, експертами по технічному контролю, тестерами і експертами-криміналістами.
PC-lint вміє розпізнавати проблеми лише в межах файлу. PC-lint з самого початку свого існування вміє аналізувати програми, у тому числі відносини між модулями. Ця особливість, серед інших, і відрізняла його від інших інструментів.
PC-lint нездатний знайти всі можливі програмні помилки. Враховуючи, що жоден статичний аналізатор не здатний діагностувати всі помилки, PC-lint, тим не менш, має великий послужний список виявлення дефектів, що включає безліч складних, реально існуючих помилок, і його можливості продовжують удосконалюватися.
Висновок
Gimpel Software вітає щирі відгуки та конструктивну критику як від наших користувачів, так і від конкурентів, однак свідомо неправдиві заяви, які роблять автори розглянутого звіту, не є ні чесними, ні конструктивними і не служать інтересам програмістів або індустрії статичного аналізу. Політика Grammartech, заснована на помилкових і зневажливих заяв на адресу конкуруючих продуктів, виставляє в невигідному світлі саму цю компанію, а не її конкурентів.

Як можна укласти з звіту, основні відмінності між PC-lint і CodeSonar зводяться до наступного:
  • CodeSonar оснащена жорстко інтегрованим графічним інтерфейсом, в той час як PC-lint надає користувачеві гнучкі можливості по інтеграції аналізатора з сучасними інструментами, такими як Visual Studio, Eclipse та інші.
  • Область застосування CodeSonar вже, ніж у PC-lint, отже, його набір функціональних можливостей менше, ніж у нашого продукту.
  • CodeSonar володіє рядом функцій, поки не підтримуються PC-lint. Зокрема, у звіті згадуються аналіз метрик та досконалий-аналіз — обидва цих механізму ми розробляємо для майбутньої версії PC-lint Plus. Зрозуміло, багато наявні в PC-lint функціональні можливості відсутні у CodeSonar.
Примітка перекладача
Ось хороший приклад, чому ми не любимо писати про якісь не було порівняння аналізаторів. Надто це комплексне завдання, де треба однаково добре знати різні інструменти, не бути суб'єктивним і так далі. Я вважаю, що хоч скільки-то правдоподібна оцінка може бути тільки при порівнянні результатів перевірки великої кількості проектів різними аналізатора. Але це дуууже велике завдання з масою нюансів. Всі інші оцінки — це лише суб'єктивна думка, яка, як ми тут бачимо, легко може перерости на конфліктну ситуацію.
Джерело: Хабрахабр

0 коментарів

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