Меня зовут Владимир Пляшкун и в сегодняшней статье я собираюсь представить бесплатный статический анализатор Verone для C++03/C++11/C++14, главной особенностью которого является анализ на лету.

На сегодняшний день имеется большой выбор инструментов статического анализа для языка C++ это Coverity/Klocwork/Parasoft/Clang/CppCheck/PVS-Studio и многие другие. А недавно еще вышел ReSharper C++.

Тем не менее, задача анализа на лету по-прежнему актуальна, т.к. практически нет решений обладающих данной особенностью. Тот же ReSharper хоть и интегрируется с Visual Studio и находит многие проблемы онлайн (например, неиспользуемые заголовочные файлы), но он все же видится больше как средство рефакторинга, нежели классический статический анализатор. У R# практически нет диагностик семантических ошибок, copy-paste ошибок и опечаток, а также ошибок времени выполнения (утечки памяти, буферные переполнения, разыменовывания нулевых указателей и т.д.)

Coverity/Klocwork/Parasoft – каждый из этих инструментов называет себя лидером рынка, но получить даже пробную версию этих анализаторов индивидуальному разработчику будет крайне затруднительно, ибо целевая направленность у этих продуктов иная и направлена на большие компании. А потому, рассматривать их нет смысла.

Clang, в основном направлен на поиск уязвимостей в исходной программе. Доступные проверки приведены здесь.

Огорчает, что clang практически не диагностирует опечатки и семантические ошибки, да и использовать clang на данный момент за пределами XCode несколько неудобно. Конечно, анализатор можно запустить из консоли, но такая схема требует ручной настройки (указание списка файлов, пути к директориям заголовочных файлов, предопределенные макросы и пр.). А потому, такой периодический запуск внешней утилиты за пределами IDE вряд ли можно назвать удобным и полезным.

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

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



Цвет, так называемого, глифа зависит от типа диагностики (заметка, предупреждение, ошибка). Чтобы посмотреть все найденные проблемы достаточно щелкнуть на иконку анализатора на панели инструментов, после чего появится окно со всеми обнаруженными проблемами:



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

Например, в исходном коде LLVM были найдены проблемы следующего характера:

else if (ArgTy->isStructureOrClassType())
    return record_type_class;
else if (ArgTy->isUnionType())   //<---
    return union_type_class;
else if (ArgTy->isArrayType())
    return array_type_class;
else if (ArgTy->isUnionType())  //<----
    return union_type_class;

Здесь условие

ArgTy->isUnionType()

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

if ((NDesc & MachO::REFERENCE_TYPE) == MachO::REFERENCE_FLAG_UNDEFINED_LAZY)  //<----
    outs() << "undefined [lazy bound]) ";
else if ((NDesc & MachO::REFERENCE_TYPE) == MachO::REFERENCE_FLAG_UNDEFINED_LAZY)  //<---
    outs() << "undefined [private lazy bound]) ";
else if ((NDesc & MachO::REFERENCE_TYPE) == MachO::REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY)
    outs() << "undefined [private]) ";
else
    outs() << "undefined) ";

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

(NDesc & MachO::REFERENCE_TYPE) == MachO:: REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY

Подобная проблема была обнаружена и проекте libsass.

if (lhs_tail->combinator() != rhs_tail->combinator()) return false;
if (lhs_tail->head() && !rhs_tail->head()) return false;
if (!lhs_tail->head() && rhs_tail->head()) return false;
if (lhs_tail->head() && lhs_tail->head()) {  //<---
    if (!lhs_tail->head()->is_superselector_of(rhs_tail->head())) 
        return false;
}

В этом фрагменте ошибочно последнее условие:

lhs_tail->head() && lhs_tail->head()

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

Было найдено множество подозрительных мест в графическом движке Torque 3D.

// Update accumulation texture if it changed.
// Note: accumulation texture can be NULL, and must be updated.
if ( passRI->accuTex != lastAccuTex )
{
    sgData.accuTex = passRI->accuTex;
    lastAccuTex = lastAccuTex;
    dirty = true;
}

Исходя из логики, указатель lastAccuTex (GFXTextureObject*) ссылается на последний обработанный объект passRI->accuTex, но из-за невнимательности переменной присваивается собственное значение.

А в этом фрагменте:

class ConnectionProtocol
{
public:
   ConnectionProtocol();
   virtual void processRawPacket(BitStream *bstream);
   virtual Net::Error sendPacket(BitStream *bstream) = 0;
   virtual void keepAlive() = 0;
   virtual void handleConnectionEstablished() = 0;
   virtual void handleNotify(bool recvd) = 0;
   virtual void handlePacket(BitStream *bstream) = 0;
	...
};

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

Есть подозрительные места с целочисленным делением, отбрасывающее дробную часть, как например здесь:

bm->avgfloat=PACKETBLOBS/2

Переменная bm->avgfloat имеет тип с плавающей точкой, но поскольку справа от оператора присваивания стоит целочисленное деление, дробная часть будет отброшена. Не сказать что это однозначная ошибка, но внимания требует.

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

  1. Более продвинутый алгоритм анализа на лету, основанный на генерации mock-файлов (спасибо Саше Коковину за идею)
    Сейчас данный алгоритм работает крайне просто. По сути, все завязано вокруг файла с парами <имя файла> — <дата последнего анализа>. А ядро анализатора просто запускается с некоторой периодичностью. Поскольку в процессе работы изменяется небольшое количество файлов, такая схема все же позволила масштабироваться на большие проекты.
  2. Возможность анализировать еще несохраненные изменения в редакторе
    Вышеописанная схема, к сожалению, завязана на файлах и не учитывает изменения, которые уже сделаны в редакторе, но еще не сохранены. Данная задача достаточно объемная и требует тщательного тестирования, а потому не была реализована в первом релизе.
  3. Анализ ошибок времени выполнения
    Одним из основных направлений является и анализ ошибок времени выполнения, которого нет в текущей версии. Анализ ошибок времени выполнения вкупе с анализом на лету выглядит очень перспективно, ибо видеть, например, разыменовывание нулевого указателя в момент кодирования, по-моему, очень круто! Безусловно, придется пойти на некоторые упрощения, ибо алгоритмы анализа потока данных достаточно ресурсоемки. Но даже, например, локальный анализ все-равно будет приносить ощутимую пользу, предупреждая разработчика о возможных проблемах.

Из более отдаленных задач стоит выделить также:

  1. Улучшение графического интерфейса
    Сейчас нет функционала для скрытия диагностик, их поиска и сохранения в файл, что было бы полезно в повседневной работе.
  2. Освоение иных IDE и операционных систем
  3. Standalone приложение
    Самостоятельное графическое приложение полезно, т.к. не приковано ни к какой среде разработки. Уже сейчас можно спокойно использовать консольную версию анализатора, но версия с графическим интерфейсом видится более удобным решением для этой цели.

Анализатор доступен для загрузки по ссылке. На данный момент анализатор доступен только для Windows, доступна версия для интеграции с Visual Studio 2015. Версии для Visual Studio 2012 и Visual Studio 2013 появятся в течение недели-двух.

Готов ответить на любые вопросы. Буду рад обратной связи и пожеланиям!

Комментарии (28)


  1. maxdm
    04.12.2015 13:51

    Установил, но расширение никак себя в студии не проявляет. Что нужно сделать?


  1. qvlad
    04.12.2015 13:52
    +3

    Анализатор бесплатный, но не открытый?


  1. OlegMax
    04.12.2015 14:29
    +3

    Если качественно реализован разбор C++ кода со всеми Microsoft-specific вещами, пришедшими из глубины веков, плюс хорошая интеграция со студийными проектами и IDE, то вполне можно пока сосредоточиться на VS. На месте недорогих fixed price решений статического анализа пока зияющая пустота.


    1. Andrey2008
      04.12.2015 14:36
      -20

      Недорогие fixed price решения никому не нужны: "Мы закрываем проект CppCat".


      1. OlegMax
        04.12.2015 14:54
        +3

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


      1. BelBES
        04.12.2015 16:13
        +8

        1) Разве у вас был вариант покупки безлимитной по времени использования но ограниченной по возможностям обновления лицензии? Для программистов-одиночек это актуально.
        2) Отсутствие Linux-версии… в связи с чем индивидуальные разработчики скорей всего предпочитали использовать cppcheck.


  1. paceholder
    04.12.2015 15:29
    +4

    Почему бы сразу не написать кросс-платформенное решение?


    1. qw1
      04.12.2015 15:50
      +2

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


      1. fshp
        04.12.2015 17:33
        -4

        делать бинарные сборки под платформы
        20 минут jenkins настроить и хук в репозитории повесить.


        1. 0xd34df00d
          04.12.2015 18:00
          +6

          А также заморочиться с написанием debian/, аналога под RPM, и прочих замечательных вещей.

          Собрать под дистрибутив — это не только скомпилировать в Jenkins.


        1. qw1
          04.12.2015 20:41
          +1

          Это вершина айсберга. Нужно тестировать под все платформы, продумать интеграцию с IDE, отличными от Visual Studio, следить за совместимостью с другим компилятором (основной, как я понимаю, MSVC, он не соберёт под все интересные платформы)


      1. paceholder
        04.12.2015 18:08
        +2

        Интересно-неинтересно, но анонсируя анализатор для кросс-платформенного языка странно игнорировать большое количество разработчиков под Linux. Если разрабатывать только то, что интересно, и только под Windows, то это домашняя поделка, а не продукт который будут использовать.


        1. 0xd34df00d
          04.12.2015 19:41
          +2

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

          Сужу по личному опыту. В итоге я в своём опенсорсе вообще забил на поддержку Windows (код кроссплатформенный, но собрать под Win надо постараться ввиду упарывания C++14) и не занимаюсь пакетированием под дистрибутивы.


    1. 0xd34df00d
      04.12.2015 17:59
      +4

      Круче: почему бы сразу не дописывать диагностики в том же clang?


    1. vplyashkun
      04.12.2015 19:14

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


      1. paceholder
        04.12.2015 19:26
        +3

        1. qw1
          04.12.2015 20:43

          Мне больше нравится эта ссылка ))
          toster.ru/q/14928


  1. 0xd34df00d
    04.12.2015 17:58
    +5

    Конечно, анализатор можно запустить из консоли, но такая схема требует ручной настройки (указание списка файлов, пути к директориям заголовочных файлов, предопределенные макросы и пр.).

    Нет, см. scan-build. Например, для одного моего проекта весь запуск статического анализа, включая создание специфичной для сборки директории, сводится к
    mkdir -p build/clang-analyzer
    cd build/clang-analyzer
    export CCC_CC=/usr/bin/clang
    export CCC_CXX=/usr/bin/clang++
    cmake -DCMAKE_C_COMPILER=/usr/bin/ccc-analyzer -DCMAKE_CXX_COMPILER=/usr/bin/c++-analyzer ../../src
    scan-build make -j12
    


  1. webhamster
    05.12.2015 01:13
    +3

    Вы взялись за хорошее дело, сообществу нужен статический анализатор. Но почему вы заточились на Windows???

    В идеале анализатор должен быть кроссплатформенным и состоять из двух частей: консольная утилита, и GUI-морда, которая взаимодействует с консольной утилитой. Если вам тяжело делать под Linux, делайте под Win со своей GUI-мордой. При наличии консольной утилиты, сообщество быстро запилит GUI под Линуха на свободных инструментах.


    1. vplyashkun
      05.12.2015 12:48
      +4

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


  1. oYASo
    06.12.2015 15:23
    +4

    Вопрос еще в том, планируете ли вы получать деньги за анализатор или нет. Если планируете, то развитие VS направления будет самым оправданным.

    Если же не планируете, то нет никакого смысла писать проект в одиночку — делайте репозиторий на гитхабе, чтобы получать фидбеки, патчи, а, может, и целую команду заинтересованных разработчиков найдете. Также нет смысла поддерживать только VS, потому что многие разработчики, особенно одиночки, работают в других редакторах или IDE (vim, emacs, Qt Creator, KDevelop и прочее). Ну и как-то так сложилось, что среди юниксойдов больше ребят, готовых кодить open-source решения.


  1. 1eqinfinity
    08.12.2015 13:10

    Здорово, спасибо! Жду версии для VS2013 :)


  1. fareloz
    08.12.2015 15:40

    А где можно посмотреть описание диагностик?
    Вот допустим я получил Error: Undefined behavior detected. «delete» operator has argument with no definition.
    Соотв код:

    if ( m_pLastContour )
      delete m_pLastContour;
    m_pLastContour = pContour;
    

    В чем ошибка так и не понял


    1. xaizek
      08.12.2015 16:26
      +1

      Видимо, не полностью определён тип, вроде такого:

      class Class;
      Class *m_pLastContour;
      ...
      delete m_pLastContour;
      


      1. vplyashkun
        09.12.2015 16:31

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


        1. fareloz
          10.12.2015 14:41

          это был typedef


  1. mmatrosov
    11.12.2015 12:39

    Чем парсите код? clang?


    1. vplyashkun
      12.12.2015 12:05
      +1

      Да, именно им.