Главное предназначение статических анализаторов – найти те ошибки, которые остались незамеченными разработчиком. И недавно команда PVS-Studio снова столкнулась с интересным примером мощи этой методики. Работа с инструментами статического анализа кода требует внимательности. Часто код, на который указал анализатор, кажется корректным. В таких случаях хочется посчитать предупреждение ложным срабатыванием. На днях мы сами попали в такую ловушку. Вот как это получилось. Недавно мы доработали ядро анализатора, и коллега, который просматривал новые срабатывания, обнаружил среди них ложное. Он выписал срабатывание для тимлида, тот бегло просмотрел код и создал задачу. Задачу взял я. Таким нехитрым образом и образовалась цепочка из трёх программистов. Предупреждение анализатора: V645 The 'strncat' function call could lead to the 'а.consoleText' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. Фрагмент кода: Перед изучением примера давайте вспомним, что делает функция strncat: где: 'destination' — строка-получатель; 'source' — строка-источник; 'count' — максимальное число символов, которое можно добавить. На первый взгляд, кажется, что с кодом всё отлично. В нём вычисляется количество пустого места в буфере. При этом там даже запас в 4 байта вроде как есть... И именно из-за этого ощущения, что с кодом всё хорошо, он и был выписан как пример, где анализатор выдаёт ложное предупреждение. Давайте разберёмся, как обстоят дела на самом деле. В выражении: максимальное значение может быть достигнуто при минимальном значении второго операнда: Тогда результатом будет 507, и никакого переполнения не случится. О чём же тогда говорит PVS-Studio? Давайте немного углубимся во внутренние механизмы анализатора и попробуем разобраться. В статическом анализаторе за подсчёт таких выражений отвечает механизм анализа потока данных (data-flow). В большинстве случаев, когда выражение состоит из констант времени компиляции, data-flow вернёт строгое значение выражения. В остальных случаях, как и в случае с предупреждением, он сформирует только диапазон возможных значений выражения. В данном случае значение операнда strlen(a.consoleText) неизвестно нам на этапе компиляции. Давайте посмотрим диапазон. Спустя несколько минут отладки мы получаем от data-flow аж 2 диапазона: На первый взгляд, кажется, что второй диапазон лишний. Однако это не так. Просто мы забыли о том, что результатом выражения может стать отрицательное число. Например, такое может случиться при strlen(a.consoleText) = 508. В таком случае произойдет переполнение беззнакового числа, и результатом выражения будет максимальное значение результирующего типа, в данном случае — size_t. Получается, что анализатор прав! В данном выражении к полю consoleText может добавиться гораздо большее количество символов, чем его размер, что приведёт к переполнению буфера и, как следствие, неопределённому поведению. Причина неожиданного предупреждения – то, что ложного срабатывания тут нет! Вот так мы снова получили подтверждение, что ключевое преимущество статического анализа над человеком – его внимательность. Вдумчивое изучение предупреждений анализатора помогает разработчику сэкономить время при отладке и нервы, уберегает от ошибок и скоропалительных выводов. Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Vladislav Stolyarov. One Day in the Life of PVS-Studio Developer, or How I Debugged Diagnostic That Surpassed Three Programmers. Разработчик