Статический анализ защищает ваш код от бомб замедленного действия

0848_Timebomb_ru/image2.png
Статический анализ кода позволяет выявлять и устранять многие дефекты на раннем этапе. Более того, можно обнаружить спящие ошибки, которые в момент появления никак не проявляют себя. Они могут доставить массу проблем в будущем, потребовав для своего обнаружения многих часов отладки. Рассмотрим пример такой спящей ошибки.

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

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

void UI_but_drag_set_asset(uiBut *but,
                           const AssetHandle *asset,
                           const char *path,
                           int import_type,
                           int icon,
                           struct ImBuf *imb,
                           float scale)
{
  ....
  asset_drag->asset_handle = MEM_mallocN(sizeof(asset_drag->asset_handle),
                                         "wmDragAsset asset handle");
  *asset_drag->asset_handle = *asset;
  ....
}

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

Ошибка здесь:

sizeof(asset_drag->asset_handle)

Правильный вариант:

sizeof(*asset_drag->asset_handle)

Анализатор выявил эту ошибку, выдав предупреждение: V568: It’s odd that 'sizeof ()' operator evaluates the size of a pointer to a class, but not the size of the 'asset_drag→asset_handle' class object. interface.c 6192

Всё просто. Классический паттерн ошибки, с которым мы встречались в различных проектах. Интересно другое! Этот код сейчас работает правильно! Автору кода повезло. Посмотрим, что собой представляет структура AssetHandle:

typedef struct AssetHandle {
  const struct FileDirEntry *file_data;
} AssetHandle;

Структура сейчас содержит в себе ровно один указатель. Получается, что размер структуры совпадает с размером указателя!

Перед нами красивая бомба замедленного действия. Этот код будет надёжно и стабильно работать годами. Он будет полностью работоспособен до тех пор, пока кто-то не захочет добавить в структуру новое поле.

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

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

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

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Static analysis protects your code from time bombs.

© Habrahabr.ru