Для получения триального ключа
заполните форму ниже
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Бесплатная лицензия PVS-Studio для специалистов Microsoft MVP
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Мне интересно попробовать плагин на:
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
Статический и динамический анализ кода

Статический и динамический анализ кода

13 Апр 2014

Мне, как разработчику инструмента статического анализа PVS-Studio, часто приходят предложения, реализовать в инструменте новую диагностику. Многие из предложений, опираются на опыт использования динамических анализаторов кода, например, Valgrind. К сожалению, часто это невозможно или почти невозможно. В этой статье я хочу кратко объяснить, почему статические анализаторы кода не могут делать то же самое, что динамические анализаторы и наоборот. Каждый из них силён в своё области. Один вид анализа не способен заменить другой, зато они отлично дополняют друг друга.

Статический анализ кода - это процесс выявления ошибок и недочетов в исходном коде программ. Статический анализ можно рассматривать как автоматизированный процесс обзора кода (code review).

Динамический анализ кода - это способ анализа программы непосредственно при ее выполнении.

Я часто слышу приблизительно следующую мысль:

У вас отличный инструмент. Но он не находит некоторые ошибки. Например, недавно Valgrind нашёл вот такую ошибку, а анализатор кода PVS-Studio промолчал. Было бы замечательно, добавить в PVS-Studio диагностику вот таких и таких ошибок. Тогда станет возможным использовать только один инструмент, что будет удобнее.

Да, пользоваться только одним инструментом, было бы удобнее и проще. К сожалению, статический анализатор, не может выполнять многие проверки, которые осуществляют динамические анализаторы. Это не значит, что статический анализ хуже. Это просто две разные технологии, дополняющие друг друга. Попробую объяснить имеющиеся у них ограничения.

Предположим, что есть функция вида:

void OutstandingIssue(const char *strCount)
{
  unsigned nCount;
  sscanf_s(strCount, "%u", &nCount);
  
  int array[10];
  memset(array, 0, nCount * sizeof(int));
}

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

Чтение строки из файла, это крайний случай. Облегчим задачу. Предположим, что строка формируется где-то в другой функции. Тогда, теоретически анализ возможен. На практике же это нереально сложно. Нужно понять, в какой последовательности будет выполняться код, понять какие значения могут принимать переменные, что будет записано в буферы в памяти, возможен ли вариант, когда возникнет переполнение.

Хранение данных в строке приведено, чтобы продемонстрировать, как не просто работать статическому анализатору. И таких сложностей при анализе возникает масса. Чем дальше от места вычисления какого-то значения происходит его использование, тем сложней анализ. Если место формирования строки отделено от её использования вызовами нескольких функций, то я не представляю насколько сложен должен быть анализатор и сколько памяти ему потребуется. Количество возможных ветвлений и значений переменных растёт невообразимо быстро. Чтобы найти такую ошибку придётся практически виртуально выполнить программу, причём во всех возможных вариантах ветвления. Нереально сложная алгоритмическая задача, которая в добавок потребует недостижимых вычислительных ресурсов.

И самое главное, преодолевать все эти сложности просто ненужно! То, что для статического анализа невообразимо сложно, легко решается динамическим анализатором. Динамический анализатор видит, что затирается маркер после массива, а значит произошел выход за границы массива.

Более того, динамический анализатор решит задачу выхода за границу массива, даже если строка прочитана из файла!

Означает ли это, что динамический анализ лучше? Быть может стоит лучше усовершенствовать динамический анализатор, чтобы он умел тоже самое, что и статический?

И вновь ответ: к сожалению, ничего не получится. Есть задачи, с которыми легко справляется статический анализ и которые неразрешимы при динамическом анализе.

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

Для динамического анализатора в приведённом ниже коде нет никаких проблем. Сравнивается часть буфера. Ничего подозрительного. Полным полно ситуаций, когда функция memcmp() сравнивает не весь выделенный буфер памяти, а только его часть. Это очень частое явление, когда используется только часть буфера. Ругаться динамическому анализатору здесь не на что.

А вот статический анализатор смотрит на код и понимает, что количество сравниваемых байт, скорее всего вычисляется неверно. Пример, взятый из реального open source проекта:

const unsigned char stopSgn[2] = {0x04, 0x66};
....
if (memcmp(stopSgn, answer, sizeof(stopSgn) != 0))
  return ERR_UNRECOGNIZED_ANSWER;

Ошибка в том, что не там поставлена скобка. Статический анализатор легко замечает аномалию в коде и сообщает об этом. Для динамического анализатора здесь всё корректно. Сравнивается один байт. Бывает. Сравнение только одного байта частая ситуация, особенно в коде, построенного на макросах.

Заключение. Мы рассмотрели два примера ошибок. Каждый из анализаторов способен обнаружить только одну из них в силу тех принципов, которые положены в основу их работы. Как бы не хотелось, обойтись только одним инструментом, это невозможно. Наилучший результат можно получить только применяя статический и динамический анализ совместно.

Дополнительные ссылки:

Последние статьи:

Опрос:

Популярные статьи по теме
Как увеличилась производительность LINQ в .NET 7?

Дата: 30 Ноя 2022

Автор: Михаил Евтихевич

В новой версии .NET улучшилась производительность методов Min, Max, Average и Sum для массивов и списков. Как вы думаете, во сколько раз увеличилась скорость их выполнения? В 2 раза, в 5? Нет, они ст…
Что такое катастрофический возврат и как регулярное выражение может стать причиной ReDoS-уязвимости?

Дата: 03 Ноя 2022

Автор: Андрей Москалёв

Регулярные выражения – очень полезный и удобный инструмент для поиска и замены текста. Однако в некоторых случаях они могут привести к зависанию системы или даже стать причиной уязвимости к ReDoS-ата…
Обзор нововведений в C# 11

Дата: 21 Окт 2022

Автор: Константин Волоховский

C# 11 выходит уже совсем скоро, так что пора детально изучить новые особенности, которые появятся в языке. И хотя их немного, среди них есть довольно интересные: обобщённая математика, исходные строк…
Есть ли жизнь без RTTI: пишем свой dynamic_cast

Дата: 13 Окт 2022

Автор: Владислав Столяров

В современном С++ осталось не так много вещей, которые не подходят под парадигму "Не плати за то, что не используешь". Одна из них – dynamic_cast. В рамках данной статьи мы разберёмся, что с ним не т…
Особенности реализации List в C#

Дата: 04 Окт 2022

Автор: Артём Ровенский

List является одной из самых популярных коллекций в C#. Давайте разберёмся в некоторых особенностях работы с ним и посмотрим на внутреннюю реализацию его отдельных частей.

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

Следующие комментарии
Unicorn with delicious cookie
Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо