Здорово, когда энтузиастам-разработчикам удаётся сделать работающий клон известной игры. Ещё лучше, когда находятся люди, готовые продолжить развитие таких проектов! В этой статье с помощью PVS-Studio мы проверим TheXTech – открытую реализацию игры по вселенной Super Mario.  TheXTech – это полностью переписанный на C++ игровой движок SMBX 1.3. Оригинальный SMBX (Super Mario Bros. X) был написан на Visual Basic 6 Эндрю Спинксом в 2009 году. Он позволял создавать уровни из элементов игр Nintendo серии Super Mario Bros. TheXTech умеет достаточно точно воспроизводить поведение оригинала с опционально включаемыми исправлениями его багов. Может запускаться не только под Windows, но и на macOS, Linux-системах с процессорами x86, ARM или PowerPC, также его портируют на 3DS и PS Vita. Разработчик TheXTech – Виталий Новичков (Wohlstand) – подробно описал процесс разработки на Хабре и то, какие техники можно использовать для сглаживания различий при переводе с языка VB6 на C++. На странице GitHub опубликован дисклеймер (англ), поясняющий, почему исходный код находится не в самом лучшем состоянии: потому что оригинал состоит из лютого спагетти-кода, фрагменты которого мы сейчас и увидим. Фрагмент N1 Итак, можно ли здесь увидеть ошибку, которую нашёл анализатор? V547 Expression 'NPC[A].Type == 54 && NPC[A].Type == 15' is always false. Probably the '||' operator should be used here. thextech npc_update.cpp 1277  Конечно же, нет :) Она находится посреди строки с условием длиной 1400 символов, и вам придётся пролистать 5 экранов вправо чтобы до неё добраться. Переформатируем код для наглядности: Разумеется, переменная NPC[A].Type не может одновременно равняться двум разным значениям. По всей видимости, логика условия должна была выполняться в том числе и для снарядов с типами 54 и 15, но сейчас эта часть условия всегда ложна. По всей видимости, нужно поменять оператор логического И на оператор логического ИЛИ либо удалить эту часть выражения. Ещё несколько примеров ошибок в слишком длинных строках: Фрагмент N2 Следующий фрагмент кода уже был форматирован для чтения человеком. Впрочем, несмотря на больший шанс заметить здесь ошибки, они остались. Причём в количестве 4 штук:  Здесь нет смысла дважды проверять одни и те же значения и лишние сравнения можно удалить. Далее можно обойтись без скриншотов. Фрагмент N3 V501 There are identical sub-expressions '(evt.AutoSection) >= (0)' to the left and to the right of the '&&' operator. thextech layers.cpp 568 В этом примере анализатор смутило дублирование выражений, получившееся в результате раскрытия макросов: Подобные срабатывания можно подавлять или переписать условие как-то так: Также на эту строку сработала диагностика V590. V590 Consider inspecting this expression. The expression is excessive or contains a misprint. thextech layers.cpp 568 Устранение этих предупреждений не исправит какие-то баги, а компиляторы и так могут позаботиться о вырезании лишних инструкций, но таким образом можно помочь почистить код. На мой взгляд, интересный момент можно найти в issues из ссылки в комментарии из примера. Там пользователь с ником ds-sloth предложил исправить строчку: на Это исправление чинит управление автопрокруткой уровня через внутриигровые события:  Однако этот фикс по умолчанию остался в выключенном состоянии, поскольку меняет или ломает оригинальное поведение игры: Поэтому в некоторых случаях исправление ошибок, которые показывает анализатор, требует дополнительного обдумывания, поскольку это может нарушить багосовместимость :), например, в следующих примерах. Фрагмент N4 V501 There are identical sub-expressions to the left and to the right of the '!=' operator: NPC[A].Projectile != NPC[A].Projectile thextech npc_hit.cpp 2105 В этом условии сравнивается набор полей между объектами NPC[A] и oldNPC. Посередине поле Projectile сравнивается у NPC[A] само с собой. Очень похоже на неаккуратный копипаст. Классика. Однако, к чему может привести исправление этого условия, можно узнать только тестированием или полным пониманием игровой логики. Возможно, там просто лишняя проверка. Аналогичная ошибка: Фрагмент N5 И последняя на сегодня ошибка V501: V501 There are identical sub-expressions 'MenuMode == MENU_SELECT_SLOT_1P_DELETE' to the left and to the right of the '||' operator. thextech menu_main.cpp 1004 Здесь не совсем понятно, должно ли право удалять слот сохранения быть только у первого игрока. В таком случае дополнительная проверка на MENU_SELECT_SLOT_1P_DELETE здесь лишняя. Константа MENU_SELECT_SLOT_2P_DELETE в коде существует и, может, она должна была использоваться в правой части выражения. Чуть ниже в блоке этого условия есть такое же срабатывание: Фрагмент N6 V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 1561, 1570. thextech player_update.cpp 1561 В этом фрагменте последовательно выполняют проверки несколько конструкций else-if с одинаковым условием (Player[A].Character == 3). Это приводит к недостижимому коду во второй конструкции else-if. Похоже на то, что этот код не даёт принцессе Пич летать в каких-то случаях. Стоит попробовать убрать лишнюю ветку и просто присвоить число 280 в переменную Player[A].FlyCount. Фрагмент N7 В следующем примере находится подозрительное дублирование кода в then и else ветках условия: V523 The 'then' statement is equivalent to the 'else' statement. thextech npc_hit.cpp 1546 Возможно, здесь не работает какое-то специальное исключение при определении, может ли снаряд убить NPC конкретного типа. Фрагмент N8 В одном из срабатываний попалось невыполнимое условие: V547 Expression 'A == 48' is always false. thextech effect.cpp 1652 Поскольку в этот блок программа может зайти, только если переменная A равна 16, условие 'A == 48' не выполнится. В результате у эффекта будет установлена неправильная вертикальная скорость, и Гигантский Пуля Билл погибнет недостаточно драматично :) Фрагмент N9 Ещё один пример бесполезного условного оператора: V547 Expression 'tempPlayer == 0' is always true. thextech blocks.cpp 576 Видимо, в ходе рефакторинга переменная tempPlayer стала всегда инициализироваться нулём. Можно уменьшить вложенность конструкций, убрав лишнее условие. Фрагмент N10 А здесь лишняя проверка на то, что логический результат сравнения не равен 0: V562 It's odd to compare a bool type value with a value of 0. thextech editor.cpp 102 Можно написать просто: Ещё такие же срабатывания: Фрагмент N11 В следующем примере может содержаться логическая ошибка. В условии сначала проверяется значение в массиве по индексу whatPlayer и только после этого проверяется диапазон переменной whatPlayer: V781 The value of the 'whatPlayer' index is checked after it was used. Perhaps there is a mistake in program logic. thextech blocks.cpp 159 Это может привести к неопределённому поведению. Фрагмент N12 Немного странный код, в котором после комментирования переменной стало присваиваться её же текущее значение: V570 The 'NPC[A].Location.X' variable is assigned to itself. thextech npc_hit.cpp 1995 В принципе поведение программы от таких выражений не страдает. Но подобный код может свидетельствовать о появлении логических ошибок, например, если после отладки забыли вернуть закомментированный фрагмент на место. Ещё примеры лишнего присваивания: Фрагмент N13 Странный цикл в функции, которая пытается прочитать JPEG изображение: V654 The condition 'chunk_size > 0' of loop is always true. thextech image_size.cpp 211 Переменная chunk_size обновляется практически в самом начале итерации цикла и, если она оказывается равной 0, цикл завершается. После этого она дойдёт до проверки условия выхода из цикла, при этом гарантированно будет больше нуля. Здесь можно просто использовать бесконечный цикл 'while (true)'. Фрагмент N14 В этом выражении использовали оператор побитового ИЛИ вместо логического между вызовами функций, возвращающих bool. В результате всегда будут выполнены обе функции, что менее эффективно: V792 The 'vScreenCollision' function located to the right of the operator '|' will be called regardless of the value of the left operand. Perhaps, it is better to use '||'. thextech gfx_update.cpp 1007 Такая же ошибка встречается ещё в нескольких местах: Фрагмент N15 В следующем примере зачем-то лишний раз конструируют строку, передавая результат вызова метода c_str() в функцию, которая принимает ссылку на std::string. Такой код менее эффективен, поскольку при преобразовании из std::string в char* теряется информация о текущей длине строки. И при последующем конструировании нового std::string длину придётся пересчитать линейным поиском терминального нуля. Причём этот момент компилятором не оптимизируется (проверено через Clang с -O3 оптимизациями). V811 Decreased performance. Excessive type casting: string -> char * -> string. Consider inspecting first argument of the function open_file. thextech graphics_funcs.cpp 63 Фрагмент N16 В этом цикле многократно вызывается расчёт длины одних и тех же строк. Стоит объявить их как константы типа std::string и пользоваться методом size(): V814 Decreased performance. The 'strlen' function was called multiple times inside the body of a loop. thextech menu_main.cpp 1027 И такой паттерн встречается достаточно часто: Если верить Википедии, первый публичный релиз TheXTech был выпущен всего через месяц после публикации оригинальных исходников SMBX. Это, на мой взгляд, очень круто для полноценного кроссплатформенного перевода проекта на другой язык программирования (особенно на C++). В случае если планируется капитальная переработка кода, возможно, стоит начать использовать PVS-Studio, ведь мы даём бесплатную лицензию для open-source проектов. В качестве бонуса – видео с нашего YouTube канала на тематику Марио: Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Alexey Govorov. Thanks, Mario, but the code needs fixing — checking TheXTech.