>
>
Как же программа работает со всеми этим…

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

Как же программа работает со всеми этими ошибками?

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

Введение

Пара вводных слов для читателей, ещё не знакомых с нашим инструментом. Мы разрабатываем анализатор PVS-Studio для нахождения ошибок в исходном коде приложений, написанных на C/C++. Самый лучший способ показать, что он умеет, это проверять открытые проекты и находить в них ошибки. Найденные ошибки мы собираем в базу. Если в проекте мы находим интересные на наш взгляд ошибки, то пишем статью. Всем желающим предлагаю взглянуть на обновляемый список статей.

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

Самое важное

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

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

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

Если ошибка зависит от входных данных, то ей уже легче спрятаться. Представим, что программист разрабатывает графический редактор. Он протестировал работу приложения на картинках с разрешением 100x100 и 300x400. И хотя он написал плохой код, у него всё работало. К счастью в компании есть отдел тестирования, в котором заметили, что программа не работает с вытянутыми картинками разрешением 100x10000. Обратите внимание, что ошибка прожила немного дольше.

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

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

Ответ на вопрос

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

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

QV4::ReturnedValue
QQuickJSContext2DPrototype::method_getImageData(....)
{
  ....
  qreal x = ctx->callData->args[0].toNumber();
  qreal y = ctx->callData->args[1].toNumber();
  qreal w = ctx->callData->args[2].toNumber();
  qreal h = ctx->callData->args[3].toNumber();
  if (!qIsFinite(x) || !qIsFinite(y) ||
      !qIsFinite(w) || !qIsFinite(w))
  ....
}

Функция содержит ошибку. Не проверяется значение 'h'. Вместо этого дважды проверяется переменная 'w'. Это ошибка? Да, это ошибка. Но вероятность, что она проявит себя весьма мала.

Во-первых, не во всех приложениях, использующих библиотеку Qt, будет вызывается функция method_getImageData(). Скорее всего её вообще мало кто использует. Во-вторых, ошибка проявит себя только если будет неправильно конвертирован последний аргумент. Все остальные аргументы обрабатываются правильно. В-третьих, этот последний аргумент должен оказаться некорректно заданным. Таким образом вероятность что эта ошибка проявится себя очень мала.

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

Подведём итог.

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

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

То есть ошибки есть. Их даже много (проверка N1, N2, N3, N4). Но, запуская Chromium, вы вряд ли с ними столкнётесь. Нужно будет приложить усилие и, возможно, очень большое, чтобы суметь попасть в ветвь кода, содержащую ошибку.

PVS-Studio не нужен?

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

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

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

Самое плохое, что можно делать, это прогонять анализатор незадолго до релиза. В этом нет никакого прока. Огромное количество ошибок, которые мог бы найти анализатор, к этому моменту будут исправлены ценой пота и крови. Эти неэффективно (пример, как зря потратить 50 часов времени). А потом после бессонных ночей в отладчике, переписываний с тестерами, разработчики запускают анализатор и получают всего пару полезных сообщений. Ведь всё самое страшное они исправили самостоятельно, потратив массу времени и сил. Зачем? Это какой-то Epic Fail.

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

Я не говорю, что анализатор может найти все ошибки. Он найдет только часть. Но зато сделает это сразу. Как только программист закончит писать очередной фрагмент кода.

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

PVS-Studio нужен!

Чтобы окончательно вас убедить, я приведу один практический пример. Недавно мы проверяли и исправляли все ошибки в игровом движке Unreal Engine. Большинство ошибок было некритичными. Это естественно. Было бы много критичных ошибок, никто бы не стал использовать Unreal Engine в своих разработках.

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

static void GetArrayOfSpeakers(....)
{
  Speakers.Reset();
  uint32 ChanCount = 0;
  // Build a flag field of the speaker outputs of this device
  for (uint32 SpeakerTypeIndex = 0;
       SpeakerTypeIndex < ESpeaker::SPEAKER_TYPE_COUNT,    // <=
       ChanCount < NumChannels; ++SpeakerTypeIndex)
  {
    ....
  }
  check(ChanCount == NumChannels);
}

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

Желающим поподробнее узнать историю нашей работы с компанией Epic Games предлагаю познакомиться со статьёй "Как команда PVS-Studio улучшила код Unreal Engine".

Заключение

Предлагаю не откладывать и попробовать наш анализатор кода PVS-Studio на своём проекте. Вы можете скачать его здесь. Интерфейс анализатора весьма прост, но предлагаю взглянуть на статью "PVS-Studio для Visual C++". Возможно, вы почерпнете для себя полезные советы по использованию анализатора. Например, многие не догадываются как легко и быстро можно исключить из результатов анализа сторонние библиотеки.

Если вы используете для сборки makefile или свою собственную сборочную систему, то проверить проект вам поможет PVS-Studio Standalone. В этом инструменте имеется механизм отслеживания запусков компилятора.

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

Желаю удачи!