Личный опыт разработки ПО
Использование Cppcheck для статического анализа кода
Что такое статический анализатор кода? Это программа, которая проверяет исходный код программы и пытается найти в нем ошибки. Иногда это у нее ловко получается. Диагностируются выходы за границы массивов, утечки памяти, использование неинициализированных переменных и прочие неприятные вещи. Часто минутный прогон программы позволяет сэкономить час работы в отладчике в поисках бага.
Цена вопроса
Есть программы которые стоят немалых денег (Coverity Prevent for C/C++, Klocwork), а есть абсолютно бесплатные (Cppcheck).
Платные я не тестировал, но судя по статье, Cppcheck им нисколько не уступает. Так на тесте приведенном в указанной статье она находит все ошибки. Вот результат работы:
Кроме затрат материальных, у инструментов есть определенный уровень вхождения, который некоторых может остановить. Я человек ленивый, поэтому мне особенно приятно, что у данного инструмента этот порог очень низкий.
Есть версия программы с графическим интерфейсом, где единственное усилие которое нужно сделать – выбрать пункт меню Check->Files для проверки одного файла, или Check->Directory для проверки всей директории, после чего просмотреть результаты работы.
Также есть консольная версия, на вход которой также достаточно подать имена проверяемых файлов.
Интеграция в систему сборки
Уже упомянутую консольную версию программы легко добавить в скрипт сборки проекта, чтобы автоматически проводить проверку. При этом Cppcheck может в случае нахождения ошибок вернуть определенный код. Используйте для этого ключ —error-exitcode=код_возврата.
Вывод
Cppcheck отличный инструмент, очень простой в использовании, бесплатный и довольно эффективный. Крайне рекомендую к использованию. Есть версии как для Windows, так и для Linux.
Источник
Анализируем исходный код с помощью cppcheck
В свете множества недавних статей, посвящённых статическому анализу кода на С++, пользователи неоднократно интересовались анализатором cppcheck. Это относительно молодой проект статического анализа с открытым исходным кодом, ориентированный в первую очередь на нахождение реальных ошибок в коде с минимальным количеством ложных срабатываний.
Совсем недавно cppcheck помог найти уязвимость в проекте Xorg, которая существовала там почти 23 года! Он помог уже тысячам программистов по всему миру, на официальном сайте можно найти информацию о найденных с помощью cppcheck уязвимостях в программах, и этот список постоянно растёт. Итак, если вы хотите знать, почему нужно использовать cppcheck всегда и везде — прошу под кат.
CppCat и cppcheck
Начну со сравнения этих утилит, поскольку данная просьба озвучивалась неоднократно в комментариях. Разработчики CppCat уже самостоятельно провели такое сравнение (с PVS-Studio), но с тех пор утекло много воды, а сравнение не очень объективно, так как PVS-Studio (насколько я понимаю всю замысловатость фразы «Please write us to get a price for PVS-Studio. Please specify interesting license type.») не предназначена для программистов-одиночек. CppCat же, как и cppcheck, доступен каждому (с оговоркой на привязку к VisualStudio определённых версий и лицензию на год).
Сравнить эти анализаторы будет непросто: у меня не имеется под руками ни одной версии Visual Studio под Linux. Поэтому сначала я ограничусь анализом уже проанализированного кода в недавнем обзоре CppCat: натравлю cppcheck на Notepad++, приведу статистику ошибок/предупреждений, которые можно будет сравнить с уже готовым анализом CppCat.
Параллельно пробую поставить в виртуальной машине CppCat. Надо сказать, что после того как Visual Studio 2010 был установлен, инсталлятор недолго думая выдал следующее:
Из-за чего тестирование усложнилось квестом найди-поставь-Visual-Studio-2013-переустанови-IE-11-перезагрузись-обновись, что на виртуалке, не обременённой обновлениями, заняло ровно полдня.
Что в итоге? При открытии проекта Notepad++ Visual Studio гордо завис. Попытка создать новый проект привела к тому, что в анализе CppCat посыпались ошибки о не найденных заголовочных файлах. По сему сравнивать придётся с тем, что имеем в предыдущей статье. Хотя Visual Studio я ставлю практически в первый раз, но эффект «юзабилити» налицо.
Подготовка
Так как cppcheck — проект с открытым исходным кодом, никто не мешает загрузить самую свежую версию из гита и скомпилировать её самостоятельно. Cppcheck предназначен для разработчиков, поэтому компиляция программы из исходного кода не должна вызывать каких-либо проблем:
Готово. Я специально взял свежую версию из гита и положил её в отдельную папку — с ней будет проще работать, так как в дальнейшем cppcheck можно сильно улучшить и настроить «под себя».
Тестовая конфигурация: RHEL 6.1, процессор i5-2400 @ 3.10GHz (для оценки времени работы анализатора).
Удобства ради действия будут производится в командной строке — при желании их можно будет повторить (в Linux:). Конечно, cppcheck имеет несколько плагинов для популярных IDE, но сегодня не об этом.
Анализ Notepad++
cppcheck устроен так, что все предупреждения отсортированы по категориям. По умолчанию включён только один вид анализа — error (ошибки). Ошибки нельзя игнорировать, так как если cppcheck выдал error — это место придётся переписывать в 99% случаев. Основной улов cppcheck — утечки памяти и переполнение буфера, а это уже чего-то стоит.
Может возникнуть резонный вопрос — как анализировать notepad++ в операционной системе Linux, если notepad++ использует исключительно WInAPI? Ответ прост — cppcheck на моей памяти единственный анализатор, который не привязан к сборочной среде или операционной системе. Он использует свой лексический анализатор, не требует обязательного присутствия всех заголовочных файлов, лояльно относится к хитросплетениям классов и т. п. Это замечательное свойство позволяет использовать cppcheck где угодно и для чего угодно, чего не скажешь о CppCat (см. квест по установке выше).
Анализ с помощью cppcheck прост до безобразия:
Пока проводится простейший анализ «из коробки». Команда определяет два параметра -q («тихий» режим — не выводить прогресс выполнения на экран) и -j4 — многопоточный анализ в 4 потока по количесву ядер процессора.
Результат выполнения предыдущей команды:
Время работы — 5 минут. В глаза сразу бросается ошибка «(error) Internal error. Token::Match called with varid 0. Please report this to Cppcheck developers». Это означает, что вместо бага в анализируемой программе нашёлся баг в самом анализаторе:) Сделаем скидку на то, что проект заточен под Win, и cppcheck не подозревает, что означают DWORD, LPTR и т. п. Возможно, в Win он покажет себя иначе.
Реально нашлась всего одна ошибка (с разницей в 2 строчки). Неплохо, возможно, что автор notepad++ сам пользуется cppcheck. Участок кода, который вызвал у cppcheck подозрение:
Это выглядит как ложное срабатывание, хотя исходники, честно говоря, шокируют. UPD: это всё-таки undefined behaviour, массив нужно освобождать с помощью оператора delete [].
Но это ещё не всё. Дело в том, что девизом cppcheck является отсутствие ложных срабатываний, то есть по умолчанию сканер ищет только очень критические ошибки — переполнение буфера, утечки памяти. После того как все ошибки найдены, можно просканировать повторно, включив флаги предупреждений:
Используется параметр —enable, который включает категории проверок:
— performance — проблемы производительности;
— portability — проблемы совместимости;
— warning — предупреждения — подозрительные места программы;
— style — ошибки стиля программирования.
В таком режиме вылавливается львиная доля стилистических/логических ошибок и потенциальных багов (т. е. ошибки, в которых cppcheck «не уверен»). Время сканирования — 5 минут. Результат я сразу отправил в файл, чтобы собрать статистику.
Небольшая статистика по типу найденных ошибок:
Всего 28 уникальных сообщений.
Сообщения «The scope of the variable % can be reduced», «C-style pointer casting», «Variable % is assigned in constructor body.» можно не рассматривать — это стилистические рекомендации, очень характерные для многих проектов, написанных на старых плюсах.
Море переменных не инициализировано в конструкторе: (warning) Member variable % is not initialized in the constructor. Это ошибку cppcheck считает предупреждением. Возможно, поведение такого кода зависит от компилятора, потому что npp каким-то чудом работает.
(style) Same expression on both sides of ‘||’. Проверка одного и того же условия. Эту же ошибку выдавал CppCat, однако то ли в той статье версия npp старая, то ли ошибку уже пофиксили, но сейчас тот самый код выглядит так:
А это — новая добыча cppcheck:
(performance) Variable ‘lineIndent’ is reassigned a value before the old one has been used. По сути — двойное присваивание. Обычно это следствие копипасты, но cppckeck характеризует такую ошибку как ошибку производительности. Этот код стоит проверить, так как неизвестно, что подразумевал автор программы. Таких двойных присваиваний, а также неиспользуемых значений переменных по коду очень много:
Это предупреждение обычно бесполезно — редко, когда в данном участке кода есть ошибка, просто при рефакторинге забыли удалить старое значение.
(portability) The extra qualification ‘FunctionListPanel::’ is unnecessary and is considered an error by many compilers. Полезное предупреждение, которого физически нет и не планируется в CppCat: ошибки переносимости между разными платформами (portability). Данный кусок кода будет работать не во всех компиляторах:
Это «политкорректное» сообщение на самом деле означает, что код не соберётся без костылей в компиляторе gcc. Если вы планируете запускать приложение больше чем на одной платформе — cppcheck станет хорошим подспорьем.
(style) Exception should be caught by reference. Интересное предупреждение — ловить исключение следует по ссылке, а не по значению:
(style) Consecutive return, break, continue, goto or throw statements are unnecessary. Мёртвый код: break после return:
(warning) Assignment of function parameter has no effect outside the function. Обычно это полезное предупреждение, сигнализирующее об опечатке — значение, присвоенное внутри функции, никуда не передаётся. Однако здесь это очевидно ложное срабатывание, так как value — переменная класса:
(portability) scanf without field width limits can crash with huge input data on some versions of libc. Подобная привычка использования scanf может привести к опасному переполнению буфера. В случае числовых переменных это банальный undefined behaviour:
Для преобразования чисел лучше использовать безопасный strtol.
Ещё один представитель:
Здесь нет переполнения буфера только потому, что wordBuffer и sKeywordBuffer одинакового размера.
(style) ‘TiXmlStringA::operator=’ should return ‘TiXmlStringA &’. Оператор = возвращает void:
С таким оператором нельзя использовать стандартную для C++ цепочку:
(warning) The class ‘ControlsTab’ defines member variable with name ‘_isVertical’ also defined in its parent class ‘TabBar’. Ошибка двойного определения переменной в классе:
которая уже определена в родительском классе:
Не являясь экспертом в плюсах, не могу сразу ответить, можно ли так делать (protected/private).
(style) Found duplicate branches for ‘if’ and ‘else’. Аналогичные ошибки нашёл и CppCat. Лишнее условие:
(style) Checking if unsigned variable ‘lenFile’ is less than zero. Аналогичное сообщение выдавал CppCat за исключением того, что cppcheck, не обнаружив файла windows.h не стал строить гипотезы относительно типов вроде WPARAM. Недочёт неориентированности исключительно на Windows всё же есть.
Думаю, если бы у меня были заголовочные файлы винды, можно указать путь к ним через параметр -I, тогда ошибок будет значительно больше.
(style) Array index ‘j’ is used before limits check. Несмотря на то что предупреждение низкоприоритетное, найденная ошибка представляет опасность выхода за границы массива:
Учитывая, что параметр startcol внешний, можно вылететь за границу массива, не говоря об индексе -1.
(style) Unsigned variable ‘i’ can’t be negative so it is unnecessary to test it. Условие в цикле всегда положительно => бесконечный цикл:
Этой ошибки можно было бы избежать при сборке проекта компилятором gcc и флагами -Wall -Wextra. Думаю, такая ошибка часто появляется при рефакторинге проекта по другой ошибке компилятора — несоответствие типов. Было int — стало unsigned, вот и результат.
Мелкие недочёты
(style) Unused variable: ent. Это предупреждение умеют выдавать и компиляторы, ничего интересного.
(warning) %d in format string (no. 2) requires ‘int’ but the argument type is ‘DWORD
Класс без конструктора:
(performance) Function parameter ‘range’ should be passed by reference. cppcheck рекомендует передавать параметр по ссылке, чтобы избежать копирования аргумента:
(warning) Ineffective call of function ’empty()’. Did you intend to call ‘clear()’ instead? Метод empty имеет смысл только внутри условия и не очищает строку. Это ложное срабатывание, cppcheck не подозревал, что автор сделает свой класс String:) Следует проверить логику именования методов.
(style) ‘class ByteArray’ does not have a copy constructor which is recommended since the class contains a pointer to allocated memory. cppcheck просто рекомендует создать в классе отсутствующий конструктор копирования на случай, если программист забыл его реализовать.
Вывод
Оба анализатора наковыряли приличное количество ошибок, причём многие из них уникальны для каждого анализатора. В целом, было бы неплохо иметь cppcheck под рукой всегда, так как он открыт, кроссплатформенный, реально находит ошибки и помогает улучшать стиль программирования. Использование cppcheck обычно не представляет проблем.
Из этого анализа можно сделать вывод, что инструменты хорошо друг друга дополняют. Для кого-то главным минусом cppcheck является отсутствие плагина под Visual Studio, поэтому авторы cppcheck любезно предлагают попробовать PVS-Studio. Несмотря на интерфейс командной строки, пользоваться cppcheck очень удобно. Не требуется ни компилятора, ни IDE, ни заголовочных файлов — это самый простой в использовании статический анализатор, который я только видел. Кроме того, я специально поставил в виртуальной машине сборку cppcheck для Windows — она имеет приятный графический интерфейс, устанавливается быстро и без проблем выполняет анализ:
Результат анализа можно экспортировать в XML и смотреть в браузере.
Обоим проектам стоит пожелать успехов в развитии — это действительно очень нужные программы. А в развитии cppcheck вы можете принять участие самостоятельно прямо сейчас: проверяйте свои проекты, пишите разработчику cppcheck о найденных или не найденных ошибках, сообщайте о багах, присылайте полезные патчи на гитхаб. Если ещё недавно cppcheck не мог найти ошибку if(malloc()), то сейчас просто сыпет сообщениями об утечках памяти — результат конкуренции налицо.
Данный анализ можно было бы сильно улучшить, если явно указать cppcheck, где искать заголовочные файлы, какие функции выделяют и освобождают память. Так как статья получилась большой, про то, как настроить cppcheck под конкретный проект, улучшить качество анализа и писать свои правила для cppcheck — в следующий раз.
P. S. Прошу простить одну глупость. Cppcheck имеет в настройках возможность анализировать код специально для Windows, из-за чего очень много интересных ошибок было пропущено. Нужно было анализировать npp с флагом —platform=win32A.
Источник