- Личный опыт разработки ПО
- Использование Cppcheck для статического анализа кода
- Цена вопроса
- Интеграция в систему сборки
- Вывод
- Информационный портал по безопасности
- Тонкости анализа исходного кода C/C++ с помощью cppcheck
- Загрузка и установка
- Настройка анализатора
- Пишем реализации функций самостоятельно
- Трюки с макросами и реализациями
- Анализ с учётом особенностей библиотек
- Пишем правила для cppcheck
Личный опыт разработки ПО
Использование Cppcheck для статического анализа кода
Что такое статический анализатор кода? Это программа, которая проверяет исходный код программы и пытается найти в нем ошибки. Иногда это у нее ловко получается. Диагностируются выходы за границы массивов, утечки памяти, использование неинициализированных переменных и прочие неприятные вещи. Часто минутный прогон программы позволяет сэкономить час работы в отладчике в поисках бага.
Цена вопроса
Есть программы которые стоят немалых денег (Coverity Prevent for C/C++, Klocwork), а есть абсолютно бесплатные (Cppcheck).
Платные я не тестировал, но судя по статье, Cppcheck им нисколько не уступает. Так на тесте приведенном в указанной статье она находит все ошибки. Вот результат работы:
Кроме затрат материальных, у инструментов есть определенный уровень вхождения, который некоторых может остановить. Я человек ленивый, поэтому мне особенно приятно, что у данного инструмента этот порог очень низкий.
Есть версия программы с графическим интерфейсом, где единственное усилие которое нужно сделать – выбрать пункт меню Check->Files для проверки одного файла, или Check->Directory для проверки всей директории, после чего просмотреть результаты работы.
Также есть консольная версия, на вход которой также достаточно подать имена проверяемых файлов.
Интеграция в систему сборки
Уже упомянутую консольную версию программы легко добавить в скрипт сборки проекта, чтобы автоматически проводить проверку. При этом Cppcheck может в случае нахождения ошибок вернуть определенный код. Используйте для этого ключ —error-exitcode=код_возврата.
Вывод
Cppcheck отличный инструмент, очень простой в использовании, бесплатный и довольно эффективный. Крайне рекомендую к использованию. Есть версии как для Windows, так и для Linux.
Информационный портал по безопасности
Тонкости анализа исходного кода C/C++ с помощью cppcheck
Автор: admin от 30-01-2014, 00:04, посмотрело: 2 195
В предыдущем посте были рассмотрены основные возможности статического анализатора с открытым исходным кодом cppcheck. Он показывает себя не с худшей стороны даже при базовых настройках, но сегодня речь пойдёт о том, как выжать из этого анализатора максимум полезного.
В этой статье будут рассмотрены возможности cppcheck по вылавливанию утечек памяти, полезные параметры для улучшения анализа, а также экспериментальная возможность по созданию собственных правил. Сегодня никаких сравнений анализаторов «кто лучше», статья полностью посвящена работе с cppcheck.
Загрузка и установка
Загрузить cppcheck можно с официального сайта, для Windows есть инсталлятор, а для Linux я рекомендую скачать исходный код с гита, так как собирается он очень легко и не имеет зависимостей. Сборка из исходного кода позволит включить экспериментальную возможность фичи, о которой будет рассказано в конце статьи.
В частности, для своей Linux-машины я форкнул на GitHub cppcheck и сделал git clone форка. Это позволит в будущем коммитить в репозиторий свои собственные конфиги и собственноручно написанные правила проверки, периодически синхронизируясь с основным репозиторием, что, согласитесь, очень удобно (не говоря о возможности отправлять патчи в проект).
Собираем для Linux
Сборка в Linux крайне проста: скачать, распаковать, перейти в каталог и выполнить make:
Собираем для Windows
Сборка в Windows тоже не должна представлять трудностей — там есть проект для VS. Открываем, собираем. Сам не пробовал, т. к. не имею такой потребности.
Cppcheck как плагин
По части плагинов для IDE — присутствуют плагины под Code::Blocks, CodeLite, Gedit и Eclipse, разумеется. Но этим дело не ограничивается, так как есть плагины для сборочных ферм Hudson, Jenkins, для систем управления версиями Tortoise SVN и Mercurial. Для Visual Studio плагина нет, но есть очень милая фраза на главной странице:
В cppcheck присутствует GUI, написанный на Qt. Он ещё более упрощает процесс анализа, но сильно вдаваться в его подробности не будем — суровые программисты не пользуются графическими интерфейсами:) Тем более что GUI полностью повторяет возможности командной строки (а в некоторых случаях — уступает) и разобраться в нём после знакомства с cppcheck труда не составит.
Стоит заметить, что cppcheck распространяется под лицензией GNU GPL. Это позволяет без труда взять исходный код этой программы, утащить в свой репозиторий Git и допиливать там под любые нужды, добавляя свои правила и библиотеки.
Настройка анализатора
Положительным моментом cppcheck является возможность тонкой настройки. Можно и уровень чувствительности настроить, и вывод сообщений форматировать, и фильтровать некоторые надоедливые сообщения.
Справедливости ради отмечу, что всю информацию, изложенную ниже, можно получить из документации или выполнив команду cppcheck —help, но я остановлюсь на наиболее важных нюансах поподробнее (и на русском языке:). Документация по cppcheck становится из года в год всё лучше, поэтому её полезно иногда перечитывать.
Если вам кажется, что я занимаюсь пересказом документации — переходите к чтению следующего раздела, т. к. здесь информация для тех, кто никогда ранее не использовал cppcheck и анализаторы в принципе.
Уровни ошибок
По умолчанию cppcheck старается по возможности выводить только стопроцентные ошибки. Это означает, что если анализатор нашёл подозрительный участок, но не уверен, что это баг, он не будет сообщать об этом. Данная особенность позволяет использовать cppcheck на сборочных станциях, чтобы из-за ложных срабатываний анализатора не сборка не стопорилась после каждого коммита.
Пример того, как это работает. Предположим, нужно проанализировать следующий код:
На первый взгляд, здесь ошибка: нет free. Однако если функция process_a является библиотечной функцией, невозможно с уверенностью сказать, что process_a где-то внутри не делает free для указателя a. Если внутри функции process_a переменная a действительно освобождается — это ложное срабатывание, которое будет мешать анализу. Поэтому cppcheck сначала попробует найти реализацию функции process_a в коде анализируемой программы, убедится, что она не вызывает free, и только в этом случае выдаст ошибку. Если реализация не найдена, cppcheck предполагает наиболее благоприятный сценарий из всех возможных: process_a освобождает память и поэтому не выдаст ошибки. Впрочем, cppcheck можно «научить» распознавать функции библиотек, тем самым увеличивая точность анализа — об этом будет рассказано ниже.
Здесь уже явно есть утечка памяти, так как g_exit — обёртка библиотеки GLib над стандартной функцией exit. Но cppcheck не знает, что g_exit прерывает выполнение программы, поэтому анализатор не сможет распознать здесь ошибки. Нужно как-то предоставить cppcheck информацию о том, что функция g_exit может прерывать программу, чтобы анализатор научился распознавать такие ошибки.
Из примеров видно, что cppcheck перестраховывается, причём планка адекватности высока. Большинство проверок cppcheck по умолчанию не включает. Среди них следующие категории проверок, каждая из которых может включаться/выключаться независимо:
- error — явные ошибки, которые анализатор считает критическими и обычно они приводят к багам (включено по умолчанию);
- warning — предупреждения, здесь даются сообщения о небезопасном коде;
- style — стилистические ошибки, сообщения появляются в случае неаккуратного кодирования (больше похоже на рекомендации);
- performance — проблемы производительности, здесь cppcheck предлагает варианты, как сделать код быстрее (но это не всегда даёт прирост производительности);
- portability — ошибки совместимости, обычно связано с различным поведением компиляторов или систем разной разрядности;
- information — информационные сообщения, возникающие в ходе проверки (не связаны с ошибками в коде);
- unusedFunction — попытка вычислить неиспользуемые функции (мёртвый код), не умеет работать в многопоточном режиме;
- missingInclude — проверка на недостающий #include (например, используем random, а подключить stdlib.h забыли).
Включаются проверки параметром —enable, список категорий проверок перечисляется через запятую. Например:
Таким образом я обычно включаю наиболее важные проверки. Существует ключевое слово all, которое включает все перечисленные проверки.
Примечание. Параметры -j и режим проверки unusedFunction несовместимы, поэтому -j выключит проверку unusedFunction, даже если она указана явно.
Пример команды, которая «гоняет» код по всем правилам:
И это ещё не всё. Если ваша программа безошибочна с точки зрения анализатора, попробуйте запустить cppcheck с параметром —inconclusive. Данный режим действительно включает все возможные проверки, даже ошибки с малой вероятностью, которые cppcheck пропускает по умолчанию.
Таким образом, самый подробный режим проверки:
Не забывайте о кроссплатформенности!
Cppcheck изначально создавался как инструмент, работающий с разными операционными системами и платформами. Поэтому нужно обязательно следить за тем, для какой платформы написана программа и какой режим проверки использует cppcheck. Платформа переключается параметром —platform.
- unix32 — все 32-разрядные *nix (включая Linux);
- unix64 — все 64-разрядные *nix;
- win32A — семейство 32-разрядных Windows с кодировкой ASCII;
- win32W — семейство 32-разрядных Windows с кодировкой UNICODE;
- win64 — семейство 64-разрядных Windows.
Если нужно проверить код, который был написан для Win32, используя Linux, нужно обязательно указать платформу:
Можно указать, по какому стандарту написан исходный код, что иногда позволяет уточнить проверку и выловить новые ошибки. Используйте параметр —std со следующими возможными вариантами:
- posix — для ОС, совместимых со стандартом POSIX (включая Linux);
- c89 — язык Си, стандарт 89-го года;
- c99 — язык Си, стандарт 99-го года;
- c11 — язык Си, стандарт 11-го года (по умолчанию для Си);
- c++03 — язык Си++, стандарт 03-го года;
- c++11 — язык Си++, стандарт 11-го года (по умолчанию для Си++).
Можно использовать сразу два стандарта:
Полезные аргументы в командной строке
Возможно уже бросается в глаза, что я везде использую параметры -q, -j. Зачем они нужны? Рассмотрим наиболее интересные.
-j — очень полезный параметр, позволяющий запускать проверку в многопоточном режиме. Использовать очень просто — в качестве параметра передаётся количество процессоров и проверка пойдёт веселее.
-q — тихий режим. По умолчанию cppcheck выдаёт информационные сообщения о ходе проверки (которых может быть очень много). Данный параметр полностью выключает информационные сообщения, остаются только сообщения об ошибках.
-f или —force — включить перебор всех вариантов директив ifdef (по умолчанию cppcheck проверяет дюжину вариантов). Что это такое — потом будет рассмотрено отдельно.
-v — режим отладки — cppcheck выдаёт внутреннюю информацию о ходе проверки.
—xml — выводить результат проверки в формате XML.
—template=gcc — выводить ошибки в формате предупреждений компилятора gcc (удобно для интеграции с IDE, поддерживающей такой компилятор).
—suppress — режим подавления ошибок с указанными идентификаторами (нужен повторный анализ).
-h — выдаёт справку по всем параметрам на чистейшем английском языке.
Фильтрация сообщений и исключения
Как любой уважающий себя анализатор cppcheck позволяет при проверке гибко настроить отображение ошибок. Наиболее полезной является возможно отключить определённое предупреждение в определённом файле (и возможно в определённой строке). Отключение сообщений реализовано параметром —suppress, где нужно указать исключение, либо —suppress-file с указанием текстового файла, где содержится список исключений. Чтобы передать несколько исключений в командной строке можно указать несколько параметров —suppress подряд, но лучше для таких целей завести файл с исключениями.
Обязательный параметр id (идентификатор ошибки), следом через двоеточие можно опционально указать имя файла, после имени файла, также опционально, можно указать номер строки.
Например, очень часто всплывает такое предупреждение:
Эта ошибка присуща большинству проектов и чтобы она не отбила желание заняться рефакторингом, на первых порах я рекомендую её выключать. Это можно сделать следующим способом:
Как видно из этого примера, cppcheck использует понятные человеку идентификаторы ошибок, а не номера, что гораздо проще запомнить.
Узнать список всех возможных ошибок поможет параметр —errorlist, который выдаёт полный список в формате XML. Но я могу посоветовать другой метод определения «неугодных» сообщений. Для этого потребуется изменить формат вывода сообщений с помощью параметра —template:
Мой шаблон теперь в первую очередь выводит идентификатор сообщения, после чего файл с указанием номера строки и, наконец, само сообщение. Узнать идентификатор мешающего сообщения не составит труда.
И наконец, рецепт автоматического создания файла для использования в параметре —suppress-file. В командной строке это делается в два счёта:
Теперь полученный файл можно подать на вход cppcheck и в выводе не окажется ни одной ошибки. Это полезно, когда анализ завершён и все срабатывания анализатора — ложные.
Cppcheck также позволяет писать исключения внутри комментариев, но приведённый выше пример позволит избежать захламления исходного кода, собрав все исключения в одном месте, а не раскидывать их по всему проекту.
Разбираемся с include и define
Cppcheck понимает некоторые параметры компиляторов, что позволяет уточнить, по которому пути идёт проверка. Так как cppcheck не пользуется услугами компилятора, он имеет свой собственный препроцессор. Данный препроцессор не требует ни наличия всех заголовочных файлов, ни корректности исходного кода. Если где-то встречается неизвестный include — cppcheck его просто не обрабатывает.
Неизвестность может сыграть злую шутку. Обычной практикой в библиотеке GLib является проверка аргументов:
Всё хорошо за исключением того, что g_return* — это макросы, которые прерывают выполнение функции в случает ошибки. Таким образом, если первый аргумент функции f окажется корректным, а второй — нет, возникает утечка памяти. Cppcheck об этом не догадывается, так как считает g_return_if_fail по умолчанию «хорошей функцией», а не макросом.
Поведение cppcheck можно изменить, если подключить необходимые заголовочные файлы, чтобы препроцессор сделал всю необходимую работу: он найдёт реализацию макроса g_return_if_fail, раскроет его, а cppcheck увидит условный return без free, что является паттерном утечки памяти.
Для того чтобы заставить препроцессор работать как следует, нужно указать пути, где искать заголовочные файлы. За это отвечает параметр -I, который аналогичен одноименному параметру компилятора gcc. Для GLib и Linux это вполне предсказуемый путь:
Интересная особенность (которая сильно увеличивает время анализа) — перебор ifdef-вариантов. Если в программе есть один ifdef — cppcheck сделает два варианта препроцессинга и просканирует оба варианта исходного кода. Чем больше в исходном коде ifdef-ветвлений, тем больше вариантов нужно перебирать. Управлять этим поведением можно параметрами -D и -U. Параметр -DA означает, что макрос A определён. Параметр -UB означает, что макрос B не определён.
По умолчанию проверяется только дюжина конфигураций. Изменить это число можно параметром —max-configs. Чтобы проверять только одну конфигурацию, можно задать проверку одной конфигурации. Параметр —force проверит все конфигурации (очень медленно).
Cppcheck не отличает макросов из заголовочных файлов от макросов, определённых в исходном коде. Если анализ усилить препроцессингом всех #include, указав каталог с заголовочными файлами — приготовьтесь к очень длительному анализу — cppcheck будет шелушить все макросы из всех заголовочных файлов, до которых дотянулся препроцессор.
Использование препроцессора — самый простой способ улучшить проверку, но очевидно, что время этой проверки сильно увеличивается, так как препроцессинг раздувает исходный код до сотен мегабайт. На примере GLib будет показан более эффективный способ ловить утечки памяти, используя другую возможность cppcheck предоставления информации о сторонних библиотеках.
Пишем реализации функций самостоятельно
Стоит заметить, что параметр -I лишь сообщает cppcheck, где искать заголовочные файлы и подключает их только в случае, если в исходном файле есть соответствующий #include. Можно использовать чуть более затратный альтернативный вариант: реализовать наиболее частые макросы и функции вручную и подключить их ко всем файлам проекта. Придётся немного поработать над созданием такого файла, но анализ пойдёт значительно быстрее и точнее. Более того, можно использовать некоторые интересные трюки с макросами.
Подключение файла с реализацией функций реализуется параметром —append. Указанный файл автоматически вставляется в конец каждого файла проекта.
Подключение файла с макроопределениями реализуется параметром —include. Указанный файл автоматически вставляется в начало каждого файла проекта.
Например, программа использует библиотеку GLib и нужно сообщить cppcheck, что g_return_if_fail — это макрос.
Попробуем проанализировать такой код:
Создаём файл gtk.h следующего содержания:
Так как это макрос, его нужно включать в начало:
Хм. Снова ничего? Если присмотреться к коду в примере, можно заметить функцию g_strdup, о которой cppcheck пока ничего не знает. Попробуем написать простейшую реализацию (файл gtk.c):
Обратите внимание, что это функция, а не макрос.
Анализируем. Файл с реализацией функции вставляется параметром —append;
Готово! Теперь cppcheck научился обнаруживать новые утечки памяти. Умный анализатор залез внутрь кода функции g_strdup, сделал трассировку возвращаемого значения и обнаружил там strdup, пометив указатели как указатели на память, которую нужно освободить.
Данный пример взят не с потолка: это одна из самых частых ошибок в GLib-программах, которые я нахожу постоянно.
Трюки с макросами и реализациями
Механизм подключения файлов к проекту — очень гибкий инструмент. С его помощью можно проделывать такие вещи как исключение функций с известным поведением, подмена аргументов функций, определение недостающих typedef-ов.
Во многих случаях cppcheck не нуждается в том, чтобы присутствовал какой-либо заголовочный файл или был определён тип данных/класс. Поэтому писать макросы имеет смысл только в том случае, если они улучшают проверку.
Нестандартное выделение памяти
Предположим, есть функция my_alloc, которая выделяет память внутри своих аргументов:
Следующий пример не будет распознан как утечка памяти:
Добавим теперь реализацию:
и проверка выдаст следующее:
Исключаем функцию из проверки
Если есть какая-то функция, которую мы не хотим проверять, её легко спрятать:
Выделение памяти с флагом ошибки
Некоторые функции, выделяя память, могут сообщать об ошибке через свои аргументы. Например, такой код корректен:
Однако если в базе cppcheck my_alloc значится как функция выделения памяти, результатом проверки будет ошибка. Чтобы её избежать, можно использовать следующую хитрость:
Анализ с учётом особенностей библиотек
Большинство приложений используют какие-либо библиотеки, в частности, glibc. Анализатору порой нужна некоторая информация о библиотечных функциях. Например, malloc выделяет память, а free — освобождает. Если есть malloc, но нет free, зачастую это означает утечку памяти. Данные функции входят в glibc и являются частью стандарта, поэтому анализатор (да и компилятор) о них знать просто обязан.
Что же касается всего остального, то в идеале для каждой используемой библиотеки анализатор должен иметь некоторое описание её особенностей, чтобы уметь обнаруживать ошибки, допущенные не только при использовании glibc.
Почему это так важно? Если встречается неизвестная функция — cppcheck предполагает, что она находится где-то в сторонней библиотеке и строит относительно неё самые благоприятные прогнозы (она освобождает за собой память, не прерывает выполнение программы и т. п.). Если где-то был обнаружен паттерн утечки памяти или неинициализированной переменной, но внутри находится функция неизвестного назначения, cppcheck заблокирует сообщение об ошибке.
Рассмотрим простой пример. В ОС Linux многие графические приложения используют библиотеку GLib. Данная библиотека практически дублирует glibc, в том числе она имеет свои реализации malloc/free. Обычно код, использующий GLib, выглядит так:
То, что после g_strdup и g_malloc память нужно освобождать, человек догадается интуитивно, даже не имея ранее опыта работы с GLib. Чего не скажешь об анализаторе: реализация функции g_malloc спрятана внутри двоичного кода библиотеки — кто её знает, что она там делает?
Из такой ситуации один выход: вести базу данных наиболее популярных библиотек и постепенно пополнять её, фиксируя особенности функций каждой библиотеки. Анализатор, пользуясь базой данных, сможет без труда в дальнейшем проверять любой код, использующий библиотеку, находить в нём ошибки.
Самое приятное в cppcheck то, что он имеет такую пополняемую базу данных. Это означает, что сообщество может улучшать cppcheck просто добавляя всё новую информацию о стандартных библиотеках, которую cppcheck потом будет использовать при анализе.
Cppcheck не загружает автоматически необходимые библиотеки, это нужно делать вручную. Библиотеки указываются параметром —library, можно указывать несколько библиотек, разделяя их запятыми. cppcheck сначала ищет файл с именем библиотека.cfg в текущем каталоге (удобно так хранить библиотеку для своего проекта), потом пытается найти её в своей базе библиотек. Если библиотеки нигде не нашлось, cppcheck выдаст ошибку.
Пример анализа проекта с использованием библиотеки gtk:
Можно явно указать, в каком файле лежит библиотека (надо сказать, не посмотрев в исходники, я бы не догадался, что так можно делать):
На сегодняшний день cppcheck поддерживает совсем немного библиотек:
- gtk (на самом деле, glib, gtk там не поддерживается)
- Qt
- windows (всякие scanf_s. )
- posix
- glibc (стандартная библиотека перестаёт быть hard-coded и постепенно вытесняется в базу)
Список явно небогат. Более того, от содержимого этой базы хочется пустить скупую слезу, вот, например, база POSIX:
Довольно небольшой улов.
27 новых ошибок — уже кое-что!
Действительно ли это баги или куча ложных срабатываний?
Утечка памяти в случае проверки ошибки — наиболее распространённый тип утечек памяти:
Несоответствие конструктора/деструктора. На самом деле это утечка памяти, так как внутренняя структура массива не освобождается. Код ниже будет прекрасно работать без ошибок, но будет течь в элементах массива:
Указатель возвращается после освобождения:
Это спорное предупреждение, так как g_object_unref считает ссылки и будет ли удалён объект неизвестно, но к ошибке стоит присмотреться.
Ложные срабатывания. Были и такие — некоторые функции gtk позволяют отчуждать объект, который уничтожится автоматически вместе с родителем, если их не исключить — cppcheck будет ругаться.
Здесь используется другая функция освобождения, что всё равно корректно, так как g_object просто считает ссылки:
От этого ложного срабатывания сложно избавиться, так как g_new часто используется чтобы выделить массив строк:
Тут cppcheck запутался в условиях. Баг в cppcheck:
Но прелесть этих ложных срабатываний в том, что их легко исправить, пропатчив XML-файл с библиотекой. Таким образом, удалось сократить количество ошибок до 14. Со временем интроспекция в GLib будет улучшена и можно будет в автоматическом режиме собрать информацию о библиотеке.
Пишем правила для cppcheck
Напоследок — самое вкусное. Здесь пойдёт речь о том, как реализовать свои собственные проверки для cppcheck. Наверняка, вы уже находили у себя какой-то баг и хотели бы проверить весь проект на наличие аналогичных ошибок. Другая ситуация — вы тимлид и программисты в вашей команде регулярно лепят типовые ошибки, которые хотелось бы пресекать в автоматическом режиме. Держать свой собственный код «в узде» полезно, чтобы разрабатывать эффективные и безопасные программы. Нашли ошибку — написали к ней не только регрессионный тест, но и правило анализатора.
Что под капотом?
Для начала пара слов о том, как работает cppcheck. Перед непосредственно анализом cppcheck прогоняет препроцессор, аналогично компилятору, после чего следует этап упрощения исходного кода. То есть: убираются все лишние отступы и пробелы, каждая лексическая конструкция языка разделяется ровно одним пробелом. Все константы, которые могут быть упрощены во время препроцессинга, — вычисляются. Везде расставляются блоки <>, даже если они опущены. Если присутствует объявление или присвоение переменной внутри блока if/for/while — оно будет вынесено снаружи этого блока.
Так как стиль кодирования у всех разный, cppcheck стремится сделать так, чтобы код был приведён к своего рода «нормальной форме». Например, один программист пишет цикл так:
Cppcheck приведёт это всё к виду:
Не очень читабельно, зато легко анализировать. Все лексические конструкции аккуратно разделены пробелами и не имеют ничего лишнего.
Cppcheck использует несколько уровней упрощения: простой, нормальный и исходный. Например, на «простом» уровне оператор sizeof раскрыт как число, а обычном уровне он остаётся оператором. Это иногда полезно для поиска ошибок, связанных с конкретным оператором. Исходный уровень — это код в первозданном виде, над которым ещё не поработал препроцессор и он только приведён к нормальной форме.
Таким образом, cppcheck строит на основе исходного кода некоторую модель (класс Tokenizer), где все токены просто разделены пробелом, позволяя анализирующим модулям легко пользоваться этими токенами. Анализирующий модуль может в свою очередь строить свою модель, если ему это необходимо, перемещаться по токенам, определять тип токена и т. п. На данный момент есть базовая модель (разбиение на токены, она используется практически везде), модель ValueFlow — для проверки утечек памяти и экспериментальная модуль AST (синтаксическое дерево). Разработчики правил могут использовать любую из этих моделей.
Чтобы не ломать голову, как cppcheck оптимизирует те или иные конструкции, можно воспользоваться отладочным режимом, в котором cppcheck выдаст на экран упрощённую версию кода:
Правила на основе регулярных выражений
Cppcheck позволяет расширять свои возможности при помощи регулярных выражений. Схема работы такова: производится упрощение исходного кода, он склеивается в одну строку, после чего к полученной строке применяется регулярное выражение. Если совпадение найдено — cppcheck выдаст настоящее предупреждение и сообщит строку кода, в которой сработало правило.
Перед тем как пользоваться данной возможностью, нужно перекомпилировать cppcheck (вот почему я рекомендую загрузить версию исходников из гита), включив экспериментальную поддержку регулярных выражений. Для этого потребуется библиотека pcre. Компилируется всё так же просто:
Теперь у cppcheck появится два новых параметра: —rule — можно задать регулярное выражение прямо в командной строке и —rule-file — XML-база с вашими собственными правилами проверки.
Наглядно познакомиться с новой возможностью можно следующим образом. Тестовый пример:
Далее проверка. Составим регулярное выражение, которое подминает под себя всё подряд:
Отлично! Теперь понятно, какой вид является «упрощённым».
Теперь напишем правило, которое позволяет искать ненужные проверки переменной перед освобождением:
Паттерн работает. Осталось записать его в базу данных. Создадим файл rules.xml, окончательно оформив регулярное выражение:
Теперь можно проверить какой-нибудь исходник, используя этот файл как базу правил:
Формат XML-файла вполне очевиден. Иногда может потребоваться другая версия упрощённого кода, например, raw — исходный код без упрощений. Тогда достаточно в правило добавить тег:
Внутри тега tokenlist можно использовать:
- raw — код с минимальным упрощением
- normal — режим по умолчанию
- simple — максимально упрощённый код
- define — для проверки директив препроцессора, cppcheck не исключает из исходника макросы
Если удалить тег summary — можно увидеть, какая строка попадает под регулярное выражение — такой режим дебаггинга.
Несколько примеров правил:
В Си хорошим тоном считается объявление функции без аргументов с void: