Для получения триального ключа
заполните форму ниже
Team License (базовая версия)
Enterprise License (расширенная версия)
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
GBP
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
Команда PVS-Studio расширяет кругозор, …

Команда PVS-Studio расширяет кругозор, выполняя разработку на заказ

Как вы знаете, основная наша деятельность – это разработка анализаторов кода PVS-Studio. И хотя мы давно и, как нам кажется, успешно этим занимаемся, недавно у нас появилась необычная мысль. Все-таки мы не пользуемся своими инструментами в том режиме, что и наши клиенты. Нет, конечно, мы проверяем код PVS-Studio с помощью PVS-Studio. Но откровенно говоря, проект PVS-Studio не такой уж большой. И работа с кодом PVS-Studio по стилю и характеру отличается от, к примеру, работы с кодом Chromium или LLVM.

0264_Outsourcing_ru/image1.png

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

Ну да ладно, к чему это все? Наши теоретические желания попробовать себя в других проектах совпали с практическими предложениями, которые постепенно стали к нам поступать. В прошлом году мы решили выделить у нас в компании команду, которая бы занималась – о ужас! – разработкой на заказ. То есть участвовала в сторонних проектах в качестве программистов. Причем нам было интересно участвовать в долгосрочных и довольно крупных проектах, т.е. не менее 2-3 разработчиков и не менее 6 месяцев разработки. У нас было две цели:

  • попробовать альтернативный тип бизнеса (заказную разработку помимо продуктовой разработки);
  • самим посмотреть на использование PVS-Studio в долгосрочных проектах.

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

Замеченные интересные моменты

Сторонние проекты многому учат нас. Думаю, этой тематике мы посвятим далеко не одну статью. А сейчас мы начнём с некоторых интересных наблюдений. Мы заметили 3 яркие особенности кода, которые проявили себя в больших и старых проектах. Уверены, со временем будут публикации и про другие наблюдения.

У нас есть достаточно много статей, посвященных смене разрядности платформы с 32-бит на 64-бита. С портированием связано множество ошибок, которые начинают проявлять себя в 64-битных программах. Мы называем их 64-битные ошибки.

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

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

Примечание. Чтобы избежать ограничений NDA, мы изменили имена и отредактировали код. Собственно, это совсем не тот код, который был в программе. Однако, "всё основано на реальных событиях".

Изменение в поведении оператора new

Давным-давно оператор 'new' возвращал NULL в случае ошибки выделения памяти. Затем компиляторы начали поддерживать современный стандарт языка Си++, и бросать в таких ситуациях исключения std::bad_alloc. Можно заставить оператор 'new' возвращать NULL, но сейчас речь не об этом.

Видимо, эти изменения были замечены сообществом программистов весьма поверхностно. Взяли себе на заметку этот факт и начали писать код с учётом нового поведения. Есть, конечно, программисты, которые до сих пор не в курсе, что оператор 'new' кидает исключение. Но мы говорим о нормальных, адекватных программистах. Личности, которые ничего не хотят знать и продолжают писать в стиле, как делали это 20 лет назад, нам не интересны.

Тем не менее, даже те, кто знает, что оператор 'new' изменил своё поведение, не провели обзор существующего кода. Кто-то просто не сообразил это сделать. Кто-то хотел, но ему было некогда, а потом он забыл про это. В результате, в огромном количестве программ продолжают жить некорректные обработчики ситуаций, когда невозможно выделить память.

Некоторые фрагменты кода безобидны:

int *x = new int[N];
if (!x) throw MyMemoryException(); // 1
int *y = new int[M];
if (!y) return false; // 2

В первом случае вместо MyMemoryException будет сгенерировано исключение std::bad_alloc. Скорее всего, эти исключения обрабатываются одинаковым образом. Проблем нет.

Во втором случае проверка не сработает. Функция не вернёт значение 'false'. Вместо этого возникнет исключение, которое как-то будет потом обработано. Это ошибка. Поведение программы изменилось. Но на практике это почти никогда не приводит к проблемам. Просто программа немного по-разному будет реагировать на нехватку памяти.

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

Вот пара примеров, когда при нехватке памяти должны быть выполнены определённые действия:

image->SetBuffer(new unsigned char[size]);
if (!image->GetBuffer())
{
  CloseImage(image);
  return NULL;
}

FormatAttribute *pAttrib = new FormatAttribute(sName, value, false);
if (pAttrib )
{
    m_attributes.Insert(pAttrib);
}
else
{
  TDocument* pDoc = m_state.GetDocument();
  if (pDoc)
     pDoc->SetErrorState(OUT_OF_MEMORY_ERROR, true);
}

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

Рекомендация. Найдите в своей программе все операторы 'new'. Проверьте, не собирается ли программа предпринять какие-то действия, если указатель будет нулевым. Перепишите такие места.

Анализатор PVS-Studio помогает обнаружить бессмысленные проверки с помощью диагностики V668.

Замена char * на std::string

С переходом от Си к Си++ и с ростом популярности стандартной библиотеки, в программах стали широко использовать классы строк, такие как std::string. Это понятно и объяснимо. Проще и безопасней работать с полноценной строкой, а не с указателем "char *".

Класс строки стали использовать не только в новом коде, но и изменять некоторые старые фрагменты. Как раз с этим и могут возникать неприятности. Достаточно ослабить внимание, и код становится опасным или вовсе некорректным.

Но начнём не со страшного, а скорее забавного. Иногда попадаются вот такие неэффективные циклы:

for (i = 0; i < strlen(str.c_str()); ++i)

Явно, когда-то переменная 'str' была обыкновенным указателем. Плохо вызывать функцию strlen() при каждой итерации цикла. Это крайне неэффективно на длинных строках. Однако, после превращения указателя в std::string, код стал выглядеть ещё более глупо.

Сразу видно, что замена типов происходила бездумно. Это может приводить к подобному неэффективному коду или вообще к ошибкам. Поговорим про ошибки:

wstring databaseName;
....
wprintf("Error: cannot open database %s", databaseName); // 1

CString s;
....
delete [] s; // 2

В первом случае, указатель 'wchar_t *' превратился в 'wstring'. Беда возникнет, если не удастся открыть базу, и нужно будет распечатать сообщение. Код компилируется, но на экран будет распечатана абракадабра. Или программа вообще упадёт. Причина - забыли добавить вызов функции c_str(). Корректный вариант:

wprintf("Error: cannot open database %s", databaseName.c_str());

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

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

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

Замена char на wchar_t

Проект развивается, появляется желание сделать интерфейс приложения многоязычным. И вот в какой-то момент происходит массовая замена char на wchar_t. Поправлены ошибки, выданные компилятором. "Готова" unicode-версия приложения.

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

Как-же такое может быть? Очень просто. Многие фрагменты кода не готовы, что размер символа изменится. Код компилируется без ошибок и без предупреждений. Но работает только на "50%". Сейчас вы поймете, что мы имеем в виду.

Примечание. Напомню, что мы не запугиваем плохим кодом, полученным от студентов. Мы рассказываем о суровых реалиях программистского бытия. В больших и старых проектах неизбежно появляются такие ошибки. И их сотни. Серьёзно. Сотни.

Примеры:

wchar_t tmpBuffer[500];
memset(tmpBuffer, 0, 500); // 1

TCHAR *message = new TCHAR[MAX_MSG_LENGTH];
memset(charArray, 0, MAX_MSG_LENGTH*sizeof(char)); // 2

LPCTSTR sSource = ...;
LPTSTR sDestination = (LPTSTR) malloc (_tcslen(sSource) + 12); // 3

wchar_t *name = ...;
fprintf(fp, "%i : %s", i, name); // 4

В первом случае программист вообще не задумался, что размер символа со временем изменится. Поэтому очистил только половину буфера. Вот откуда мои слова про 50%.

Во втором случае программист догадывался, что размер символа изменится. Однако, подсказка "* sizeof(char)" никак не помогла при массовой замене типов. Надо было делать не так. Правильный вариант:

memset(charArray, 0, MAX_MSG_LENGTH * sizeof(charArray[0]));

Третий пример. С типами всё хорошо. Для подсчёта длины строки используется функция _tcslen(). На первый взгляд всё замечательно. Однако, когда символы стали иметь тип 'wchar_t', программа опять стала работать на 50%.

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

Четвёртый пример. Поправили функции printf(), sprintf() и так далее, но забыли проверить frintf(). В результате, в файл записывается мусор. Надо было сделать как-то так:

fwprintf(fp, L"%i : %s", i, name);

Или так, если это обыкновенный ASCII-файл:

fprintf(fp, "%i : %S", i, name);

Примечание. Сейчас подумал, что говорил про 50%, с точки зрения Windows. В Linux тип wchar_t занимает не 2, а 4 байта. Так что в Linux мире программа будет работать вообще только на 25% :).

Рекомендация. Если вы уже массово заменили char на wchar_t, не знаем, что может помочь. Этим можно было внести массу интересных ошибок. Внимательно просмотреть все места, где используется wcahr_t, не реально. Отчасти вам помогут статические анализаторы кода. Часть ошибок они выявят. Показанные выше ошибки найдет анализатор PVS-Studio. Последствия замены char на wchar_t весьма разнообразны и выявляются различными диагностиками. Перечислять их не имеет смысла. В качестве примера, можно назвать V512, V576, V635 и т.д.

Если вы ещё не делали замену, но планируете, то подойдите к ней со всей серьезностью. Заменить типы автоматически и поправить ошибки компиляции займет, например, 2 дня. Сделать те же замены вручную, с просмотром кода, займет на порядок больше времени. Например, вы потратите на это 2 недели. Но это лучше, чем потом втечение 2 лет вылавливать все новые ошибки:

  • неправильное форматирование строк;
  • выход за границы выделенных буферов памяти;
  • работа с буферами, очищенными на половину;
  • работа с буферами, скопированными на половину;
  • и так далее.

Заключение

Мы остались довольны своим опытом участия в сторонних заказных разработках, так как это позволило нам посмотреть на мир другими глазами. Мы продолжим свое участие в сторонних проектах в качестве разработчиков, так что, если кому интересно пообщаться с нами на эту тему, пишите. Если боитесь, что потом про вас будет разоблачительная статья (про найденные ошибки), то все-равно пишите – да поможет нам всем NDA!

Популярные статьи по теме
PVS-Studio для Java

Дата: 17 Янв 2019

Автор: Андрей Карпов

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

Дата: 20 Мар 2017

Автор: Андрей Карпов

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

Дата: 31 Май 2014

Автор: Андрей Карпов

Я изучил множество ошибок, возникающих в результате копирования кода. И утверждаю, что чаще всего ошибки допускают в последнем фрагменте однотипного кода. Ранее я не встречал в книгах описания этого …
Зло живёт в функциях сравнения

Дата: 19 Май 2017

Автор: Андрей Карпов

Возможно, читатели помнят мою статью под названием "Эффект последней строки". В ней идёт речь о замеченной мной закономерности: ошибка чаще всего допускается в последней строке однотипных блоков текс…
Как PVS-Studio оказался внимательнее, чем три с половиной программиста

Дата: 22 Окт 2018

Автор: Андрей Карпов

PVS-Studio, как и другие статические анализаторы кода, часто выдаёт ложные срабатывания. Но не стоит спешить считать странные срабатывания ложными. Это короткая история о том, как PVS-Studio вновь ок…
Любите статический анализ кода!

Дата: 16 Окт 2017

Автор: Андрей Карпов

Я в шоке от возможностей статического анализа кода, хотя сам участвую в разработке инструмента PVS-Studio. На днях я был искренне удивлён тому, что анализатор оказался умнее и внимательнее меня.
PVS-Studio ROI

Дата: 30 Янв 2019

Автор: Андрей Карпов

Время от времени нам задают вопрос, какую пользу в денежном эквиваленте получит компания от использования анализатора PVS-Studio. Мы решили оформить ответ в виде статьи и привести таблицы, которые по…
Главный вопрос программирования, рефакторинга и всего такого

Дата: 14 Апр 2016

Автор: Андрей Карпов

Вы угадали, ответ - "42". Здесь приводится 42 рекомендации по программированию, которые помогут избежать множества ошибок, сэкономить время и нервы. Автором рекомендаций выступает Андрей Карпов - тех…
Бесплатный PVS-Studio для тех, кто развивает открытые проекты

Дата: 22 Дек 2018

Автор: Андрей Карпов

В канун празднования нового 2019 года команда PVS-Studio решила сделать приятный подарок всем контрибьюторам open-source проектов, хостящихся на GitHub, GitLab или Bitbucket. Им предоставляется возмо…
Технологии, используемые в анализаторе кода PVS-Studio для поиска ошибок и потенциальных уязвимостей

Дата: 21 Ноя 2018

Автор: Андрей Карпов

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

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

Следующие комментарии

На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Этот сайт использует куки и другие технологии, чтобы предоставить вам более персонализированный опыт. Продолжая просмотр страниц нашего веб-сайта, вы принимаете условия использования этих файлов. Если вы не хотите, чтобы ваши данные обрабатывались, пожалуйста, покиньте данный сайт. Подробнее →
Принять