Мы часто проверяем ретро-игры. Многие наши разработчики находят интересные для себя проекты и ностальгируют при их изучении. Однако, ретро-игры нужно на чём-то запускать. В этот раз мы проверили проект, помогающий запускать старые игры на современном железе.  DuckStation – это эмулятор консоли Sony PlayStation. Как видно по сайту, он имеет версию как для Windows и Linux, так и для смартфонов на Android. А недавно его запустили на Xbox Series X и S. Сам проект содержит чуть меньше миллиона строк исходного кода на языках С и С++. У DuckStation нет релизов, в него постоянно коммитят изменения, и поэтому для проверки пришлось зафиксировать SHA коммита: 13c5ee8. Мы проверили проект и обнаружили множество предупреждений (170 уровня High и 434 уровня Medium). Давайте разберём 10 самых интересных из них. Предупреждение N1 V726 An attempt to free memory containing the 'wbuf' array by using the 'free' function. This is incorrect as 'wbuf' was created on stack. log.cpp 216 Здесь анализатор обнаружил ошибочный код, в котором осуществляется попытка удаления массива, выделенного на стеке. Так как память не была выделена на куче, не стоит также и вызывать каких-либо специальных функций, таких как std::free для её очистки – она будет произведена автоматически при уничтожении объекта. Также в процессе написания статьи, коллега, который проверял её, посчитал срабатывание ложным. Этот интересный случай я описал в виде отдельной статьи, с которой приглашаю вас ознакомиться, она так и называется: История о том, как один разработчик защищал баг в проверяемом проекте от другого разработчика PVS-Studio. Предупреждение N2 V547 Expression 'i < pathLength' is always true. file_system.cpp 454 Индуктивная переменная i увеличивается уже после инициализации nextCh. Судя по тому, что, для определения длины строки используется функция strlen, строка Path нуль-терминирована. Тогда проверка i < pathLength явно лишняя, и её можно убрать, так как условие будет всегда верным. На последней итерации цикла мы получим нулевой символ и без неё. Тогда код: эквивалентен: Однако, даже если бы строка не заканчивалась терминальным нулём, проверка была бы неверной. На завершающей итерации цикла при попытке взять последний символ Path[i + 1] произойдет выход за границу массива. В таком случае надо было бы написать: Предупреждения N3, N4 На данный фрагмент кода анализатор выдал сразу два срабатывания: В этом операторе происходит сравнение значений от года до миллисекунд, но ошибка в коде видимо осталась ещё с того момента, когда сравнение заканчивалось на секундах. Забытое (или случайно поставленное) <= в проверке секунд приводит к тому, что последующие операции стали недостижимыми. Интересно, что данная ошибка была допущена ещё раз. Во второй раз это был подобный operator >=. На него анализатор тоже выдал два срабатывания: На тему функций сравнения, кстати, есть отличная статья моего коллеги, в которой он показывает разные паттерны ошибок такого типа. Предупреждение N5 V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value. gamelistmodel.cpp 415 Получилось два одинаковых сравнения. Операнды условного оператора, находящиеся по обе стороны от знаков больше и меньше, просто заменены местами в двух его ветвлениях. По сути, фрагмент кода в операторе return эквивалентен: Возможно, код должен был выглядеть так: Предупреждения N6, N7, N8 V501 There are identical sub-expressions 'c != ' '' to the left and to the right of the '&&' operator. file_system.cpp 560 В данном месте два раза происходит лишняя проверка на пробел. Также, было обнаружено ещё несколько подобных предупреждений: V501 There are identical sub-expressions to the left and to the right of the '|' operator: KMOD_LCTRL | KMOD_LCTRL sdl_key_names.h 271 Здесь, по обе стороны от знака | стоит одинаковое значение KMOD_LCTRL, выглядит подозрительно. V501 There are identical sub-expressions 'TokenMatch(command, "CATALOG")' to the left and to the right of the '||' operator. cue_parser.cpp 196 Тут происходит два одинаковых вызова функции TokenMatch. Интересно, что, в проверке ниже, также есть ошибка: command записан как строковый литерал вместо переменной. Кстати, мы давно собирались сделать диагностическое правило, которое позволит отслеживать такие ситуации, а этот фрагмент кода – один из показателей, что оно будет полезно. Возможно, на месте избыточных проверок, во всех этих случаях, должны были быть проверки на другие значения и фрагменты кода работают не совсем так, как ожидают программисты, написавшие их. Предупреждение N9 V1065 Expression can be simplified, check 'm_display_texture_height' and similar operands. host_display.cpp 549 Да, тут нет ошибки, просто код можно немного сократить, упростив выражение: Сказать по правде, это не серьёзное предупреждение и его не следовало бы добавлять в статью. Однако, я добавил, просто потому что, это моя диагностика и мне приятно, что она сработала:) Предупреждение N10 V614 The 'host_interface' smart pointer is utilized immediately after being declared or reset. It is suspicious that no value was assigned to it. main.cpp 45 Диагностика говорит об использовании неинициализированной переменной. Здесь происходит бессмысленная проверка умного указателя. Первая проверка: !host_interface всегда будет возвращать true.  Казалось бы, ошибка не очень критичная и избыточный код написан для поддержания общей стилистики, однако его можно переписать так, чтоб он стал ещё читабельнее: Кажется, что мы превратили один return в четыре и код должен работать медленнее, однако я написал похожий синтетический пример. Как можно видеть, под оптимизациями O2 компиляторы Сlang 13 и GCC 11.2 генерируют меньше ассемблерных инструкций для второго примера (особенно это видно для GCC). Несмотря на то, что проект небольшой, мы смогли найти несколько интересных срабатываний. Надеюсь, что благодаря статье, разработчики DuckStation смогут исправить некоторые недочёты, а может быть захотят самостоятельно перепроверить свою кодовую базу PVS-Studio. А если вы хотите попробовать PVS-Studio на своём проекте, то вы можете сделать это, скачав его здесь. Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Vladislav Stolyarov. How a PVS-Studio developer defended a bug in a checked project. Разработчик