Инженер-программист
0,0
рейтинг
4 декабря 2015 в 12:45

Разработка → Verone — статический анализатор для C++ с анализом на лету из песочницы

Меня зовут Владимир Пляшкун и в сегодняшней статье я собираюсь представить бесплатный статический анализатор 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 появятся в течение недели-двух.

Готов ответить на любые вопросы. Буду рад обратной связи и пожеланиям!
Владимир Пляшкун @vplyashkun
карма
18,0
рейтинг 0,0
Инженер-программист
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

  • 0
    Установил, но расширение никак себя в студии не проявляет. Что нужно сделать?
  • +3
    Анализатор бесплатный, но не открытый?
  • +3
    Если качественно реализован разбор C++ кода со всеми Microsoft-specific вещами, пришедшими из глубины веков, плюс хорошая интеграция со студийными проектами и IDE, то вполне можно пока сосредоточиться на VS. На месте недорогих fixed price решений статического анализа пока зияющая пустота.
    • –20
      Недорогие fixed price решения никому не нужны: "Мы закрываем проект CppCat".
      • +3
        Там была скорее специфика бизнеса, достигшего определенной зрелости. В программировании нельзя недооценивать возможности одиночки.
      • +8
        1) Разве у вас был вариант покупки безлимитной по времени использования но ограниченной по возможностям обновления лицензии? Для программистов-одиночек это актуально.
        2) Отсутствие Linux-версии… в связи с чем индивидуальные разработчики скорей всего предпочитали использовать cppcheck.
  • +4
    Почему бы сразу не написать кросс-платформенное решение?
    • +2
      Кросс-платформа без открытого кода возможна лишь на сильной коммерческой основе.
      Открытому коду помогали бы владельцы заинтересованных платформ.
      А так, зачем автору распыляться на то, что не интересно. Лучше улучшать свои алгоритмы, чем портировать и делать бинарные сборки под платформы, с которыми не работаешь.
      • –4
        делать бинарные сборки под платформы
        20 минут jenkins настроить и хук в репозитории повесить.
        • +6
          А также заморочиться с написанием debian/, аналога под RPM, и прочих замечательных вещей.

          Собрать под дистрибутив — это не только скомпилировать в Jenkins.
        • +1
          Это вершина айсберга. Нужно тестировать под все платформы, продумать интеграцию с IDE, отличными от Visual Studio, следить за совместимостью с другим компилятором (основной, как я понимаю, MSVC, он не соберёт под все интересные платформы)
      • +2
        Интересно-неинтересно, но анонсируя анализатор для кросс-платформенного языка странно игнорировать большое количество разработчиков под Linux. Если разрабатывать только то, что интересно, и только под Windows, то это домашняя поделка, а не продукт который будут использовать.
        • +2
          А если разрабатывать не то, что интересно, под неинтересные и неудобные платформы, то можно быстро выгореть.

          Сужу по личному опыту. В итоге я в своём опенсорсе вообще забил на поддержку Windows (код кроссплатформенный, но собрать под Win надо постараться ввиду упарывания C++14) и не занимаюсь пакетированием под дистрибутивы.
    • +4
      Круче: почему бы сразу не дописывать диагностики в том же clang?
    • 0
      Кросс-платформенное решение требует большое количество времени и ресурсов для его поддержки. Мало выпустить решение под какую-либо платформу, т.к. по хорошему нужно обеспечить еще интеграцию с популярной для этой платформы средой разработки. Поэтому, на данный момент логичным решением все же является добавление начинки и функционала.
  • +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
    
  • +3
    Вы взялись за хорошее дело, сообществу нужен статический анализатор. Но почему вы заточились на Windows???

    В идеале анализатор должен быть кроссплатформенным и состоять из двух частей: консольная утилита, и GUI-морда, которая взаимодействует с консольной утилитой. Если вам тяжело делать под Linux, делайте под Win со своей GUI-мордой. При наличии консольной утилиты, сообщество быстро запилит GUI под Линуха на свободных инструментах.
    • +4
      До этого момента, важнее было сосредоточиться на алгоритмах и начинке анализатора. Пустое приложение на многих платформах никому нужно. Тем не менее, я собираю все предложения и идеи, которые здесь звучат, например, мне действительно нравится идея с кроссплатформенным open-source решением, поэтому думаю, что в течение нескольких месяцев, что-нибудь в этом направлении поменяется.
  • +4
    Вопрос еще в том, планируете ли вы получать деньги за анализатор или нет. Если планируете, то развитие VS направления будет самым оправданным.

    Если же не планируете, то нет никакого смысла писать проект в одиночку — делайте репозиторий на гитхабе, чтобы получать фидбеки, патчи, а, может, и целую команду заинтересованных разработчиков найдете. Также нет смысла поддерживать только VS, потому что многие разработчики, особенно одиночки, работают в других редакторах или IDE (vim, emacs, Qt Creator, KDevelop и прочее). Ну и как-то так сложилось, что среди юниксойдов больше ребят, готовых кодить open-source решения.
  • 0
    Здорово, спасибо! Жду версии для VS2013 :)
  • 0
    А где можно посмотреть описание диагностик?
    Вот допустим я получил Error: Undefined behavior detected. «delete» operator has argument with no definition.
    Соотв код:
    if ( m_pLastContour )
      delete m_pLastContour;
    m_pLastContour = pContour;
    

    В чем ошибка так и не понял
    • +1
      Видимо, не полностью определён тип, вроде такого:
      class Class;
      Class *m_pLastContour;
      ...
      delete m_pLastContour;
      
      • 0
        Да, эта диагностика сигнализирует именно об этой ситуации. На данный момент никакой дополнительной документации нет. В ближайшее время будут поправлены тексты ошибок, они станут более вменяемыми.
        Хочу правда заметить, что может возникнуть проблема с понимаем, если функция в которой это произошло будет шаблонной. В этом случае диагностика будет указывать тогда внутрь шаблонной функции и понять какая точка инстанцирования вызвало проблему не всегда тривиально.
        • 0
          это был typedef
  • 0
    Чем парсите код? clang?
    • +1
      Да, именно им.
  • 0
    При установке версии для VS2013: "The extension manifest is invalid."
    • 0
      Проблема может заключаться в том, что для открытия .vsix файла зарегистрирована более ранняя версия Visual Studio. О том как это изменить, хорошо написано здесь. А вообще такие вещи лучше писать и обсуждать в лс, нежели в комментариях.
      • 0
        А вообще такие вещи лучше писать и обсуждать в лс, нежели в комментариях.
        Это спорно. Вам тогда придётся отвечать одно и то же несколько раз, а разным пользователям задавать один и тот же вопрос. А так этот комментарий можно найти и никого не беспокоить лишний раз.
      • 0
        Verone совместим с ReSharper C++? Не вижу в VS 2013 ничего похожего на скрины выше, хотя папка #Verone на диске есть, внутри xml'ки с диагностикой. Может что-то где-то включить нужно?
        • 0
          Да, совместим.
          Но такая проблема может иметь место если скачанная, например, версия для Visual Studio 2013 была установлена на иную версию Visual Studio (например, 2012). Поскольку бинарные сборки между Visual Studio несовместимы между собой, расширение может некорректно работать. Но это, видимо, не для Вашего случая.
          Чтобы увидеть все обнаруженные диагностики можно щелкнуть на иконку анализатора на панели инструментов

          После перехода в нужный файл, справа от редактора будет отступ с подсвеченными ошибками как на первом скриншоте.

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