Здравствуйте, уважаемые читатели.
Меня зовут Стас, я инженер команды DevOps Tooling в компании Align Technology.
В этой статье я попробую коротко рассказать про то, как в нашей компании внедрили статический анализ кода на основе PVS-Studio.
Введение
Приблизительно год назад мы задумались о том, чтобы внедрить в нашей компании статический анализ.
Мы и раньше использовали для этого различные средства, в том числе и для C/C++ проектов. Потому было интересно попробовать новый инструмент для известной задачи.
Инструмент, более совершенный, чем встроенный средства VS, cpp-check, интегрированный в Sonar.
Как такое всегда происходит, вначале возникла инициативная группа разработчиков (из одного разработчика, а его менеджер идею в целом поддержал).
Он пришел к нашей команде и сказали, что хочет попробовать.
Мы связались с разработчиками PVS студии, они предложили воспользоваться пробной бесплатной версией.
Разработчик проверил код своего проекта и устроил для коллег-разработчиков презентацию, показав, какие ошибки помогает выявить PVS-Studio.
Причем на презентации было показано, что и в относительно новом коде встречаются серьезные ошибки.
Презентация была очень успешной, разработчики смеялись над кодом коллег и над своим кодом.
Ряды этузиастов пополнились, причем, что ценно, в том числе и менеджерами.
После этого менеджеры (совместо с этузиастами-разработчиками) посчитали ROI для внедрения этого продукта.
Как они ROI считали — на этот счет, возможно, будет в дальнейшем отдельная статья.
В общем, купили мы PVS-Studio.
И перед нашей командой поставилась задача внедрить статический анализ и исправление ошибок в процесс разработки продукта.
Вот как мы эту задачу решили:
Процесс
Процесс устранения выявленных предупреждений двигается рывками, в основном за счет энтузиастов.
Время от времени, когда кто-то из инициативных разработчиков находит время, он устраняет какой-то один вид предупреждений.
Это удобнее, потому что как правило, предупреждения легко устранимы без изменения логики кода, и потому подозрительные
участки кода могут быть легко отрефакторены. Кроме того, многие предупреждения устраняются при помощи одного или нескольких
более или менее стандартных приемов, так что устранение одного типа предупреждений во всем проекте продуктивнее,
чем, например, устранение всего разнообразия предупреждений в отдельном файле.
Техническая реализация
Для облегчения жизни разработчикам-этузиастам нами была произведена интеграция PVS-анализа в процесс CI.
CI-сервером у нас служит Atlassian Bamboo, потому я буду использовать его термины при описании.
Нами были созданы специальные сборочные планы, автоматически запускаемые по расписанию. По одному плану на каждый проект.
Этот план запускает через командную строку standalone-проверку головного проекта для актуальной версии кода.
Анализ продолжается довольно долго (2-4 часа), потому такую проверку мы производим не чаще одного раза в день.
После проверки PVS-анализатор генерирует plog-файл — по сути xml с перечнем найденных предупреждений.
После этот файл немного обрабатывается (заменяются абсолютные пути на относительные), а потом, при помощи xslt-преобразования,
превращается в xUnit-отчет, который умеет понимать и визуализовывать Bamboo.
Мы превращаем каждый тип предупреждения в один юнит-тест, с перечнем всех файлов, в которых это предупреждение найдено.
После первого запуска все предупреждения отправляются в карантин (то есть билды будут считаться успешными в случае проваленных тестов).
В тот момент, когда разработчик фиксит какой-то конкретный тип предупреждения, он выпускает соответствующий тест из карантина.
Таким образом, если вдруг предупреждение, которое мы полагаем исправленным во всем проекте, вдруг появляется снова, мы получаем свалившийся билд
и принимаем меры.
Выглядит свежеобнаруженное предупреждение так:
Стандартно xUnit содержит трехуровневую иерархию: TestSuite как объединение TestCase-ов. В TestCase содержится от 0 (если тест прошел) до множества Failures. Мы транслируем результаты проверки следующим образом.
-
Testsuite — это анализаторы в PVS-студии, например “Problems related to code analyzer”. Этот уровень не используется на практике для каких-то целей, можно было бы свалить все TestCase в один TestSuite. Но мы предполагаем, что для некоторых проектов часть проверок не будет рассматриваться, и этот уровень разделения пригодится.
-
Testcase — это конкретные предупреждения, например “Implicit type conversion N argument of function 'foo' to 32-bit type”. Собственно, это и есть один тест с точки зрения Bamboo. TestCase для любого предупреждения есть всегда, независимо, возникает это предупреждение в нашем коде или нет. Когда предупреждения в нашем коде не встречается — тест становится зеленым.
- Failure — это файл и номер строки, где конкретное предупреждение обнаружено. Можно посмотреть, где конкретно встречается и стоит ли браться за исправление этого типа вечером в субботу. :-)
Сложности
При интеграции PVS-анализатора в наш CI возникло несколько трудностей.
Во-первых, трудности с лицензией. Нами была куплена site-лицензия на 30 пользователей. В соответствии с лицензионной политикой, одна установка на конкретный экземпляр ОС “съедает” одну лицензию.
Поскольку сборок и проектов у нас много, то и сборочных агентов тоже прилично — мы подходим уже к лимиту в 100 агентов (сейчас их ~90). Конечно, не все из них используются для сборки C++-кода, однако и таких машин у нас достаточно. Поставить на них на всех PVS-студию мы не могли, пришлось сделать несколько выделенных под это агентов. В результате, если в момент запуска анализа эти выделенные агенты заняты, анализ ждет их освобождения и в результате занимает их на продолжительное время в первой половине рабочего дня. Из-за этого у нас health-check билды, стартуемые после каждого коммита, испытывают нехватку агентов. Кроме того, затрудняется автоматическое развертывание агентов (приходится все время пересчитывать используемые лицензии, что непросто в отсутствие лицензионного сервера).
Во-вторых, трудности с получением справочника предупреждений. Единственное место, откуда их можно получить — это сайт разработчиков PVS-студии, раздел технической документации. Однако сделать автоматический парсер этой страницы нам пока не удалось — верстка страницы слишком неудобная для правильного разбора и каждый раз немного меняется. Да и есть некоторая сложность автоматического централизованного обновления версий PVS-студии на всех агентах и справочника (это конечно можно решить, но пока не дошли руки). Так что обновляем пока руками.
Результат
Вот несколько примеров найденных ошибок, то есть когда процесс сработал в новом коде.
-
V544. It is odd that the value 'X' of HRESULT type is compared with 'Y'
-
Minor issue: в этом месте проблема стиля
static HRESULT findSomething(const X& x); // later in the code if (findSomething(x) == -1)
-
-
V501: There are identical sub-expressions to the left and to the right of the 'foo' operator
-
Minor issue: неверный assert — no production impact
assert(leftPoints.size() == leftPoints.size());
-
Major issue: баг
if (upper && upper) return something;
-
Major issue: баг
template<class T> void operator () ( const char* name, const T& t1, const T& t2 ) { if( t1 != t1 ) { diff = true; } }
-
-
V674: The expression contains a suspicious mix of integer and real types.
- Major issue: баг
double precision = getSettingInt("Model", "Precision", 1e-6);
· V655: The strings were concatenated but are not utilized. Consider inspecting the expression.
switch (someEnum) { case someCondition: err + " is null "; break;
V596: The object was created but it is not being used. The 'throw' keyword could be missing.
- Minor issue: баг, который проявится с небольшой вероятностью
if (open(…)) { std::runtime_error("Can not open database"); }
V557: Array overrun is possible.
- Major issue: баг
float Jx[3]; … for (int i=0; i<4; i++){ for (int j=i; j<4; j++){ value += Jx[i]*Jx[j]; } }
- Major issue: баг
Автор: Align Technology, R&D