Не панацея, но помощник. О статическом анализаторе кода
Приветствую, уважаемые читатели Хабра!
Сегодня я хочу предложить вашему вниманию статью о статических анализаторах кода, о том, что это такое и для чего они, собственно, нужны.
Давайте перейдем сразу к сути. Итак, что же такое статический анализ? Попробуем разобраться быстро и на «на пальцах». Представьте, что вы пишете текст в Word, и он подчеркивает ошибки красной волнистой линией. Статический анализ — это примерно то же самое, только для кода. Специальная утилита анализирует исходный код еще до запуска программы и ищет в нем потенциальные ошибки, основываясь на определенных правилах. Обычно для этого используются плагины для IDE, например, Visual Studio, JetBrains, VS Code. Плагин запускает статический анализатор в фоне, анализатор выдает предупреждения, и разработчик может сразу перейти к проблемному месту в коде и во всем разобраться, опираясь на информацию в предупреждениях и подробную документацию.
Так это выглядит для пользователя. А как алгоритм понимает, что в коде ошибка, потенциальная уязвимость? Нужны миллионы паттернов плохого кода, с которыми сравнивать?
Исходный код — это просто набор символов. Чтобы его анализировать, нужно преобразовать его в более удобное представление, например, абстрактное синтаксическое дерево (AST). Сначала лексер разбивает код на токены (слова), затем по грамматическим правилам они объединяются в конструкции, и в итоге получается AST. Помимо этого, конечно, нужно понимать систему типов языка (что такое int, например) и отслеживать таблицу символов (какие функции, переменные объявлены).
Это базовый уровень, на котором уже можно создавать диагностические правила. Чаще всего они основаны на сопоставлении с шаблоном (pattern-based). Становится понятно, что определенные конструкции часто приводят к ошибкам (например, разыменование нулевого указателя, сравнение операнда самого с собой). На основе таких шаблонов можно сделать выводы о потенциальных ошибках.
На основе AST, системы типов и таблицы символов создаются диагностические правила. Помимо этого, используются и другие технологии: анализ потока данных (data flow analysis), анализа потока управления (control flow analysis) и т.д. Из всех этих «кирпичиков» складывается статический анализ.
Чем же он отличается от компилятора? Ведь фактически он делает то же самое, являясь своего рода фронтендом. А дело в том, что компилятор сосредоточен на оптимизации кода, на что уходит львиная доля времени. При этом оптимизация может приводить к неожиданным побочным эффектам, отследить которые бывает очень сложно. Статический анализ, в свою очередь, может не экономить время и тщательно анализировать все ветки кода, чуть больше уделить внимания функциям, выявить какие-нибудь интересные факты и потом выдать предупреждение, что через цепочку вызовов вы, например, разыменовали нулевой указатель или нулевую ссылку, в зависимости от языка программирования.
Кстати, часто анализаторы сами способны определить, какие файлы проверять. Сначала нужно получить представление о структуре проекта. Для Microsoft это MSBuild, для C/C++ — Compile Commands JSON, CMake и т.д. После этого берется список измененных файлов из коммита и на его основе определяется, что нужно анализировать. То есть плагин запускает анализ после сборки и проверяет только те файлы, которые были изменены. Конечно, если среди них есть header-файлы, то будет проанализировано больше.
Конечно, статические анализаторы могут выдавать и ложные срабатывания. Не получится проанализировать абсолютно все за разумное время, поэтому используются разные допущения, ограничивается глубина анализа и т.д.
Что же делать в таких ситуациях? Можно использовать комментарии, чтобы подавить предупреждение. У каждого анализатора свой синтаксис, но обычно достаточно добавить комментарий в строку с кодом. Еще есть механизм массового подавления предупреждений (baseline). Например, вы запустили анализатор для C/C++ проекта и получили 100 тысяч предупреждений. Очевидно, что этот «технический долг» нельзя исправить немедленно. Поэтому вы можете создать baseline и при следующем анализе увидите только новые предупреждения. То есть, можно просто «обнулить пробег».
Разумеется, разработчики статических анализаторов непрерывно борются с причинами ложных срабатываний. В этом помогают в том числе и пользователи, которые делятся такими кейсами, а также ошибками в работе сценариев анализатора посредством обратной связи. В том числе и на основании этих репортов анализаторы постоянно дорабатываются, «учатся» вылавливать все больше ошибок и становятся более универсальными.
И вот, размышляя о пользе статических анализаторов, напрашивается одна идея:, а что если внедрить статический анализ в репозитории вроде npm, Maven Central и другие? Представьте: перед публикацией библиотеки автоматически проверяются на известные уязвимости. Обнаружили устаревший log4j с дырой в безопасности, и разработчику сразу прилетает уведомление — обновить. Это значительно повысило бы безопасность open-source проектов.
Тот же GitHub мог бы стать отличной площадкой для реализации подобной функциональности. Представьте: вы ищете там проект, открываете вкладку Warnings и видите список потенциальных ошибок, найденных статическим анализом. У одного проекта много «звездочек», но и предупреждений куча, а у другого —«звездочек» меньше, зато код чистый. И сразу становится понятно, кто заботится о качестве. Конечно, GitHub вряд ли будет этим заниматься. Но это неплохая возможность для стартапов. Например, сервис, анализирующий репозитории по ссылке и предоставляющий подробный отчет, был бы очень востребован.
Ну и в конце своей статьи не могу обойти стороной популярную тему нейросетей. Готовые решения для анализа кода на их основе — это же мечта. Но сейчас все задачи, которые встают перед статическим анализом, в основном все же решаются классическими методами. Нейросети, разумеется, тоже могут применяться для поиска ошибок, но у такого подхода есть свои минусы. Во-первых, стандарты языков программирования постоянно меняются. Как обучать нейросеть чему-то, чего еще нет? Классические методы в этом плане гибче: вышел новый стандарт C++ — добавили новые правила, и все работает. Во-вторых, сложно связать нейросеть с документацией. Ее поведение постоянно меняется: сегодня она ищет один паттерн, завтра — другой.Кроме того, для обучения нейросети нужно очень много данных. Да, кто-то обучает нейросети прямо на коде. Но тут возникает проблема с семантикой: нейросеть ее не понимает. Если же находить способ передавать ей семантическую информацию, то результаты могут быть интересными. Вот так мечты о том, как заставить нейросеть работать вместо тебя, и разбиваются о суровую действительность…
Что ж, на этой мысли я, пожалуй, завершу статью и подведу итоги. Итак, статический анализ кода — это удобно. Такой инструмент экономит время и ресурсы, ошибки выявляются на ранних этапах. В целом нетрудно, наверное, даже провести расчеты и анализ экономической выгоды. С другой стороны, нужно понимать, что это все же не панацея, а помощники в разработке. Качественный код — это, в первую очередь, внимательность и профессионализм программиста.
Спасибо, что прочитали!
Пишите хороший код и не ленитесь делиться своими идеями и багрепортами с разработчиками инструментов. =)