RPCS3 – интересный проект, который эмулирует консоль PS3. Он активно развивается: недавно была новость о том, что он научился запускать все игры из своего каталога. Это хороший повод для проверки – посмотрим, какие ошибки остались после исправлений.  Проект довольно увесистый. Он насчитывает около 300 тысяч строк кода на C++ и к тому же тянет за собой большое количество внешних зависимостей, в числе которых есть: Для GUI части также понадобится Qt, однако он берётся из системы. Полный список зависимостей можно посмотреть на скриншоте:  Что примечательно, используемый стандарт языка C++ – свежий C++20. PVS-Studio хорошо справился с проверкой такого современного кода, ведь мы активно работаем над поддержкой нововведений. Да, есть некоторые недоработки, но мы их постепенно исправим. В целом проверка проекта послужила хорошим тестом поддержки новых языковых конструкций. Проект использует сборочную систему CMake. К сожалению, во время сборки у меня возникли проблемы – GCC 11.2 отказывался компилировать какую-то constexpr-конструкцию, однако Clang 12 справился со сборкой отлично. Сборку я производил под devel-версией Ubuntu, так что, возможно, это проблема дистрибутива. Полная процедура сборки и проверки под Linux в режиме межмодульного анализа выглядит так: Что же, проект проверен – время перейти к ошибкам! V1061 Extending the 'std' namespace may result in undefined behavior. shared_ptr.hpp 1131 Стандарт C++ явно запрещает определение пользовательских шаблонов функций в пространстве имён std, а C++20 также запрещает определение специализаций шаблонов функций. Определение пользовательской функции swap – частый случай такой ошибки. В этой ситуации следует поступать таким образом: При таком подходе работает механизм Argument-Dependent Lookup (ADL), в результате чего находится функция swap, которую мы определили рядом с классом. А пространство имён std не изменено. V597 The compiler could delete the 'memset' function call, which is used to flush 'cty' object. The memset_s() function should be used to erase the private data. aes.cpp 596 Частая ошибка – вызов memset будет удалён компилятором во время оптимизаций, а приватные данные останутся лежать в памяти. Да, в случае кода эмулятора вряд ли это представляет какую-либо угрозу утечки данных, но тем не менее ошибка присутствует. Также были найдены ещё случаи такой ошибки: V547 Expression 'rawcode == CELL_KEYC_KPAD_NUMLOCK' is always false. cellKb.cpp 126 Здесь ошибка кроется в первом условии: оно перекрывает условие ниже, которое проверяет значение переменной rawcode на равенство константе CELL_KEYC_KPAD_NUMLOCK. Значение CELL_KEYC_KPAD_NUMLOCK соответствует числу 0x53 – это число попадает под первое условие, которое в результате выполнения выходит из функции, так что нижний if никогда не выполнится. Ошибка может быть в двух местах – либо в первом условии не учтено значение константы, либо сама константа определена неверно. V557 Array underrun is possible. The value of 'month + — 1' index could reach -1. cellRtc.cpp 1470 В этом случае в операторе return обращение к массиву DAYS_IN_MONTH может произойти по индексу -1, т.к. значение аргумента month может быть равно 0. Скорее всего, ошибка находится в первом условии – судя по коду, отсчет месяцев ведётся с единицы, а условие проверяет на то, что month меньше 0, когда надо month < 1. Эта ошибка напомнила мне интересный случай из проекта protobuf: 31 февраля. V519 The 'evnt->color.white_x' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 51, 52. sys_uart.cpp 52 Классическая ошибка: при написании функции скопировали строку и забыли в ней поменять нужную переменную. Глазами заметить ошибку здесь сложно, но статический анализатор отлично справляется в таких случаях. V581 The conditional expressions of the 'if' statements situated alongside each other are identical. Check lines: 4225, 4226. PPUTranslator.cpp 4226 Похоже на ещё одну ошибку копипасты – видимо, скопировали условие и забыли его поменять, однако then-часть теперь другая. Что интересно, это не единственный случай такой ошибки. В коде было ещё одно срабатывание: V560 A part of conditional expression is always true: i != 1. PPUTranslator.cpp 4252 Проверка переменной i на равенство 1 и 2 никогда не выполнится – цикл работает с числами от 16 до 20. Возможно, этот код переписывался и индексы забыли поменять на корректные. V595 The 'cached_dest' pointer was utilized before it was verified against nullptr. Check lines: 3059, 3064. texture_cache.h 3059 Ещё один частый паттерн – сначала используется указатель, а затем проверяется. Опять же, возможно, что ошибка появилась в процессе изменений этого фрагмента кода. V668 There is no sense in testing the 'movie' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. movie_item.h 56 Проверка на nullptr здесь бессмысленна: при вызове new в случае ошибки будет брошено исключение std::bad_alloc. Если бросать исключение не требуется, то следует использовать конструкцию std::nothrow – тогда как раз вернётся нулевой указатель. Ещё места с подобной ошибкой: V773 The function was exited without releasing the 'buffer' pointer. A memory leak is possible. rsx_debugger.cpp 380 В начале функции видим выделение памяти через malloc, и если вернулся nullptr, то выходим. Пока всё хорошо. Но дальше идут проверки параметров width и height – они происходят уже после выделения памяти, и в случае успеха из функции также возвращается nullptr. Да, при равенстве этих переменных нулю malloc будет выделять 0 байт, однако стандарт говорит, что в этом случае может вернуться как nullptr, так и валидный указатель, который нельзя разыменовывать. Но освободить его все равно нужно, к тому же, free способна принимать и нулевой указатель. Так что исправление может выглядеть таким образом: Либо проверки на 0 можно вообще убрать – цикл в этом случае не выполнится: V557 Array overrun is possible. The 'pad' index is pointing beyond array bound. pad_thread.cpp 191 При проверке входных данных использован оператор > вместо >=, в результате чего значение pad может быть равно размеру контейнера m_pads, поэтому при дальнейшем обращении к контейнеру есть вероятность выхода за границу. V547 Expression 'current_version < threshold_version' is always false. Unsigned type value is never < 0. device.cpp 91 Константа threshold_version всегда будет равна 0, т.к. вместо сдвига влево используется сдвиг вправо. Сдвиг вправо эквивалентен делению на степень двойки – в нашем случае на 2^22 и 2^14 соответственно. Очевидно, что числа из выражений меньше этих степеней, поэтому результат будет равен 0. Похоже, что это место было скопировано из кода декодирования значения версии, но при копировании поменять операторы просто забыли. В результате проверки анализатор нашёл в проекте ошибки разного рода: традиционно были выявлены опечатки, а также есть логические ошибки, где код просто не тестировался. Надеемся, что эта проверка поможет исправить пару багов, а также желаем разработчикам эмулятора хорошей поддержки игр и отличной производительности. А вы можете попробовать триальную версию анализатора PVS-Studio и посмотреть, какие ошибки вы найдёте у себя. А если вы разрабатываете какую-то открытую игру, эмулятор либо просто open-source проект, то рассмотрите вариант бесплатного лицензирования. Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Sergey Larin. PVS-Studio to check the RPCS3 emulator.