Топ-20 сервисов инспекции кода для разработчиков

Время на прочтение

Вызывает интерес ваш технический прогресс.Как у вас там сеют брюкву, с кожурою, али без?

Программист — творческая профессия. Мы создаем что-то новое, руководствуясь своими знаниями, внутренним пониманием качества и поставленными дедлайнами. Дедлайны и знания пока оставим в стороне и сосредоточимся на качестве.

Даже у двух братьев-программистов, закончивших один вуз и работающих в одной компании, это понимание качества  будет разным. А работать приходится в команде, и у коллег не должно возникать желания выкинуть весь ваш код и написать все с нуля.

Ниже делимся статьей 2017 года.

Топ-20 сервисов инспекции кода для разработчиков

«Что такое плохой/хороший код» — вопрос риторический. В нашей статье под качественным кодом мы будем понимать:

  • корректный с точки зрения платформы (без запросов к БД в шаблонах компонентов, запросы к БД с использованием индексов),
  • совместимый с актуальными версиями PHP (5.6, 7.0),
  • соответствующий единым стандартам кодирования , принятым в команде,
  • без ляпов (запросы к БД в цикле, запросы к внешним системам на хитах),
  • без уязвимостей (XSS, CSRF и т.д.).

С написанием хорошего кода отлично справляются самые опытные из нас: тимлиды. Но их мало (в штуках), а их время стоит дорого. У менее опытных разработчиков бывают проблемы со всем вышеперечисленным.

Как обеспечить качество кода с точки зрения методологии известно. Нужно развивать в команде процессы Continuous Integration: версионирование, тестирование, code review. Мы решили разобраться: какие инструменты подойдут для автоматизации code review при промышленной веб-разработке на PHP. А заодно проверим, как найденные инструменты оценят код нашего собственного сайта и нескольких решений из Marketplace 1С-Битрикс.

Инструменты для автоматизации code review PHP

Мы нашли на github.com и packagist.org самые оцениваемые и самые скачиваемые проекты, связанные с анализом кода PHP. В обзор мы включили только надежные (созданы не вчера), поддерживаемые (есть сообщество и контрибьюторы) и популярные (количество звезд-”лайков”).

Выделили три категории инструментов:

Инструменты для анализа PHP-кода

Топ-20 сервисов инспекции кода для разработчиков

PHP Code Sniffer анализирует PHP, CSS и JavaScript-файлы на соответствие стандартам кодирования, находит и исправляет ошибки. Стандарт представляет собой совокупность sniff-файлов, задающих правила. Количество установок анализатора с 2016 года превысило ~330 тысяч. Использование зафиксировано в ~7500 проектах.

PHP CS Fixer — инструмент, разработанный автором фрэймворка Symfony. Обнаруживает и исправляет ошибки в коде. Помимо следования стандартам кодирования (PSR-1, PSR-2 и др.). Позволяет писать собственные правила. Поддерживает PHP 7. Проект стремительно развивается и приобретает популярность как среди пользователей (~325 тысяч установок с начала 2016 года), так и у разработчиков (с 2017 года используется в ~1500 проектах).

PHP Mess Detector определяет ошибки кода, неоптимальные и усложненные места, неиспользуемые переменные, методы, свойства. Позволяет создавать пользовательские правила. Поддерживает PHP 7. Сохраняет отчеты в трех форматах: текстовый, html, xml. С 2016 года пользователи установили ~315 тысяч раз, используется в ~2000 проектах.

PHP Metrics — инструмент статического анализа для PHP. Выдает информацию о проекте и используемых классах в виде сгенерированного сайта. Поддерживает PHP 7. С 2016 года зарегистрировано ~260 тысяч скачиваний.

PHP Static Analysis Tool анализирует количество и типы параметров, передаваемых конструкторам, методам и функциям; типы, возвращаемые методами и функциями и т.д. Инструмент включает поддержку PHP 7 и предоставляет написание собственных правил. Количество скачиваний с 2016 года ~150 тысяч раз, использован разработчиками в ~200 проектах.

PHP Copy Paste Detector выдает информацию о дублированных участках кода. Проект набирает популярность (количество установок с 2016 года составляет ~100 тысяч раз, используется в ~1000 проектах).

Phan — статический анализатор для PHP. Проверяет определение и доступность методов, функций, классов, traits, интерфейсов, констант, свойств и переменных, обнаруживает неиспользуемый код, проверяет код на обратную совместимость php7 к php5. С 2016 года зафиксировано ~80 тысяч раз скачиваний.

PHP Dead Code Detector сообщает пользователю о функциях и методах, которые не используются в коде. Развитие проекта было остановлено в 2015 году. Начиная с 2016 года, количество установок пошло на спад.

Qafoo Quality Analyzer — инструмент, предназначенный для визуализации метрик исходного кода. Начиная с 2015 года, инструмент установлен пользователями ~4800 раз.

Инструменты для контроля совместимости кода с разными версиями PHP

Топ-20 сервисов инспекции кода для разработчиков

PHP Compatibility — набор правил для PHP Code Sniffer, которые проверяют совместимость текущего кода с другими версиями PHP, включая PHP 7. Начиная с 2016 года, инструмент установлен пользователями ~22 тысячи раз.

PHP 7 Compatibility Checker — инструмент для проверки кода PHP 5.3 – 5.6 на совместимость с PHP 7. Находит потенциальные проблемы в коде и генерирует отчеты, содержащие имена файлов, номера строк и краткое описание проблемы. При этом проблемы двух типов: ошибки, вызывающие серьезные проблемы (фатальная ошибка, ошибка синтаксиса и т.д.) и предупреждения, приводящие к логическим ошибкам. Начиная с 2016 года, инструмент установлен пользователями ~15 тысяч раз.

Инструменты для поиска уязвимостей в PHP-коде

Топ-20 сервисов инспекции кода для разработчиков

Вывод простой: не надейтесь что нажатием пары кнопок вы сможете найти все уязвимости в своем коде. Вместо этого изучите и применяйте правила безопасности, специфичные для вашей CMS/фреймворка .

Что на выходе

Результат запуска PHP CS Fixer — единый патч-файл, в котором указываются:

  • файлы, в которых обнаружены ошибки;
  • правила, которые сработали;
  • изменения, затронувшие строки файла: удаленные, оставшиеся без изменений, добавленные строки.

Топ-20 сервисов инспекции кода для разработчиков

PHP Code Sniffer

Итог работы PHP Code Sniffer — список файлов и сопутствующих им сообщений о том, сколько ошибок может быть автоматически исправлено, а также таблиц с описаниями нарушений в виде: строка, вид (ошибка, предупреждение), возможность исправления, описание.

Топ-20 сервисов инспекции кода для разработчиков

PHP Mess Detector

Отчет в PHP Mess Detector генерируется в одном из трех форматов: текстовый, html, xml. Полученный файл содержит информацию об обнаруженных проблемах в файлах. Проблема описывается начальной и конечной строкой, сработавшим правилом и поясняющим сообщением.

Топ-20 сервисов инспекции кода для разработчиков

PHP Dead Code Detector

Отчет в PHP Dead Code Detector — это список неиспользуемых функций и методов с указанием количества занимаемых строк, файла и номера строки в файле.

Топ-20 сервисов инспекции кода для разработчиков

PHP Copy Paste Detector

В отчет по PHP Copy Paste Detector заносятся файлы, в которых дублируются строки.

Топ-20 сервисов инспекции кода для разработчиков

PHP Static Analysis Tool

В отчете PHP Static Analysis Tool для каждого файла, в котором обнаружены ошибки, указывается номер строки в файле и описание ошибки. В конце отчета приводится общее количество найденных ошибок.

Топ-20 сервисов инспекции кода для разработчиков

Инструмент показал себя не лучшим образом и был исключён из анализа. Почему — читайте в конце статьи.

Phan

Результат работы в Phan может быть выведен в следующих режимах: ‘text’, ‘json’, ‘csv’, ‘codeclimate’, ‘checkstyle’, и ‘pylint’. В режиме ‘csv’ выводятся следующие столбцы: файл, строка, категория ошибки, phan-тип ошибки, сообщение.

Топ-20 сервисов инспекции кода для разработчиков

PHP Compatibility

Результатом PHP Compatibility является список ошибок и предупреждений, найденных при проверке на совместимость с указанной версией PHP.

Топ-20 сервисов инспекции кода для разработчиков

PHP 7 Compatibility Checker

При проверке на совместимость PHP 7 Compatibility Checker генерирует список нарушений в коде: указывается файл, строка, поясняющее сообщение и ошибочный код.

Топ-20 сервисов инспекции кода для разработчиков

Qafoo Quality Analyzer

Qafoo Quality Analyzer генерирует отчет в формате xml, где для каждого файла приводится список обнаруженных ошибок с описанием.

Топ-20 сервисов инспекции кода для разработчиков

PHP Metrics

PHP Metrics по окончании своего выполнения генерирует количественный отчет по метрикам и визуализирует полученные данные в виде созданного сайта.

Топ-20 сервисов инспекции кода для разработчиков

Топ-20 сервисов инспекции кода для разработчиков

Итоговая таблица сравнения инструментов

Топ-20 сервисов инспекции кода для разработчиков

Каждый из инструментов был опробован на коде:

  • тиражных решений из Marketplace 1С-Битрикс (3 платных решения, 5 бесплатных),
  • современного сайта ИНТЕРВОЛГИ (на 1С-Битрикс),
  • устаревшего сайта ИНТЕРВОЛГИ (на самописной CMS).

Прежде чем подвести итоги, сообщаем что инструмент PHP Static Analysis Tool был дисквалифицирован и исключён из соревнования. Причина — 90% его ошибок это “неизвестная константа”, “неизвестный метод”, “неизвестный класс” ядра Битрикса (мы проверяли только папку с решением). То есть, чем больше решение использовало API Bitrix, тем больше в нём было “ошибок”. Попытка добавить ядро Битрикса в анализ привела к аварийному завершению работы инструмента с ошибкой “Класс BitrixMainSystemException объявлен дважды”. И действительно, в ядре множество классов объявленных в разных местах. Решив “а давайте временно уберём этот класс”,  получили другую ошибку. Убрали её — получили третью и т.д. Попытки прекратили после 13-ой итерации.

Теперь к итогам сравнения решений. Мы считали не общее количество ошибок, а количество ошибок на 1000 строк кода.

ВАЖНО! Чтобы получить максимально показательную картину, мы суммировали ВСЕ замечания ВСЕХ 15 инструментов. Не удивляйтесь, что число ошибок часто больше числа строк.

Места распределились таким образом:

  • Старый сайт ИВ (703 ошибки  на 1000 строк)
  • 1С-Битрикс: Современный интернет-магазин (1457 ошибки на 1000 строк)
  • Новый сайт ИВ (1881 ошибки на 1000 строк)
  • 1С-Битрикс: Корпоративный сайт (1998 ошибки на 1000 строк)
  • Платный ИМ (2506 ошибки на 1000 строк)
  • Платный ИМ (2525 ошибки на 1000 строк)
  • Платный ИМ (2682 ошибки на 1000 строк)
  • Бесплатный ИМ (3542 ошибки на 1000 строк)
  • 1С-Битрикс: Информационный портал (3578 ошибки на 1000 строк)
  • Бесплатный ИМ (7361 ошибки на 1000 строк)

ВАЖНО! Чтобы получить максимально показательную картину, мы суммировали ВСЕ замечания ВСЕХ 15 инструментов. Не удивлятесь что число ошибок часто больше числа строк.

Подробные результаты приведены в таблице .

То, что лучшим стал старый сайт ИВ нас искренне удивило — мы ожидали прямо противоположного результата. У этого проекта 2 рекордных показателя — количество ошибок и количество строк кода. Так и вышло, что на 1000 строк кода проблем меньше всего. Но старый сайт — “почётный” участник, посмотрим на реальные результаты.

Настоящий победитель нашего исследования — 1С-Битрикс: Современный интернет-магазин. Что можно о нём сказать по результатам исследования:

  • Из 1000 строк в этом решении — в среднем 17 строк “копипасты”.
  • В 2 строках есть вызовы устаревшего API.
  • Есть 6 нарушений при работе с методами и полями.
  • 284 строки кода можно отформатировать автоматически с помощью PHP CS Fixer.
  • Почти нет проблем с совместимостью PHP.

Топ-20 сервисов инспекции кода для разработчиков

Второе место получает новый сайт ИНТЕРВОЛГИ . Разрабатывавшийся силами всей компании в разные годы, он как-то смог не уронить высокую планку качества, заданную компанией 1С-Битрикс.

Третье место — 1С-Битрикс: Корпоративный сайт . Снова 1С-Битрикс показал всем, как надо делать сайты.

Топ-20 сервисов инспекции кода для разработчиков

Худшим решением по мнению “жюри” признан бесплатный ИМ из ТОПа маркетплейса.

Добавим ложку дёгтя в бочку мёда компании 1С-Битрикс — вторым с конца оказалось тоже их решение, на которой выросло не одно поколение программистов.

Топ-20 сервисов инспекции кода для разработчиков

Предпоследнее место — 1С-Битрикс: Информационный портал

Отличить настоящие ошибки от ложного срабатывания было сложно в PHP Dead Code Detector — тут оказывались обработчики событий, агенты, конструкторы и инсталляторы, которые явным образом нигде не вызываются в коде или вызываются извне модулей.

Насчёт PHP Static Analysis Tool уже было сказано, что почти все его ошибки были “ложными срабатываниями”.

Читать также:  Невозможно запустить это приложение на вашем ПК — как исправить

Хорошо показал себя PHP Mess Detector: отследил мелкую, но неприятную ошибку статического вызова динамических методов и наоборот. В то же время он ругался на каждый else, так как  “else is never necessary and you can simplify the code to work without else”.

Главный “хлеб” таких инструментов, как Qafoo Quality Analyzer, PHP Code Sniffer,  PHP CS Fixer: превышение длины строк, переводы строк в конце файла, короткие-длинные открывающие php-теги, пробелы после ключевых слов if, for, while и т.п. Из предлагаемого инструментами набора правил мы выбрали наиболее похожие на стандарт оформления кода 1С-Битрикс, но всё равно получили около 50% ложных срабатываний.

Полезную статистику предоставляет PHP Metrics, если в вашей команде есть понимание, сколько операторов для класса уже “много”, а сколько — “ещё нормально”. Аналогично с цикломатической сложностью.

Вывод

Смысл затеи с инструментами проверки качества кода был в том, чтобы выяснить какой инструмент стоит выбрать для автоматизации Code Review. Эту цель мы достигли и свой выбор остановили на  PHP CS Fixer и PHP Code Sniffer. Они адекватны задаче, популярны, развиваются, их можно расширять, и есть масса уже готовых тестов. Осталось только адаптировать их к реалиям разработки сайтов на 1С-Битрикс: Управление Сайтом.

Теперь, с той же обстоятельностью и упорством, с которыми мы писали этот обзор, будем внедрять эти инструменты в свою командную разработку.

Соавтор статьи: Анатолий Ерофеев.

Топ-20 сервисов инспекции кода для разработчиков

PVS-Studio — статический анализатор исходного кода для поиска ошибок и уязвимостей в программах на языке C, C++ и C#. В этой статье я хочу дать обзор технологий, которые мы используем в анализаторе PVS-Studio для выявления ошибок в коде программ. Помимо общей теоретической информации я буду на практических примерах показывать, как та или иная технология позволяет выявлять ошибки.

Введение

Поводом для написания статьи стало моё выступление с докладом на открытой конференции ИСП РАН 2016 (ISPRAS OPEN 2016), проходившей в первых числах декабря в Главном здании Российской академии наук. Тема доклада: «Принципы работы статического анализатора кода PVS-Studio» (презентация в формате pptx).

К сожалению, время выступления было сильно ограничено, поэтому мне пришлось подготовить весьма короткую презентацию и не рассказать в докладе многое из того, что хотелось. Поэтому я решил написать эту статью, где более подробно расскажу о том, какие подходы и алгоритмы мы используем при разработке проекта PVS-Studio.

На данный момент PVS-Studio – это, фактически, два отдельных анализатора: один для C++, другой для C#. Более того, они написаны на разных языках. Ядро C++ анализатора мы разрабатываем на языке C++, а ядро C# анализатора на C#.

Однако, разрабатывая эти два ядра мы используем одинаковые подходы. Более того, ряд сотрудников одновременно принимают участие в разработке как C++, так и C# диагностик. Поэтому далее в статье я не стану разделять эти анализаторы. Описание механизмов будет общим для обоих анализаторов. Да, конечно есть какие-то отличия, но для обзорного знакомства они несущественны. Если же в процессе повествования возникнет необходимость, я явно укажу: идёт ли речь о С++ анализаторе, или C#.

Команда

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

Анализатор PVS-Studio разрабатывается в российской компании ООО «СиПроВер». Компания развивается на собственные средства, получаемые от продаж PVS-Studio. Офис компании находится в городе Тула, расположенном в 200 км. от Москвы.

На момент написания статьи штат компании насчитывает 24 человека.

Топ-20 сервисов инспекции кода для разработчиков

Некоторым кажется, что такой продукт как анализатор кода может сделать один человек. Однако, это большая работа, которая требует много человеко-лет. И еще больше человеко-лет требуется для того, чтобы поддерживать и развивать его.

Мы видим нашу миссию в популяризации методологии статического анализа кода. И конечно же в том, чтобы заработать денег, разрабатывая мощный инструмент, позволяющий выявлять как можно больше ошибок на самых ранних этапах разработки приложений.

Наши достижения

Для популяризации PVS-Studio мы регулярно проверяем различные открытые проекты и описываем в статьях найденные в них ошибки. На данный момент мы проверили около 270 проектов.

В процессе написания этих статей мы выявили уже более 10000 ошибок, о которых сообщили авторам проектов. Мы очень гордимся этим и сейчас я поясню почему.

Если разделить количество найденных ошибок на количество проектов, то получается не очень внушительное число: около 40 ошибок на проект. Поэтому я хочу выделить важный момент. Эти 10000 ошибок являются побочным эффектом. Мы никогда не ставили целью выявить как можно больше ошибок. Часто мы останавливаемся, когда нашли достаточное количество дефектов в проекте, чтобы написать статью.

Это очень хорошо демонстрирует удобство и возможности анализатора. Мы гордимся тем, что можно просто брать незнакомые проекты и почти без всякой настройки анализатора сразу находить в них ошибки. Если бы это было не так, мы бы не выявили 10000 ошибок просто как побочный результат от написания статей в блог.

PVS-Studio

Если совсем кратко, то PVS-Studio это:

  • Более 340 диагностик для C, C++;
  • Более 120 диагностик для C#;
  • Windows;
  • Linux;
  • Плагин для Visual Studio;
  • Быстрый старт (мониторинг компиляции);
  • Различные вспомогательные возможности, например, интеграция с SonarQube и IncrediBuild.

Почему C и C++

Языки C и C++ крайне эффективны и изящны. Но взамен требуют невероятного внимания к себе со стороны программиста и глубоких знаний предметной области. Поэтому статические анализаторы кода давно хорошо зарекомендовали себя среди C и С++ разработчиков. Причем, хотя языки, компиляторы и инструменты разработки развиваются, но, как говорится, ничего не меняется. Сейчас я поясню на примере, что имею в виду.

Топ-20 сервисов инспекции кода для разработчиков

К 30-летию языка C++ мы выполнили проверку первого компилятора языка Cfront, написанного в 1985 году. Кому интересны подробности, предлагаю ознакомиться со статьёй «К тридцатилетию первого C++ компилятора: ищем ошибки в Cfront».

В нем мы нашли вот такую ошибку:

Сначала указатель cl разыменовывается, а только потом проверяется на равенство NULL.

Прошло 30 лет.

Теперь перед нами код не компилятора Cfront, а современный Clang. И вот что в нём обнаруживает PVS-Studio:

Как говорится: «Баги. Баги C++ никогда не меняются». Указатель StrippedPtr сначала разыменовывается, а только потом проверяется на равенство NULL.

Анализаторы кода крайне полезны для языков C и C++. Поэтому мы развивали и будем развивать анализатор PVS-Studio для этих языков. Пока не предвидится, что у этих инструментов станет меньше работы, так как языки крайне популярны и при этом крайне опасны.

Почему C#

Конечно, в некоторых моментах язык C# более совершенный и безопасный, чем C++. Однако, далеко уйти ему не удалось, и он также доставляет массу головной боли программистам. Я ограничусь только одним из примеров, а вообще это тема для отдельной статьи.

Топ-20 сервисов инспекции кода для разработчиков

Мы вновь встречаем старого друга – ошибку, описанную выше. Фрагмент из проекта PowerShell:

Сначала ссылка other.Parameters используется для получения свойства Count, а только затем проверяется на равенство null.

Как видите, от того, что в C# указатели назвали ссылками, лучше не стало. Что касается различных опечаток, то они вообще не зависят от языка. В общем, для PVS-Studio есть работа и мы активно развиваем C#-направление.

Что дальше?

Топ-20 сервисов инспекции кода для разработчиков

Какие технологии мы не используем в PVS-Studio

Прежде чем рассказывать о внутреннем устройстве PVS-Studio кратко отмечу, чего в PVS-Studio нет.

PVS-Studio не имеет ничего общего с Prototype Verification System (PVS). Это просто совпадение. Аббревиатура PVS образована от названия нашей компании Program Verification Systems.

PVS-Studio не использует непосредственно математический аппарат грамматик для поиска ошибок. Анализатор работает на более высоком уровне. Анализ выполняется на основании дерева разбора.

PVS-Studio не использует компилятор Clang для анализа C/C++ кода. Clang используется для выполнения шага препроцессирования. Подробнее про это рассказано в статье «Немного о взаимодействии PVS-Studio и Clang». Для построения дерева разбора мы используем собственный парсер, который был основан на забытой сейчас библиотеке OpenC++. Впрочем, от кода той библиотеки почти ничего не осталось, и мы реализуем поддержку новых конструкция языка самостоятельно.

При работе с C# кодом мы опираемся на Roslyn. C# анализатор PVS-Studio проверяет непосредственно исходный код программы, что повышает точность анализа по сравнению с проверкой бинарного байт-кода (Common Intermediate Lanuage).

PVS-Studio не использует для поиска ошибок поиск подстрок (string matching) и регулярные выражения (regular expressions). Это тупиковый путь. У такого подхода так много недостатков, что на его основе невозможно сделать хоть сколько-нибудь качественный анализатор, а многие диагностики в принципе невозможно реализовать. Подробнее эта тема рассмотрена в моей статье «Статический анализ и регулярные выражения».

Какие технологии мы используем в PVS-Studio

Для обеспечения высокого качества результатов статического анализа мы используем передовые методы анализа исходного кода программы и её графа потока управления (control flow graph). Давайте ознакомимся с ними.

Примечание. Далее в качестве примеров будут рассмотрены некоторые диагностики и кратко описаны принципы их работы. Важно отметить, что я сознательно опускаю описание случаев, когда диагностика не должна срабатывать, чтобы не перегружать статью деталями. Это примечание я пишу для тех, кто не сталкивался с разработкой анализаторов: не думайте, что всё так просто, как будет написано ниже. Сделать диагностику — это только 5% работы. Ругаться на подозрительный код не сложно, намного сложнее не ругаться на корректный код. 95% времени при разработке диагностики уходит на обучение анализатора выделять различные приемы программирования, которые хотя и выглядят подозрительно для диагностики, на самом деле корректны.

Pattern-based analysis

Применяется для поиска мест в исходном коде, которые похожи на известные шаблоны кода с ошибкой. Шаблонов очень много, при этом сложность их выявления крайне разнится. Более того, некоторые диагностики для выявления опечаток прибегают к эмпирическим алгоритмам.

Топ-20 сервисов инспекции кода для разработчиков

Для начала давайте рассмотрим два самых простых случая, выявляемых с помощью паттерного анализа. Первый простой случай:

Предупреждение PVS-Studio: V523 The ‘then’ statement is equivalent to the ‘else’ statement. tree-ssa-threadupdate.c 2596

В независимости от условия, всегда выполняется один и тот же набор действий. Думаю, здесь всё настолько просто, что не требуются специальные пояснения. Кстати, этот фрагмент кода я встретил не в курсовой работе студента, а в коде компилятора GCC. С результатами проверки компилятора GCC можно ознакомиться в статье «Находим ошибки в коде компилятора GCC с помощью анализатора PVS-Studio».

Второй простой случай (код взят из проекта FCEUX):

Предупреждение PVS-Studio: V518 The ‘realloc’ function allocates strange amount of memory calculated by ‘strlen(expr)’. Perhaps the correct variant is ‘strlen(expr) + 1’. fceux cheat.cpp 609

Анализируется следующий ошибочный паттерн. Программисты знают, что когда они выделяют память для хранения строки, то должны дополнительно выделить память для одного символа, где будет храниться признак конца строки (терминальный ноль). Другими словами, программисты знают, что должны обязательно дописать +1 или +sizeof(TCHAR). Но делают это иногда небрежно. В результате они прибавляют 1 не к значению, которое возвращает функция strlen, а к указателю.

Из-за такой ошибки памяти выделяется чуть меньше, чем требуется. Далее произойдет выход за границу выделенного буфера и последствия будут непредсказуемы. Более того, программа может делать вид что корректно работает, если благодаря везению два байта после выделенного буфера не используются. При ещё более плохом сценарии, такой дефект может давать наведённые ошибки, которые будут проявлять себя совсем в другом месте.

Теперь рассмотрим анализ среднего уровня сложности.

Диагностика формулируется так: предупреждаем, если после использования оператора as на null проверяется исходный объект вместо результата оператора as.

Рассмотрим фрагмент кода из проекта CodeContracts:

Предупреждение PVS-Studio: V3019 Possibly an incorrect variable is compared to null after type conversion using ‘as’ keyword. Check variables ‘other’, ‘right’. CallerInvariant.cs 189

Обратите внимание, что на равенство null проверяется переменная other, а вовсе не right. Это явная ошибка, ведь потом работают именно с переменной right.

И под конец — сложный паттерн, связанный с использование макросов.

Макрос разворачивается так, что приоритет операции внутри макроса выше приоритета операции вне макроса. Пример:

Для разрешения проблемы нужно поместить в макросе аргумент a в скобки (а лучше и весь макрос тоже в скобки), то есть написать так:

Тогда макрос корректно развернётся в:

Рассмотрим эту ошибку на примере кода реального проекта FreeBSD:

Предупреждение PVS-Studio: V733 It is possible that macro expansion resulted in incorrect evaluation order. Check expression: chan — 1 * 20. isp.c 2301

Type inference

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

Топ-20 сервисов инспекции кода для разработчиков

Другими словами, анализатор должен знать, является токен Foo именем переменной, названием класса или функцией. Анализатор во многом повторяет работу компилятора, которому также требуется точно знать тип того или иного объекта и всю сопутствующую информацию о типе: размер, знаковый/беззнаковый тип, если класс, то от кого он наследуется и так далее.

Именно по этой причине анализатор PVS-Studio требует препроцессировать *.c/*.cpp файлы. Только анализируя препроцессированный файл можно собрать всю информацию о типах. Без такой информации многие диагностики осуществлять невозможно, или они будут давать много ложных срабатываний.

Примечание. Если кто-то заявляет, что их анализатор умеет проверять *.c/*.cpp файлы как текстовый документ, без полноценного препроцессирования, то знайте, это просто баловство. Да, такой анализатор может что-то находить, но в целом — это несерьезная игрушка.

Итак, информация о типах нужна как для выявления ошибок, так и для того, чтобы, наоборот, не выдавать ложные предупреждения. Особенно важна информация о классах.

Давайте рассмотрим на примерах, как используется информация о типах.

Первый пример демонстрирует, что информация о типе нужна, чтобы выявить ошибку при работе с функцией fprintf (код взят из проекта Cocos2d-x):

Предупреждение PVS-Studio: V576 Incorrect format. Consider checking the fourth actual argument of the ‘fprintf’ function. The pointer to string of char type symbols is expected. ccconsole.cpp 341

Функция frintf ожидает в качестве четвертого аргумента указатель типа char *. Случайно получилась так, что фактическим аргументом является строка типа wchar_t *.

Чтобы выявить эту ошибку надо знать тип, который возвращает функция gai_strerrorW. Если этой информации не будет, то и выявить ошибку не представляется возможным.

Теперь рассмотрим пример, когда знание информации о типе препятствует выдаче ложного предупреждения.

Код вида «*A = *A;» однозначно считается анализатором подозрительным. Однако, анализатор промолчит, если встретит следующую ситуацию:

Спецификатор volatile подсказывает, что это не ошибка, а специальная задумка программиста. Разработчику зачем-то надо «потрогать» ячейку памяти. Зачем ему это нужно — мы не знаем, но раз он так делает, то в этом есть смысл и выдавать предупреждение не следует.

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

Пример кода взят из проекта CoreCLR:

Предупреждение PVS-Studio: V598 The ‘memcpy’ function is used to copy the fields of ‘GCStatistics’ class. Virtual table pointer will be damaged by this. cee_wks gc.cpp 287.

Копировать один объект в другой с помощью функции memcpy вполне допустимо, если объекты являются POD-структурами. Однако здесь в классе присутствуют виртуальные методы, а значит имеется и указатель на таблицу виртуальных методов. Копировать этот указатель из одного объекта в другой крайне опасно.

Итак, диагностика стала возможна благодаря тому, что мы знаем, что переменная g_LastGCStatistics — это экземпляр класса, и что этот класс не является POD-типом.

Symbolic execution

Символьное выполнение позволяет вычислять значения переменных, которые могут приводить к ошибкам, производить проверку диапазонов (range checking) значений. В наших статьях мы иногда называем это механизмом вычисления виртуальных значений: см., например, статью «Поиск ошибок с помощью вычисления виртуальных значений».

Топ-20 сервисов инспекции кода для разработчиков

Зная предполагаемые значения переменных, можно выявлять такие ошибки как:

  • утечки памяти;
  • переполнения;
  • выход за границу массива;
  • разыменование нулевых указателей в C++ / доступ по нулевой ссылке в C#;
  • бессмысленные условия;
  • и так далее.

Рассмотрим, как используя знания о возможных значениях переменных можно находить различные ошибки. Начнём с фрагмента кода, взятого из проекта QuantLib:

Предупреждение PVS-Studio: V557 Array overrun is possible. The value of ‘i’ index could reach 64. markovfunctional.cpp 176

Здесь анализатор знает следующие данные:

Теперь рассмотрим ситуацию, где может возникнуть деление на 0. Код взят из проекта Thunderbird:

Функция UnboxedTypeSize возвращает различные значения, в том числе и 0. Не проверяя, что результат работы функции может быть 0, её используют как знаменатель. Потенциально это может привести к делению переменной offset на 0.

Предыдущие примеры касались диапазона целочисленных значений. Однако, анализатор оперирует и со значениями других типов данных, например, со строками и указателями.

Рассмотрим пример неправильной работы со строками. В данном случае анализатор хранит информацию о том, что вся строка была преобразована в верхний или нижний регистр. Это позволяет выявлять следующие ситуации:

Предупреждение PVS-Studio: V3122 The ‘lowerValue’ lowercase string is compared with the ‘lowerValue.ToUpper()’ uppercase string. ServerModeCore.cs 2208

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

Продолжать описывать диагностики, построенные на знаниях о значении переменных, можно долго. Я приведу только ещё один пример, связанный с указателями и утечкой памяти.

Код взят из проекта WinMerge:

Предупреждение анализатора: V773 The function was exited without releasing the ‘pMainFrame’ pointer. A memory leak is possible. Merge merge.cpp 353

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

Диагностика работает следующим образом. Анализатор запоминает, что указатель pMainFrame хранит адрес объекта, созданного с помощью оператора new. Анализируя граф потока управления, анализатор встречает оператор return. При этом объект не разрушался и указатель продолжает ссылаться на созданный объект. Это значит, что в этой точке возникает утечка памяти.

Method annotations

Аннотирование методов предоставляет больше информации об используемых методах, чем может быть получено путём анализа только их сигнатуры.

Топ-20 сервисов инспекции кода для разработчиков

Мы проделали большую работу по аннотированию функций:

  • C/C++. На данный момент проаннотировано 6570 функций (стандартные библиотеки C и C++, POSIX, MFC, Qt, ZLib и так далее).
  • C#. На данный момент проаннотировано 920 функций.

Рассмотрим, как в ядре C++ анализатора проаннотирована функция memcmp:

Краткие пояснения по разметке:

  • C_ — вспомогательный механизм контроля аннотаций (юнит-тесты);
  • REENTERABLE – повторный вызов с теми же аргументами даст тот же результат;
  • RET_USE – результат должен быть использован;
  • F_MEMCMP – запуск определённых проверок выхода за границы буфера;
  • HARD_TEST – особая функция: некоторые библиотеки определяют собственные идентичные функции в своих namespace и поэтому следует игнорировать namespace;
  • INT_STATUS – результат нельзя явно сравнивать с 1 или -1;
  • POINTER_1, POINTER_2 – указатели должны быть не нулевыми и разными;

Данные аннотации используются многими диагностиками. Рассмотрим некоторые ошибки, которые мы обнаружили в коде приложений благодаря приведенной выше разметке для функции memcmp.

Пример использования разметки INT_STATUS. Проект CoreCLR:

Такой код может работать, но в целом он неверен. Функция memcmp возвращает значения 0, больше нуля и меньше нуля. Важно:

Таким образом, нет никакой гарантии в работоспособности написанного кода. В любой момент сравнение может начать работать неправильно. Это может произойти при смене компилятора, изменении настроек оптимизации и так далее.

Флаг INT_STATUS помогает выявить ещё один вид ошибки. Код проекта Firebird:

PVS-Studio. V642 Saving the ‘memcmp’ function result inside the ‘short’ type variable is inappropriate. The significant bits could be lost breaking the program’s logic. texttype.cpp 3

Вновь неаккуратно работают с результатом, который возвращает функция memcmp. Ошибка в том, что происходит усечение размера типа: результат помещают в переменную типа short.

Некоторые могут подумать, что мы придираемся. Ничуть. Такой неаккуратный код может легко стать причиной самой настоящей уязвимости.

Одна такая ошибка послужила причиной серьезной уязвимости в MySQL/MariaDB до версий 5.1.61, 5.2.11, 5.3.5, 5.5.22. Причиной стал следующий код в файле ‘sql/password.c’:

Пример использования разметки BYTE_COUNT. Проект GLG3D:

Предупреждение PVS-Studio: V575 The ‘memcmp’ function processes ‘0’ elements. Inspect the ‘third’ argument. graphics3D matrix4.cpp 269

Третий аргумент функции memcmp помечен как BYTE_COUNT. Считается, что такой аргумент не должен быть равен 0. В приведённом фрагменте кода третий фактический параметр как раз равен 0.

Ошибка заключается в том, что не там поставлена скобка. В результате третьим аргументом является выражение sizeof(Matrix4) == 0. Результат этого выражения false, т.е. 0.

Пример использования разметки POINTER_1 и POINTER_2. Проект GDB:

Предупреждение PVS-Studio: V549 The first argument of ‘memcmp’ function is equal to the second argument. psymtab.c 1580

Первый и второй аргументы размечены как PONTER_1 и POINTER_2. Во-первых, это означает, что они не должны быть равны NULL. Но в данном случае нам интересно второе свойство разметки: эти указатели не должны совпадать, о чем говорят суффиксы _1 и _2.

Пример использования разметки F_MEMCMP.

Данная разметка включает ряд специальных диагностик для таких функций, как memcmp и __builtin_memcmp. В результате может быть выявлена вот такая ошибка в проекте Haiku:

Предупреждение PVS-Studio: V512 A call of the ‘memcmp’ function will lead to underflow of the buffer ‘«Private-key-format: v»’. dst_api.c 858

Строка «Private-key-format: v» состоит из 21 символа, а не из 20. Таким образом, сравнивается меньше байт чем требуется.

Пример использования разметки REENTERABLE. Если честно, слово «reenterable» не совсем верно отражает суть данного флага. Однако, все разработчики в нашей команде к нему привыкли и не хочется делать изменений в коде ради красоты.

Суть разметки в следующем. Функция не имеет состояния и никаких побочных эффектов: не меняет память, не печатает что-то на экран, не удаляет файлы на диске. Благодаря этому анализатор может отличать правильные конструкции от неправильных. Например, вот такой код вполне законен:

if (fprintf(f, «1») == 1 && fprintf(f, «1») == 1)

Анализатор не будет выдавать предупреждений. Мы записываем две единицы в файл и код нельзя сократить до:

if (fprintf(f, «1») == 1) // неправильно

А вот такой код избыточен и насторожит анализатор, так как функция cosf не имеет состояния и никуда ничего не записывает:

Вернемся теперь к функции memcmp и посмотрим, какую ошибку удалось обнаружить с помощью рассмотренной разметки в проекте PHP:

Как видите, с помощью разметки можно находить много интересных ошибок. Часто анализаторы предоставляют возможности пользователям самостоятельно аннотировать функции. В PVS-Studio эти возможности развиты слабо. В нем есть всего несколько диагностик, для которых можно что-то проаннотировать. Например, это диагностика V576 для поиска ошибок использования функций форматного вывода (printf, sprintf, wprintf и так далее).

Мы сознательно не развиваем механизм пользовательских аннотаций. На это есть две причины:

  • В большом проекте никто не станет тратить время на разметку функций. Это просто нереально, когда у вас 10 миллионов строк кода, а ведь анализатор PVS-Studio как раз ориентирован на средние и большие проекты.
  • Если не размечены функции из какой-то известной библиотеки, то лучше написать нам и мы сами проаннотируем их. Во-первых, мы сделаем это быстрее и лучше, а во-вторых, результаты разметки станут доступны всем нашим пользователям.

Ещё раз кратко о технологиях

Кратко резюмирую свой рассказ о используемых технологиях. PVS-Studio использует:

  • Сопоставление с шаблоном (pattern-based analysis) на основе абстрактного синтаксического дерева: применяется для поиска мест в исходном коде, которые похожи на известные шаблоны кода с ошибкой.
  • Вывод типов (type inference) на основе семантической модели программы: позволяет анализатору иметь полную информацию о всех переменных и выражениях, встречающихся в коде.
  • Символьное выполнение (symbolic execution): позволяет вычислять значения переменных, которые могут приводить к ошибкам, производить проверку диапазонов (range checking) значений.
  • Анализ потока данных (data-flow analysis): используется для вычисления ограничений, накладываемых на значения переменных при обработке различных конструкций языка. Например, какие значения может принимать переменная внутри блоков if/else.
  • Аннотированние методов (method annotations): предоставляет больше информации об используемых методах, чем может быть получено путём анализа только их сигнатуры.

На основе этих технологий анализатор умеет выявлять следующие классы ошибок в программах на языках C, C++ и C#:

Вывод. Анализатор PVS-Studio является мощным инструментом поиска ошибок, использующим для этого современный арсенал методов для их выявления.

Топ-20 сервисов инспекции кода для разработчиков

Да, PVS-Studio это положительный супергерой мира программ.

Тестирование PVS-Studio

Разработка анализаторов кода невозможна без их постоянного тщательного тестирования. При разработке PVS-Studio мы используем 7 различных методик тестирования:

  • Статический анализ кода на машинах разработчиков. У всех разработчиков установлен PVS-Studio. Новый или изменённый код сразу проверяется с помощью механизма инкрементального анализа. Проверяется C++ и С# код.
  • Статический анализ кода при ночных сборках. Если предупреждение не было замечено, то оно выявится на этапе ночной сборки на сервере. PVS-Studio проверяет C# и C++ код. Помимо этого, мы дополнительно используем компилятор Clang для проверки C++ кода.
  • Юнит-тесты уровня классов, методов, функций. Не очень развитая система, так как многие моменты сложно тестировать из-за необходимости подготавливать для теста большой объем входных данных. Мы больше полагаемся на высокоуровневые тесты.
  • Функциональные тесты уровня специально подготовленных и размеченных файлов с ошибками. Это наша альтернатива классическому юнит-тестированию.
  • Функциональные тесты, подтверждающие, что мы корректно разбираем основные системные заголовочные файлы.
  • Регрессионные тесты уровня отдельных сторонних проектов и решений (projects and solutions). Самый важный и полезный для нас вид тестирования. Сравнивая старые и новые результаты анализа, мы контролируем, что что-то не сломали и оттачиваем новые диагностические сообщения. Для его осуществления мы регулярно проверяем открытые проекты. C++ анализатор тестируется на 120 проектах под Windows (Visual C++), плюс дополнительно на 24 проектах под Linux (GCC). Анализатор C# пока тестируется чуть слабее. Тестовая база состоит из 54 проектов.
  • Функциональные тесты пользовательского интерфейса расширения — надстройки, интегрируемой в среду Visual Studio.

Заключение

Эта статья написана для популяризации методологии статического анализа. Думаю, читателям интересно знать не только о результатах применения анализаторов кода, но и том, как они устроены внутри. Постараюсь время от времени писать статьи на эту тематику.

Напоследок несколько ссылок:

Топ-20 сервисов инспекции кода для разработчиков

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. How PVS-Studio does the bug search: methods and technologies.

Топ-20 сервисов инспекции кода для разработчиков

Мы уже рассказывали в блоге об открытых разработках исследователей Университета ИТМО. Например, об инструменте для поиска генов устойчивости к антибиотикам и приложении, позволяющем  анализировать бизнес-процессы.

В этот раз мы поговорим о Hawk  ―  сервисе, который автоматически анализирует код и отслеживает в нем ошибки.

О команде

Команда CodeX состоит из студентов и выпускников мировых университетов, в том числе Университета ИТМО. Цель объединения — вместе с талантливыми единомышленниками менять IT мир к лучшему, создавая проекты с открытым исходным кодом. На сегодняшний день, Кодекс создал уже более ста проектов разного масштаба — от небольших библиотек, до крупных продуктов. Все они имеют открытый код.

Наиболее известная разработка команды — визуальный редактор Editor.js, вошедший в число самых популярных проектов такого типа в мире.

О проекте

Новый проект, Hawk, получился не менее масштабным, чем редактор. Он состоит из трех десятков публичных репозиториев, общая кодовая база достигает сотен тысяч строк. От первого прототипа до релиза на мировом рынке прошло более четырех лет разработки.

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

Тем не менее, первая версия имела недостатки. При достижении метрики в 10 миллионов обрабатываемых ошибок в сутки проявились проблемы с выдерживаемой нагрузкой. Стало понятно, что архитектура нуждается в пересмотре. Следующие 3 года ушли на проектирование микросервисной архитектуры, переписывание всего бэкенда с нуля, редизайн и переделку клиентов.

Топ-20 сервисов инспекции кода для разработчиков

Принцип работы

Hawk — трекер ошибок. Разработчики интегрируют в свои проекты специальный скрипт «Кэтчер», который отлавливает необработанные исключения и отправляет их в «Гараж» — сервис для просмотра случившихся событий, изучения деталей ошибок, распределения их между исполнителями для исправления.

Топ-20 сервисов инспекции кода для разработчиков

В Гараже ошибки группируются по дням, сортируются по количеству, выводятся в удобном для восприятия виде. Кроме того, CodeX рассказывала о планах по разработке системы предсказания приоритета ошибки с помощью машинного обучения.

Разработчики Hawk дали возможность видеть сразу все воркспейсы одновременно. Лэйаут Хоука похож на мессенджер: слева выводится список проектов, которые поднимаются наверх при получении новых ивентов. Справа — список ивентов в проекте. Кнопки воркспейсов работают как фильтры — скрывают из списка проектов те, что не принадлежат воркспейсу. Благодаря этому решению, работать с большим количеством интеграций становится удобнее.

По каждому событию можно посмотреть подробную информацию с собранными Хоуком данными: контекст ошибки, фрагмент кода, коммит и тд. Это должно помочь выяснить причину и исправить ошибку.

Топ-20 сервисов инспекции кода для разработчиков

О пойманных ошибках Хоук сообщает владельцу проекта через ботов в Telegram, Slack или на почту.

Как устроен сервис

Хоук выдерживает высокие нагрузки благодаря обновленной архитектуре: в отличие от прототипа, который был монолитным приложением, теперь части системы не влияют друг на друга.

Кэтчеры представляют из себя скрипты под разные языки программирования, суть которых сводится к установке глобального обработчика ошибок и отправке их в Хоука. Все отправленные ошибки попадают в Коллектор — сервис приемки, написанный на Go. Проделав минимальную обработку и валидацию, Коллектор складывает ивенты в очередь на дальнейшую обработку.

В качестве брокера очередей выбран RabbitMQ. Консюмеры — Воркеры — фоново забирают ошибки из очереди, обрабатывают их и кладут в базу. Воркеры могут писаться на любом технологическом стеке. В данный момент большинство из них работает на Node.js. Время обработки событий с момента отправки до появления в Гараже — в среднем 5-10 секунд.

Топ-20 сервисов инспекции кода для разработчиков

API для приложений работает на Node.js и GraphQL. База пользователей, проектов, ворскпейсов отделена от базы винтов. Есть отдельный микросервис CodeX Accounting для учета платежных операций. У него свой SDK, API (GraphQL), и база данных.

Сервис просмотра ошибок, Гараж, написан на Vue. Также имеются нативные приложения для Windows, Mac, Linux, созданные с помощью Electron.

Технические требования

В данный момент команда представила Кэтчеры под следующие языки: JavaScript (TypeScript), Node.js, PHP, Go, Python, Scala, Kotlin. При необходимости, разработчики могут создать собственные кэтчеры под свой стек или фреймворк, следуя инструкции.

По задумке команды, большинство пользователей Хоука имеют бесплатный полный доступ ко всем функциям. Для крупных клиентов, присылающих более 100 000 ошибок в сутки, предусмотрены несколько платных тарифных планов.

Сравнение с конкурентами

Главные аналоги — Sentry, Rollbar, TrackJS. По сравнению с ними, цены тарифных планов Хоука существенно ниже, а бесплатный план объемнее. Главная функциональность по отлову ошибок и просмотру контекста у проектов схожа. Второстепенных функций меньше. Из преимуществ: более легкая интеграция, удобная работа с несколькими проектами и воркспейсами, группировка событий по дням, удобный и приятный визуальный дизайн, быстрая скорость работы.

UPD. Обновили полностью текст вместе с командой CodeX — добавили больше информации о принципах работы приложения.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *