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

Стати́ческий ана́лиз ко́да () — анализ программного обеспечения, производимый (в отличие от динамического анализа) без реального выполнения исследуемых программ. В большинстве случаев анализ производится над какой-либо версией исходного кода, хотя, иногда анализу подвергается какой-нибудь вид объектного кода, например P-код или код на MSIL. Термин обычно применяют к анализу, производимому специальным программным обеспечением (ПО), тогда как ручной анализ называют «program understanding», «program comprehension» (пониманием или постижением программы).

В зависимости от используемого инструмента глубина анализа может варьироваться от определения поведения отдельных операторов до анализа, включающего весь имеющийся исходный код. Способы использования полученной в ходе анализа информации также различны — от выявления мест, возможно содержащих ошибки (утилиты типа Lint), до формальных методов, позволяющих математически доказать какие-либо свойства программы (например, соответствие поведения спецификации).

Некоторые люди считают программные метрики и обратное проектирование формами статического анализа. Получение метрик () и статический анализ часто совмещаются, особенно при создании встраиваемых систем.

Принципы статического анализа 

Большинство компиляторов (например, GNU C Compiler) выводят на экран «предупреждения» () — сообщения о том, что код, будучи синтаксически правильным, скорее всего, содержит ошибку. Например:

int x;
int y = x+2;    // Переменная x не инициализирована!

Это простейший статический анализ. У компилятора есть много других немаловажных характеристик — в первую очередь скорость работы и качество машинного кода, поэтому компиляторы проверяют код лишь на очевидные ошибки. Статические анализаторы предназначены для более детального исследования кода.

Типы ошибок, обнаруживаемых статическими анализаторами 
 Неопределённое поведение — неинициализированные переменные, обращение к NULL-указателям. О простейших случаях сигнализируют и компиляторы.
 Нарушение алгоритма пользования библиотекой. Например, для каждого fopen нужен fclose. И если файловая переменная теряется раньше, чем файл закрывается, анализатор может сообщить об ошибке.
 Типичные сценарии, приводящие к недокументированному поведению. Стандартная библиотека языка Си известна большим количеством неудачных технических решений. Некоторые функции, например, gets, в принципе небезопасны. sprintf и strcpy безопасны лишь при определённых условиях.
 Переполнение буфера — когда компьютерная программа записывает данные за пределами выделенного в памяти буфера.
void doSomething(const char* x)
{
    char s[40];
    sprintf(s, "[%s]", x);    // sprintf в локальный буфер, возможно переполнение
    ....
}
 Типичные сценарии, мешающие кроссплатформенности.
Object *p = getObject();
int pNum = reinterpret_cast<int>(p);    // на x86-32 верно, на x64 часть указателя будет потеряна; нужен intptr_t
 Ошибки в повторяющемся коде. Многие программы исполняют несколько раз одно и то же с разными аргументами. Обычно повторяющиеся фрагменты не пишут с нуля, а размножают и исправляют.
dest.x = src.x + dx;
dest.y = src.y + dx;  // Ошибка, надо dy!
 Ошибки форматных строк — в функциях наподобие printf могут быть ошибки с несоответствием форматной строки реальному типу параметров.
std::wstring s;
printf ("s is %s", s);
 Неизменный параметр, передаваемый в функцию — признак изменившихся требований к программе. Когда-то параметр был задействован, но сейчас он уже не нужен. В таком случае программист может вообще избавиться от этого параметра — и от связанной с ним логики.
void doSomething(int n, bool flag)   // flag всегда равен true
{
   if (flag)
   {
       // какая-то логика
   } else
   {
       // код есть, но не задействован
   }
}

doSomething(n, true);
...
doSomething(10, true);
...
doSomething(x.size(), true);
 Утечки памяти и других ресурсов. В целом статические анализаторы проигрывают в сфере поиска утечек динамическим анализаторам кода.
Traverser *t = new Traverser(Name);
if (!t->Valid())
{
  return FALSE; // Случайно написали return до delete.
  delete t;
}
 Прочие ошибки — многие функции из стандартных библиотек не имеют побочного эффекта, и вызов их как процедур не имеет смысла.
std::string s;
...
s.empty();     // код ничего не делает; вероятно, вы хотели s.clear()?

Применение 
В последнее время статический анализ всё больше используется в верификации свойств ПО, используемого в компьютерных системах высокой надёжности, особенно критичных для жизни (). Также применяется для поиска кода, потенциально содержащего уязвимости (иногда это применение называется Static Application Security Testing, SAST).

Статический анализ постоянно применяется для критического ПО в следующих областях:
 ПО для медицинских устройств.
 ПО для атомных станций и систем защиты реактора (Reactor Protection Systems)
 ПО для авиации (в комбинации с динамическим анализом)
 ПО на автомобильном или железнодорожном транспорте

По данным VDC на 2012 год, примерно 28 % разработчиков встраиваемого ПО применяют средства статического анализа, а 39 % собираются начать их использование в течение 2 лет.

Формальные методы

Инструменты статического анализа 

Инструменты по анализируемым языкам, некоторый из которых выделяет CISO CLUB:

C/C++:
 BLAST
 Clang Static Analyzer (встроен в Clang)
 Coverity
 PC-Lint
 lint и lock_lint, входящие в состав Sun Studio
 Cppcheck (Cppcheck on sf)
 
Solar appScreener
 SourceAnalyzer (также Fortran и x86 asm)
 PVS-Studio
 
 
 
 
Visual Code Grepper
C#:

 CodeCrawler
 Visual Code Grepper
 Code Warrior
 PVS-Studio

Java:

fbinfer
FindBugs (FindBugs on sf)
PVS-Studio
 
 
Solar appScreener
YASCA
Code Warrior
JavaScript:

 JsHint
 Code Warrior

.NET:
 .NET Compiler Platform (Roslyn) — фреймворк компилятора для C# и VB.NET, предоставляющий интерфейс для анализатора.
 FxCop
Microsoft FxCop
 NDepend
 PVS-Studio
 ReSharper
 StyleCop
YASCA

PHP:

 Graudit

Solar appScreener
YASCA
Visual Code Grepper
Code Warrior

Python:
 Flake8
Graudit

 Pylint
 McCabe
 Pyflakes
 Pycodestyle
Solar appScreener
YASCA
Ruby:

 Brakeman
 Code Warrior

Другие:
T-SQL Analyzer — инструмент, который может просматривать программные модули в базах данных под управлением Microsoft SQL Server 2005 или 2008 и обнаруживать потенциальные проблемы, связанные с низким качеством кода.
 АК-ВС 2 от  ЗАО "НПО "Эшелон" (Поиск НДВ, выявление опасных шаблонов по )
 SonarQube — платформа анализа и управления качеством кода с поддержкой различных языков программирования через систему плагинов.
 AppChecker — коммерческий статический анализатор кода от "НПО "ЭШЕЛОН", предназначенный для автоматизированного поиска дефектов в исходном коде приложений, разработанных на С#, C/C++, Java, PHP.
 Svace — инструмент статического анализа, разработанный в ИСП РАН. Поддерживает языки программирования C/C++, Java, C#.

См. также 
 Формальная верификация
 Тестирование программного обеспечения
 
 MISRA C

Примечания

Ссылки 
 О безошибочных программах // «Открытые системы», № 07, 2004
 Первые шаги к решению проблемы верификации программ // «Открытые системы», № 08, 2006
 Статический анализ безопасности кода // Программная инженерия и информационная безопасность. 2013 № 1, стр 50 (npo-echelon.ru)
 http://www.drdobbs.com/testing/deploying-static-analysis/240003801
 Коллекция ошибок, обнаруженных в Open Source проектах с помощью статического анализа кода // PVS-Studio (viva64.com)

Тестирование программного обеспечения
Исследование программ
Формальные методы
Статические анализаторы кода
Статьи с примерами кода C++