PVS-Studio - это инструмент для выявления ошибок в исходном коде программ, написанных на языках С, C++ и C#.
PVS-Studio выполняет статический анализ кода и генерирует отчёт, помогающий программисту находить и устранять ошибки. PVS-Studio выполняет широкий спектр проверок кода, но наиболее силён в поисках опечаток и последствий неудачного Copy-Paste. Показательные примеры таких ошибок: V501, V517, V522, V523, V3001.
Анализатор ориентирован на разработчиков, использующих среду Visual Studio, и может в фоновом режиме выполнять анализ измененных файлов после их компиляции. В идеале ошибки будут обнаружены и исправлены ещё до попадания в репозиторий. Однако ничто не мешает использовать анализатор для проверки всего решения целиком или для встраивания в системы непрерывной интеграции. Эти и иные способы использования анализатора описаны в документации.
PVS-Studio выполняет статический анализ кода и генерирует отчёт, помогающий программисту находить и устранять ошибки. PVS-Studio выполняет широкий спектр проверок кода, но наиболее силён в поисках опечаток и последствий неудачного Copy-Paste. Показательные примеры таких ошибок: V501, V517, V522, V523, V3001.
Анализатор ориентирован на разработчиков, использующих среду Visual Studio, и может в фоновом режиме выполнять анализ измененных файлов после их компиляции. В идеале ошибки будут обнаружены и исправлены ещё до попадания в репозиторий. Однако ничто не мешает использовать анализатор для проверки всего решения целиком или для встраивания в системы непрерывной интеграции. Эти и иные способы использования анализатора описаны в документации.
Проверка проекта OpenJDK с помощью PVS-Studio
Запись от el_programmer размещена 17.06.2016 в 12:31
Показов 3080
Комментарии 0
Метки c++, coding, java, open source, programming
|
Автор: Святослав Размыслов В настоящее время многие проекты открывают свой исходный код и разрешают делать изменения в нем сообществу заинтересованных разработчиков. Мы проверим один из таких проектов - OpenJDK, и поможем разработчикам улучшить их код. Введение OpenJDK (Open Java Development Kit) - проект по созданию реализации платформы Java (Java SE), состоящий исключительно из свободного и открытого исходного кода. Проект стартовал в 2006 году усилиями компании Sun. В проекте используются несколько языков - C, C++ и Java. Нас интересуют исходные коды написанные на С и С++. Для проверки возьмем 9-ю версию OpenJDK. Код этой реализации Java платформы доступен в репозитории Mercurial. Проект был проверен с помощью статического анализатора кода PVS-Studio. В нем реализовано множество диагностических правил, позволяющих найти большое количество ошибок, допущенных при программировании, в том числе и таких, которые трудно обнаружимы при простом просмотре кода. Некоторые из этих ошибок не влияют на логику работы программы, а некоторые могут привести к печальным последствиям при исполнении программы. На сайте анализатора есть примеры ошибок, найденных в других проектах. Для анализа доступны проекты, использующие языки C, C++ и С#. Ошибки в логических выражениях Сначала рассмотрим ошибки в логических выражениях:
Анализатор сообщает о дублировании проверки arg1 == c_rarg3. В этом фрагменте или избыточная проверка или, что гораздо хуже, присутствует логическая ошибка. Возможно, вместо продублированного условия должно было проверяться что-то другое. Разработчикам есть смысл взглянуть повнимательнее на этот код. В этом же условии есть еще одно повторяющееся выражение arg1 == c_rarg2: Предупреждение PVS-Studio:V501 There are identical sub-expressions 'arg1 == c_rarg2' to the left and to the right of the '||' operator. c1_Runtime1_x86.cpp 174 Эти предупреждения особенно хорошо показывают необходимость использования статического анализатора. В большом количестве однотипных выражений очень легко допустить ошибку, которую трудно обнаружить при поверхностном просмотре кода. В следующем фрагменте встретилась "неидеальная" проверка в условии метода Ideal:
Довольно странно в логическом выражении использовать 0. Скорее всего, этот фрагмент кода ещё в разработке и с целью отладки условие сделали невыполнимым. Соответствующие комментарии в коде отсутствуют, и в этом случае высока вероятность забыть исправить код в дальнейшем. Результатом этой ошибки будет то, что все, что находится внутри этого условия, никогда не будет исполняться, так как результат вычисления логического выражения будет всегда false. Приоритеты операций Довольно часто программисты надеются на свое знание приоритетов и не выделяют составные части сложного выражения скобками:
В данном случае я не знаю специфики кода, но есть подозрение, что в функции планировали выбирать значение смещения '2' или '0' в зависимости от результата вызова функции is_native(), но выражение имеет иной порядок вычисления. Сначала выполнится сложение sizeof(Method)/wordSize + is_native(), а потом уже будет возвращен результат 2 или 0. Скорее всего, код должен выглядеть так:
Copy-Paste Следующая характерная группа ошибок связана с копированием кода. От этого излюбленного приема программистов никуда не денешься, поэтому исследуем места, где был применен copy paste:
В этом примере полностью совпадают как условия в if и else if, так и тот код, который должен исполняться. Второе условие полностью бессмысленно, оно никогда не будет исполняться. Еще один похожий случай:
Исполняемый код в обоих блоках идентичен, соответственно нет никакой разницы, что рассчитывается в условии. Есть смысл взглянуть на это место и убрать ненужную ветку или, если подразумевалась разная логика, скорректировать код, чтобы исключить дублирование. Есть еще два места с идентичным дублированием. Просто укажу их, без приведения кода:
Ну и последний интересный пример, связанный с возможной ошибкой copy paste:
Что же происходит в этом коде? Перед блоком if объявлена переменная bool maybe_null = true;. Затем, когда исполняется код в блоке if, объявляется переменная с аналогичным именем. После выхода из блока значение этой переменной будет утеряно, и вызов функции, использующей эту переменную, произойдет в любом случае со значением true. Хорошо, если эта переменная была продублирована с целью отладки. В противном случае данный код исполняется некорректно и требует модификации:
Работа с указателями требует внимательности и аккуратности, так как при неправильном использовании указателей могут появиться ошибки, которые будет сложно идентифицировать. Как правило, основную опасность представляет использование битых указателей или использование указателей без проверки на нулевое значение. Первым делом рассмотрим случай явного использования нулевого указателя:
Совершенно непонятный код с использованием нулевого указателя, может привести к аварийному завершению программы. Возможно, эта ветка никогда не срабатывала, благодаря чему удалось избежать проблем при работе программы. В этом же файле нашлось еще три подобных места:
А вот в следующих случаях возможность использования нулевого указателя скрыта глубже. Это очень распространенная ситуация, подобные предупреждения встречаются практически во всех проверяемых проектах:
В нижнем блоке указатель проверяется на нулевое значение, значит, программист допускает возможность того, что значение указателя будет нулевым. Но в блоке выше указатель используется без проверки. Соответственно, если значение указателя будет нулевым, то такая проверка нам не поможет, и мы получим аварийное завершение программы. Чтобы исправить эту ошибку, надо проверить указатель выше обоих блоков. Приведу еще один аналогичный пример:
Здесь проверка указателя осуществляется снизу в тернарном операторе - _matrule ? _matrule->needs_base_oop_edge() : 0;. А выше происходит простое обращение к этому указателю - const char *src = _matrule->_rChild->_opType;. Рецепт исправления аналогичен: нужно проверить указатель до его использования. Таких мест нашлось довольно много, я приведу их списком:
Иногда программисты наоборот проверяют указатели, но делают это неправильно:
В данном случае проверка указателя _bigbuf на нулевое значение после использования new бессмысленна. В случае если системе не удастся выделить память, то будет сгенерировано исключение и выполнение функции прекратится. Для исправления ошибки можно использовать несколько подходов. Cделать выделение памяти в блоке try catch, или использовать для выделения памяти конструкцию new(std::nothrow), которая не будет генерировать исключение в случае неудачи. Есть еще несколько таких неправильных проверок:
Последняя ошибка в работе с указателями допущена при явном приведении указателя одного типа к указателю другого типа:
Указателю на float mlib_f32 *fsaпытаются присвоить указатель на double mlib_d64 dspace[1024], *dsa = dspace. Типы float и double имеют различный размер и подобное приведение типов, скорее всего, ошибочно. Несоответствие размеров приводимых типов приведет к тому, что указатель fsa будет указывать на некорректный для типа float формат числа. В другом файле есть еще два таких аналогичных преобразования, надо хорошенько проверить этот код и использовать правильные преобразования типов.
На этом закончим рассмотрение ошибок, связанных с использованием указателей и исследуем оставшиеся предупреждения анализатора. Разные ошибки Следующая ошибка, наверное, тоже результат неудачного копирования кода:
Здесь как раз тот случай, когда ошибка не влияет на работоспособность программы. Вместо сравнения трех символов, сравниваются только первые два, я не исключаю, что автор кода может и специально сделал такую проверку. Так как значение в буфере p может быть on или off, то достаточно сравнивать и первые два символа. Но для порядка, все-таки можно скорректировать код:
В этом классе попытались запретить копирование, но забыли добавить в приватную область оператор копирования. Он будет сгенерирован по умолчанию и будет доступен для использования. Даже если сейчас в коде нигде не используется этот оператор, то нет гарантии, что в дальнейшем кто-то случайно его не вызовет. При вызове такого оператора произойдет почленное копирование для класса, который не должен копироваться. Это может привести к различным эффектам вплоть до аварийного завершения программы. В данном случае следует добавить в приватную область объявление оператора "=". Есть еще два класса, где есть подобные проблемы, желательно скорректировать их таким образом, чтобы не нарушать "Закон Большой Двойки".
Последняя ошибка похожа на простую опечатку:
Программист хотел передать длину буфера, но не учел, что он не локально объявленный массив, а указатель приходящий в аргументе функции. В результате вычисления выражения sizeof(buf) мы получим не длину буфера, а размер указателя, который будет равен 4 или 8 байтам. Исправить ошибку легко, так выше длина буфера уже была получена: int len = (int)strlen(buf);.Правильный вариант будет выглядеть следующим образом:
Всегда интересно проверять проект, который использует множество людей и следит за его качеством. Нашлось достаточно большое количество ошибок, в этой статье описано только их некоторое количество, остальные требуют более глубокого исследования. Найденные ошибки еще раз подтверждают эффективность использования статического анализатора, так как он позволяет обнаружить ошибки, которые трудно обнаружить при просмотре простым глазом. Эффективнее всего использовать анализатор на постоянной основе, так как он сэкономит много времени, которое было бы потрачено на отладку программы при поиске багов. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Метки c++, coding, java, open source, programming
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии


