Blender commit

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


Каждый день система для регулярного мониторинга проекта Blender присылает мне отчёт о потенциальных ошибках в новом или изменённом коде. Описывать каждый новый баг и писать 100500 заметок про это неинтересно, но сегодня особый случай.


Статический анализатор кода PVS-Studio использует различные технологии для выявления ошибок и потенциальных уязвимостей.


Суть технологии символьного выполнения в том, чтобы вычислять выражения, не зная значений переменных. Звучит таинственно, но сейчас мы разберём это на практическом примере, и всё сразу станет понятно. Рассмотрим вот этот коммит в проекте Blender.


PVS-Studio выдаёт предупреждение на 868-ую строчку кода:


memset(&path->ptr[i], 0, sizeof(path->ptr[i]) * (path->len - i));

Анализатор считает подозрительным, что функция memset на самом деле не заполняет память:


[CWE-628] V575: The 'memset' function processes '0' elements. Inspect the third argument.


Давайте разберёмся, как он пришёл к такому выводу.


Анализатор не знает, какие числовые значения могут храниться в переменной path->len. Он знает про эту переменную кое-что другое, но про это позже.


Про переменную i известно чуть больше.


for (int i = 0; i < path->len; i++) {
  ....
  if (i != 0) {
    ....
    memset(&path->ptr[i], 0, sizeof(path->ptr[i]) * (path->len - i));

Из этого кода анализатор может извлечь следующую информацию:


  1. Переменная i меньше path->len. Это известно из анализа цикла.
  2. Переменная i больше 0. Это следует из инициализации этой переменной в цикле и последующей проверки на неравенство нулю.

Итого, значения переменной i лежат в диапазоне от 1 до path->len.


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


Анализатор видит, что перед вызовом функции memset значение path->len меняется следующим образом:


path->len = i;
if (i != 0) {
  memset(&path->ptr[i], 0, sizeof(path->ptr[i]) * (path->len - i));

Переменная path->len равна значению i. Поэтому, даже не зная диапазоны возможных значений переменных, можно вычислить выражение. Для этого анализатор делает подстановку:


sizeof(path->ptr[i]) * (i - i)

И получает 0 в качестве третьего аргумента функции:


sizeof(path->ptr[i]) * 0

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


Примечание. Поскольку в статье приводится только маленькая часть кода, то присваивание path->len = i может показаться вообще очень странным. Ведь это означает, что цикл всегда завершается после первой итерации. На самом деле, рассмотренный в статье фрагмент находится под условиями, и такой код имеет смысл. Вы можете сами изучить тело цикла целиком.


Предыдущие публикации:


  1. Как PVS-Studio защищает от поспешных правок кода, пример N4.
  2. Как PVS-Studio защищает от поспешных правок кода, пример N3.
  3. Как PVS-Studio защищает от поспешных правок кода, пример N2.
  4. Как PVS-Studio защищает от поспешных правок кода.
  5. PVS-Studio, Blender: цикл заметок о пользе регулярного использования статического анализа.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. How PVS-Studio prevents rash code changes, example N5.

Комментарии (0)