Будучи общепризнанными лидерами и вдохновителями индустрии статического анализа, мы (Gimpel Software) польщены тем, что другие компании ориентируются на наш продукт как на эталон качества при разработке своих инструментов. Обычно мы не считаем нужным как-либо реагировать на публикации о результатах сравнений нашего анализатора с другими продуктами, однако «экспертный отчет», выпущенный компанией 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 prior message
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.
Примечание переводчика
Вот хороший пример, почему мы не любим писать про какие-то ни было сравнения анализаторов. Слишком это комплексная задача, где надо одинаково хорошо знать разные инструменты, не быть субъективным и так далее. Я считаю, что хоть сколько-то правдоподобная оценка может быть только при сравнении результатов проверки большого количества проектов разными анализатора. Но это ооочень большая задача с массой нюансов. Все остальные оценки — это только субъективное мнение, которое, как мы здесь видим, легко может перерасти в конфликтную ситуацию.
Комментарии (4)
splav_asv
28.11.2016 22:19+2Остаётся вопрос — а почему собственно по умолчанию -passes=1? Неужели настолько большая разница в производительности?
sashkin
29.11.2016 01:15+1Чисто предположение: passes – это проходы анализатора, которые требуются, чтобы "складировать" с каждым проходом больше информации о том как та или иная ф-ия вызывается, иначе если просто "в лоб" пойти в глубь дерева вызовов ф-ий, то его можно анализировать годами не вылезая из вложенных циклов анализируя миллиарды-миллиардов вызовов. Используя проходы это гораздо проще.
Предполагаю, что при 1м проходе каждая ф-ия анализируется независимо от других просто по мере её появления в коде выявляя простые проблемы внутри самих ф-ий. Это просто, легко и быстро. При 2+ проходах информация о вызовах всех ф-ий уже суммируется, возможно как-то оптимизируется и складируется для дальнейшего анализа каждой ф-ии в отдельности. Если к примеру некая ф-ия вызываяется в 100 разных местах, то её надо соответственно проанализировать со 100 разными входящими параметрами, что думаю на больших проектах бьёт по скорости анализа весьма неслабо. Отсюда, вероятно, по умолчанию passes=1.
И, как вариант: поскольку один проход самый быстрый и простой – он является самым удобным для дефолтной сборки проекта, т.к. чаще всего сборки происходят именно у разработчиков. Ведь они проекты пересобирают не один раз в день. И если проект огромный с кучей инклюдов, то с 2+ проходами просто изменив 2-3 файла может потребоваться проанализировать с десяток-другой других файлов. Поэтому 2+ прохода имеет смысл включать для специфичных случаев, когда время сборки не настолько критично, например, только на билд-серверах, когда итоговые коммиты от разработчика добираются до него.
Примерно такой ход мыслей.
Antervis
29.11.2016 06:48+1Возможно, специалист Grammatech даже не знал про -passes=2. Возможно, о нем не знают и многие пользователи анализатора. Имо, этот параметр изначально стоило его делать не =1 по умолчанию, а обязательным к указанию
Yeah
Да, лоханулись в Grammatech с такими обвинениями. Зачем писать "не работает", "не находит"?
Учитесь у одной понадкусаной компании:
Мы в Grammatech мечтаем сделать жизнь программиста еще более amazing! CodeSonar на 13,5% более тщательно отслеживает ошибки, чем аналогичные решения конкурентов.
CodeSonar — think different!