программы
511
0
27 сентября 2024
программы
CodeChecker: анализируем большой проект на С++ быстро, эффективно и бесплатно
программы
программы
511
0
27 сентября 2024

CodeChecker: анализируем большой проект на С++ быстро, эффективно и бесплатно

Изображение создано
с помощью нейросети
Изображение создано с помощью нейросети
511
0
27 сентября 2024

Представьте, что у вас в команде есть большой проект на более 100 000 строк, написан на C++ (и частично на С). Код переписывался много раз, а за самим проектом на правах легаси особо никто не следил: работает — не трогай. И перед вами стоит задача: привести код в порядок и отловить ошибки, которые пропускает компилятор.

Одно из очевидных решений — использовать статический анализатор. Этим путем пошел и Давид Чиковани: выбрал известное решение и прогнал код через анализатор. Но результат не удовлетворил. Тогда инженер решил поэкспериментировать с другими вариантами статических анализаторов, сделав ставку на open source. Поиски привели к инфраструктуре CodeCheсker, которая предоставляет удобный интерфейс запуска и настройки статических анализаторов через аргументы командной строки. С помощью инструмента удалось достичь результатов, которые значительно превысили значения, полученные на коммерческом решении. В статье Давид рассказал, как с ним работать и почему его точно стоит попробовать на большом проекте.

Из статьи вы узнаете
  • Чем хорош CodeChecker и как его установить
  • Какие анализаторы входят в инфраструктуру и все ли из них полезны
  • Как запустить анализаторы и спарсить результаты
  • Каких результатов добилась команда на большом проекте

CodeCheсker: что за инструмент и чем он хорош

CodeCheсker предоставляет удобный интерфейс запуска и конфигурации статических анализаторов на Linux или macOS. С помощью него можно запускать анализаторы кода на C/C++ — Clang-Tidy, Clang Static Analyzer, Cppcheck и GCC Static Analyzer — в любой комбинации. А еще — включать и выключать различные проверки этих анализаторов. Результаты проверок можно изучать в удобном веб-интерфейсе.

Чем он нам понравился:

  • С ним просто запускать анализаторы. Статических анализаторов немало, но иногда их неудобно использовать: настраивать конфигурационные файлы долго, нет простого способа запуска чекера и парсинга результатов. CodeChecker исключает эти проблемы, предоставляя удобный интерфейс запуска и конфигурации через аргументы командной строки.
  • Его просто установить. CodeChecker — это python-пакет, ставится просто и не требует никаких настроек. Можно использовать «из коробки».

Как установить CodeChecker

Поскольку CodeChecker — это python-пакет, устанавливается он самым обычным pip3 (сам пакет доступен на pypi):

pip3 install codechecker

Можно установить пакет в виртуальном окружении или воспользоваться pipx, который самостоятельно создаст venv:

pipx install CodeChecker

Еще один вариант — установка через пакетный менеджер Snap:

sudo snap install codechecker --classic

Обратите внимание, что название инструмента в консоли в таком случае нужно писать именно прописными буквами.

Еще больше способов установки (в основном напрямую) описаны в документации CodeChecker. Также его можно запустить в Docker-контейнере или в редакторе Visual Studio Code.

Запускаем анализаторы в CodeChecker

Существует несколько способов запуска анализаторов с помощью CodeChecker, но мы выберем самый простой — через передачу compilation database (файла compile_commands.json). По сути, это просто данные о структуре проекта в одном файле. Их можно передавать, например, своей IDE, если она не видит зависимостей и из-за этого работает некорректно.

Сгенерировать этот файл можно разными способами. Например, если вы используете CMake, то можно просто прописать cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON. в рабочей директории. Но у нас Linux, поэтому воспользуемся, возможно, более сложным способом — утилитой Bear, которая генерирует базу данных компиляции для инструментов clang. Чтобы сгенерить compile_commands.json, заходим в рабочую директорию и пишем: bear -- <ваша-команда-для-билда-проекта>, в нашем случае bear -- make. Готово, в этой же директории получаем compile_commands.json.

Для запуска анализаторов будем использовать команду analyze. В общей команде нужно указать следующее:

  • путь до файла compile_commands.json,
  • названия анализаторов, которые хотим запустить,
  • настройки чекеров,
  • папку, куда сгенерировать результат.

Файл compile_commands.json у нас уже есть, разберемся c анализаторами.

Анализаторы

В CodeChecker доступны четыре анализатора — подробнее о каждом я расскажу позже:

  • Clang-Tidy — ставится дефолтным пакетом или вместе с Сlang,
  • Clang Static Analyzer — ставится вместе с Сlang,
  • Cppcheck — ставится дефолтным пакетом,
  • GCC Static Analyzer — ставится вместе с GNU Compiler Collection.
GCC нужен от версии 13.0.0. Если в PATH указана другая версия, то можно выставить в переменную окружения CC_ANALYZER_BIN путь до бинарного файла с GCC нужной версии: CC_ANALYZER_BIN='gcc:<путь-до-бинарного-файла-gcc>'

По дефолту CodeChecker запустит все доступные анализаторы. Прописать конкретный набор анализаторов можно во флаг --analyzers. Например, --analyzers gcc clangsa.

Чекеры

Чекеры — самая важная часть команды запуска, ведь они устанавливают, на что именно будет смотреть анализатор и какие ошибки подмечать.

Есть три способа включить или выключить проверку:

  • через конкретный чекер,
  • через группу чекеров, которые просто объединяют чекеры в одну сущность,
  • через соответствующий профиль.
Профили — это наборы групп чекеров. Полезны, потому что групп много и прописывать их всех трудозатратно. Изначально доступны три профиля: default (стандартный набор групп), sensitive (увеличенный набор групп), extreme (почти все группы включены). Но можно собирать и свои профили. Однако важно помнить, что с ростом количества проверок растет и количество false-positive срабатываний.

Для включения/выключения чекеров используют флаги --enable и --disable и их комбинации. Так, с помощью --enable-all и --disable-all можно включить или выключить все чекеры (за исключением тех, что включаются вручную). Эти флаги применяются последовательно, так что их можно комбинировать для нужных настроек.

Например, после --disable-all --enable alpha.unix.PthreadLock включится только чекер PthreadLock на многопоточность.

Итоговая команда запуска может выглядеть так:

CodeChecker analyze ./compile_commands.json --analyzers clangsa --enable=alpha --enable=sensitive --output ./reports

Здесь мы запускаем Clang Static Analyzer в режиме sensitive с alpha-чекерами. Результаты анализа можно будет посмотреть в папке reports.

Больше про запуски анализаторов можно почитать здесь. Перед тем как парсить результат, давайте ближе познакомимся с самими анализаторами.

Доступные анализаторы и как их лучше использовать

Clang Static Analyzer

Из четырех анализаторов, входящих в CodeChecker, этот дал нам больше всего полезного «выхлопа», причем разностороннего. Тут и проверки использования STL (например, использование итератора, вышедшего за конец контейнера), и обнаружение дедлоков при работе с мьютексами, и проверки на NULL dereference. Чтобы посмотреть полезные чекеры и группы чекеров, доступные для этого анализатора, используйте команду:

clang -cc1 -analyzer-checker-help

Анализатор прорабатывает код довольно быстро, даже с полным листом чекеров, — на наш проект с немалым числом строк кода он тратил 2−3 минуты. А еще он яснее всего подсказывает, где именно в коде он нашел ошибку.

Веб-интерфейс подсвечивает, как анализатор пришел к ошибку
Так веб-интерфейс подсвечивает, как именно анализатор пришел к ошибке

Кроме того, у анализатора есть уникальная проверка cross-translation unit, которая включается с помощью флага --ctu. Этот режим позволяет ему расширить область видимости анализа на соседние единицы трансляции. Подробнее про конфигурирование этого анализатора можно прочитать здесь.

В итоге большинство багов и опечаток нам помог исправить именно Clang SA.

Clang-Tidy

При использовании Clang-Tidy нужно быть готовым к километровому списку ошибок даже на дефолтных настройках чекеров. Анализатор ругается буквально на каждый чих, будь то неявный каст у целочисленных типов или неиспользованное значение snprintf (). Это не делает его бесполезным, но надо быть готовым, что результаты придется разгребать и выцеплять «пинцетом» действительно полезную информацию.

Еще Clang-Tidy — самый долгий из анализаторов: с дефолтными чекерами проверка может занять 30 минут. А с включением всех чекеров анализ кода может зависнуть на долгое время. Например, в наших тестах он не «проснулся» даже за час.

Но если есть время, можно подключить этот анализатор. От него мы получили несколько полезных рекомендаций — в основном из разряда, где стоит «причесать» код. Иногда указывал на ошибки, но реже. Так, он обнаружил пропуски скобок в логических операциях и пропущенный break у switch.

Чекеры для Clang-Tidy можно увидеть с помощью команды:

clang-tidy --list-checks

CppCheck

На бумаге CppCheck считают сильным анализатором, который минимизирует false-positive срабатывания. На деле он указывает на синтаксические ошибки там, где их нет, — например, при использовании varargs (variable arguments list). И в целом, дает довольно мало полезного знания о коде.

При анализе кода нашего проекта мы отметили буквально два места, где он действительно нашел ошибку в синтаксе, связанную с форматной строкой. Все это утяжеляется тем, что работает анализатор не очень быстро. В общем, рекомендую включать, только если есть свободное время — так, на всякий случай.

GCC Static Analyzer

Этот анализатор для нашего проекта не подошел совсем, потому что GCC SА не заточен под C++, а большая часть проекта написана именно на «плюсах». В теории код на C этот анализатор должен анализировать хорошо и выдавать что-то полезное. Если это ваш случай, проверьте ради эксперимента. Но в плюсах, увы, он ничего не замечает, причем не имеет значения, путь до какого бинарника мы ему передаем.

Бонус: чем можно заменять «слабые» статические анализаторы

Хочется упомянуть еще один статический анализатор, про который мы узнали из описания CodeChecker — Facebook* Infer (*компания Facebook принадлежит организации Meta, признанной экстремистской на территории РФ). Он не запускается из интерфейса CodeChecker, но мы решили опробовать его отдельно. В отличие от предыдущих анализаторов, он может проверять код не только на C/C++, но и на других языках — например, Java и Objective-C.

Для запуска нам потребуется все тот же compile_commands.json. Просто прописываем:

infer --compilation-database ./compile_commands.json

И вот, красивый прогресс-бар на время анализа скрашивает наше время ожидания.

Запуск статического анализатора Facebook Infer

Этот анализатор нам понравился. Facebook Infer показал много мест с неинициализированными и неиспользованными переменными, ситуациями гонки и лишними копированиями значений. Работает быстро, примерно как ClangSA (2−3 минуты), хоть настроек у него и мало.

Результаты можно посмотреть в .txt-файлике, сгенеренном анализаторм. А если хочется более приятного интерфейса, можно спарсить результаты Facebook Infer с помощью CodeChecker, Как парсить результаты — расскажу далее.

Парсинг результатов

После успешного анализа результаты необходимо обработать. В CodeChecker это легко сделать с помощью команды parse:

CodeChecker parse --export html --output ./reports_html ./reports

Во флаге --export указываем формат, в котором хотим получить результат, в нашем случае это html. Также прописываем папку, куда сгенерить результаты обработки, и, конечно, указываем путь до папки, где мы хотим увидеть результаты анализа.

Теперь можно посмотреть результаты и статистику по анализу на красивой html-странице.

Таблица соответствия найденных ошибок их количеству
А это statistics.html — таблица соответствия найденных ошибок их количеству
Список конкретных ошибок с описанием и ссылкой на файл, где можно посмотреть, что именно происходит в коде
Это index.html — список конкретных ошибок с описанием и ссылкой на файл, где можно посмотреть, что именно происходит в коде
  • В колонке File можно нажать на соответствующую запись — откроется вкладка с кодом. В ней будет поэтапно показано, что не понравилось анализатору и приводит к ошибке.
  • В колонке Severity указывается степень «тяжести» той или иной ошибки.
  • В колонке Message описана проблема, иногда с указанием конкретных переменных/структур.

Результаты анализа можно отсортировать по этим колонкам — например, если хотим группами рассматривать отчеты одной и той же проверки.

Перед повторным запуском советуем удалить или переместить папку со старым результатом в другую директорию. На время анализа это не повлияет, зато на выходе будет актуальное состояние кода. Если этого не сделать, CodeChecker будет добавлять результаты в старый файл и не уберет уже исправленные ошибки.

Вместо заключения

Статические анализаторы в составе CodeChecker помогли нам найти и исправить более 300 ошибок (без учета false-positive результатов). Для сравнения, коммерческое решение подсветило около 120 проблем, 99% из которых false-positive. А интерфейс инструмента в разы облегчил их использование.

Если хотите максимальной пользы для проекта в короткие сроки, рекомендую запустить ClangSA на sensitive/extreme настройках, а потом дополнительно прогнать код через Facebook Infer. Если есть время и хочется немного «причесать» код, можно подключить и остальные анализаторы — Clang-Tidy, CppCheck и даже GCC SA. Главное, что все это не требует ни покупки лицензий, ни долгих настроек.

Наверх
Будь первым, кто оставит комментарий