Понятие кода анализируется в исследованиях. Использование статического и динамического анализа для повышения качества продукции и эффективности разработки. Объединяя лучшее из двух миров

С оригиналом статьи можно познакомится, воспользовавшись Wayback Machine - Internet Archive: Static Code Analysis .

Поскольку все статьи на нашем сайте представлены на русском и английском языке, то мы выполнили перевод статьи Static Code Analysis на русский язык. А заодно решили опубликовать её на Хабре. Здесь уже публиковался пересказ этой статьи . Но уверен, многим будет интересно прочитать именно перевод.

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

Сразу нужно заметить, что нельзя все сводить к качеству, и признаться в этом вовсе не означает предать какие-то свои моральные принципы. Ценность имеет создаваемый вами продукт в целом, а качество кода - лишь один из ее компонентов наравне со стоимостью, функциональными возможностями и прочими характеристиками. Миру известно множество супер-успешных и уважаемых игровых проектов, напичканных багами и без конца падающих; да и глупо было бы подходить к написанию игры с той же серьезностью, с какой создают ПО для космических шаттлов. И все же качество - это, несомненно, важный компонент.

Я всегда старался писать хороший код. По своей натуре я похож на ремесленника, которым движет желание непрерывно что-то улучшать. Я прочел груды книг со скучными названиями глав типа «Стратегии, стандарты и планы качества», а работа в Armadillo Aerospace открыла мне дорогу в совершенно иной, отличный от предшествующего опыта мир разработки ПО с повышенными требованиями к безопасности.

Более десяти лет назад, когда мы занимались разработкой Quake 3, я купил лицензию на PC-Lint и пытался применять его в работе: привлекала идея автоматического обнаружения дефектов в коде. Однако необходимость запуска из командной строки и просмотра длинных списков диагностических сообщений отбили у меня охоту пользоваться этим инструментом, и я вскоре отказался от него.

С тех пор и количество программистов, и размер кодовой базы выросли на порядок, а акцент в программировании сместился с языка C на C++. Все это подготовило намного более благодатную почву для программных ошибок. Несколько лет назад, прочитав подборку научных статей о современном статическом анализе кода, я решил проверить, как изменилось положение дел в этой области за последние десять лет с тех пор, как я попробовал работать с PC-Lint.

На тот момент код у нас компилировался на 4-ом уровне предупреждений, при этом выключенными мы оставляли лишь несколько узкоспециальных диагностик. С таким подходом - заведомо рассматривать каждое предупреждение, как ошибку - программисты были вынуждены неукоснительно придерживаться данной политики. И хотя в нашем коде можно было отыскать несколько пыльных уголков, в которых с годами скопился всякий «мусор», в целом он был довольно современным. Мы считали, что у нас вполне неплохая кодовая база.

Coverity

Началось все с того, что я связался с Coverity и подписался на пробную диагностику нашего кода их инструментом. Это серьезная программа, стоимость лицензии зависит от общего количества строк кода, и мы остановились на цене, выраженной пятизначным числом. Показывая нам результаты анализа, эксперты из Coverity отметили, что наша база оказалась одной из самых чистых в своей «весовой категории» из всех, что им доводилось видеть (возможно, они говорят это всем клиентам, чтобы приободрить их), однако отчет, который они нам передали, содержал около сотни проблемных мест. Такой подход сильно отличался от моего предыдущего опыта работы с PC-Lint. Соотношение сигнал/шум в данном случае оказался чрезвычайно высок: большинство из выданных Coverity предупреждений действительно указывали на явно некорректные участки кода, которые могли иметь серьезные последствия.

Этот случай буквально открыл мне глаза на статический анализ, но высокая цена всего удовольствия некоторое время удерживала от покупки инструмента. Мы подумали, что в оставшемся до релиза коде у нас будет не так много ошибок.

Microsoft /analyze

Не исключено, что я, в конце концов, решился бы купить Coverity, но пока я размышлял над этим, Microsoft пресекли мои сомнения, реализовав новую функцию /analyze в 360 SDK. /Analyze прежде был доступен в качестве компонента топовой, безумно дорогой версии Visual Studio, а потом вдруг достался бесплатно каждому разработчику под xbox 360. Я так понимаю, о качестве игр на 360-й платформе Microsoft печется больше, чем о качестве ПО под Windows. :-)

С технической точки зрения анализатор Microsoft всего лишь проводит локальный анализ, т.е. он уступает глобальному анализу Coverity, однако когда мы его включили, он вывалил горы сообщений - намного больше, чем выдал Coverity. Да, там было много ложных срабатываний, но и без них нашлось немало всяких страшных, по-настоящему жутких бяк.

Я потихоньку приступил к правке кода - прежде всего, занялся своим собственным, затем системным, и, наконец, игровым. Работать приходилось урывками в свободное время, так что весь процесс затянулся на пару месяцев. Однако эта задержка имела и свой побочный полезный эффект: мы убедились, что /analyze действительно отлавливает важные дефекты. Дело в том, что одновременно с моими правками наши разработчики устроили большую многодневную охоту за багами, и выяснилось, что каждый раз они нападали на след какой-нибудь ошибки, уже помеченной /analyze, но еще не исправленной мной. Помимо этого, были и другие, менее драматичные, случаи, когда отладка приводила нас к коду, уже помеченному /analyze. Все это были настоящие ошибки.

В конце концов, я добился, чтобы весь использованный код скомпилировался в запускаемый файл под 360-ю платформу без единого предупреждения при включенном /analyze, и установил такой режим компиляции в качестве стандартного для 360-сборок. После этого код у каждого программиста, работающего на той же платформе, всякий раз при компиляции проверялся на наличие ошибок, так что он мог сразу править баги по мере их внесения в программу вместо того, чтобы потом ими занимался я. Конечно, из-за этого процесс компиляции несколько замедлился, но /analyze - однозначно самый быстрый инструмент из всех, с которыми мне доводилось иметь дело, и, поверьте мне, оно того стоит.

Однажды мы в каком-то проекте случайно выключили статический анализ. Прошло несколько месяцев, и когда я заметил это и снова включил его, инструмент выдал кучу новых предупреждений об ошибках, внесенных в код за это время. Подобным же образом программисты, работающие только под PC или PS3, вносят в репозиторий код с ошибками и пребывают в неведении, пока не получат письмо с отчетом о «неудачной 360-сборке». Эти примеры наглядно демонстрируют, что в процессе своей повседневной деятельности разработчики раз за разом совершают ошибки определенных видов, и /analyze надежно уберегал нас от большей их части.

PVS-Studio

Поскольку мы могли использовать /analyze только на 360-коде, большой объем нашей кодовой базы по-прежнему оставался не покрытым статическим анализом - это касалось кода под платформы PC и PS3, а также всех программ, работающих только на PC.

Следующим инструментом, с которым я познакомился, был PVS-Studio . Он легко интегрируется в Visual Studio и предлагает удобный демо-режим (попробуйте сами!). В сравнении с /analyze PVS-Studio ужасно медлителен, но он сумел выловить некоторое количество новых критических багов, причем даже в том коде, который был уже полностью вычищен с точки зрения /analyze. Помимо очевидных ошибок PVS-Studio отлавливает множество других дефектов, которые представляют собой ошибочные программистские клише, пусть и кажущиеся на первый взгляд нормальным кодом. Из-за этого практически неизбежен некоторый процент ложных срабатываний, но, черт возьми, в нашем коде такие шаблоны нашлись, и мы их поправили.

На сайте PVS-Studio можно найти большое количество замечательных статей об инструменте, и многие из них содержат примеры из реальных open-source проектов, иллюстрирующие конкретно те виды ошибок, о которых идет речь в статье. Я думал, не вставить ли сюда несколько показательных диагностических сообщений, выдаваемых PVS-Studio, но на сайте уже появились намного более интересные примеры. Так что посетите страничку и посмотрите сами. И да - когда будете читать эти примеры, не надо ухмыляться и говорить, что вы бы так никогда не написали.

PC-Lint

В конце концов, я вернулся к варианту с использованием PC-Lint в связке с Visual Lint для интеграции в среду разработки. В соответствии с легендарной традицией мира Unix инструмент можно настроить на выполнение практически любой задачи, однако интерфейс его не очень дружественен и его нельзя просто так «взять и запустить». Я приобрел набор из пяти лицензий, но его освоение оказалось настолько трудоемким, что, насколько я знаю, все остальные разработчики от него в итоге отказались. Гибкость действительно имеет свои преимущества - так, например, мне удалось настроить его для проверки всего нашего кода под платформу PS3, хотя это и отняло у меня немало времени и усилий.

И снова в том коде, который был уже чист с точки зрения /analyze и PVS-Studio, нашлись новые важные ошибки. Я честно старался вычистить его так, чтобы и lint не ругался, но не удалось. Я поправил весь системный код, но сдался, когда увидел, сколько предупреждений он выдал на игровой код. Я рассортировал ошибки по классам и занялся наиболее критичными из них, игнорируя массу других, относящихся больше к стилистических недоработкам или потенциальным проблемам.

Я полагаю, что попытка исправить громадный объем кода по максимуму с точки зрения PC-Lint заведомо обречена на провал. Я написал некоторое количество кода с нуля в тех местах, где послушно старался избавиться от каждого назойливого «линтовского» комментария, но для большинства опытных C/C++-программистов такой подход к работе над ошибками - уже чересчур. Мне до сих пор приходится возиться с настройками PC-Lint, чтобы подобрать наиболее подходящий набор предупреждений и выжать из инструмента максимум пользы.

Выводы

Я немало узнал, пройдя через все это. Боюсь, что кое-какие из моих выводов будут с трудом восприняты людьми, которым не приходилось лично разбирать сотни сообщений об ошибках в сжатые сроки и всякий раз чувствовать дурноту, приступая к их правке, и стандартной реакцией на мои слова будет «ну у нас-то все в порядке» или «все не так плохо».

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

Автоматизация необходима. Когда видишь сообщения о чудовищных сбоях в автоматических системах, невозможно не испытать эдакое злорадство, однако на каждую ошибку в автоматизации приходится легион ошибок человеческих. Призывы к тому, чтобы «писать более качественный код», благие намерения о проведении большего числа сеансов инспекции кода, парного программирования и так далее просто-напросто не действуют, особенно когда в проект вовлечены десятки людей и работать приходится в дикой спешке. Громадная ценность статического анализа заключается в возможности при каждом запуске находить хотя бы небольшие порции ошибок, доступных этой методике.

Я обратил внимание, что с каждым обновлением PVS-Studio находил в нашем коде все новые и новые ошибки благодаря новым диагностикам. Отсюда можно заключить, что при достижении кодовой базой определенного размера в ней, похоже, заводятся все допустимые с точки зрения синтаксиса ошибки. В больших проектах качество кода подчиняется тем же статистическим закономерностям, что и физические свойства вещества - дефекты в нем распространены повсюду, и вы можете только стараться свести к минимуму их воздействие на пользователей.

Инструменты статического анализа вынуждены работать «с одной рукой, связанной за спиной»: им приходится делать выводы на основе разбора языков, которые вовсе не обязательно предоставляют информацию для таких выводов, и в целом делать очень осторожные предположения. Поэтому вы должны помогать своему анализатору, насколько возможно - отдавать предпочтение индексации перед арифметикой с указателями, держать граф вызовов в едином исходном файле, использовать явные аннотации и т.п. Все, что может показаться статическому анализатору неочевидным, почти наверняка собьет с толку и ваших коллег-программистов. Характерное «хакерское» отвращение к языкам со строгой статической типизацией («bondage and discipline languages») на деле оказывается недальновидным: потребности крупных, долгоживущих проектов, в разработку которых вовлечены большие команды программистов, кардинально отличаются от мелких и быстрых задач, выполняемых для себя.

Нулевые указатели - это самая насущная проблема в языке C/C++, по крайней у нас. Возможность двойственного использования единого значения в качестве как флага, так и адреса приводит к невероятному числу критических ошибок. Поэтому всегда, когда для этого есть возможность, в C++ следует отдавать предпочтение ссылкам, а не указателям. Хотя ссылка «на самом деле» есть ни что иное как тот же указатель, она связана неявным обязательством о невозможности равенства нулю. Выполняйте проверки указателей на ноль, когда они превращаются в ссылки - это позволит вам впоследствии забыть о данной проблеме. В сфере игростроения существует множество глубоко укоренившихся программистских шаблонов, несущих потенциальную опасность, но я не знаю способа, как полностью и безболезненно перейти от проверок на ноль к ссылкам.

Второй по важности проблемой в нашей кодовой базе были ошибки с printf-функциями. Она дополнительно усугублялась тем, что передача idStr вместо idStr::c_str() практически каждый раз заканчивалась падением программы. Однако, когда мы стали использовать аннотации /analyze для функций с переменным количеством аргументов, чтобы поверки типов выполнялись корректно, проблема была решена раз и навсегда. В полезных предупреждениях анализатора мы встречали десятки таких дефектов, которые могли привести к падению, случись какому-нибудь ошибочному условию запустить соответствующую ветку кода - это, между прочим, говорит еще и том, как мал был процент покрытия нашего кода тестами.

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

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

Если вас не напугали до глубины души все те дополнительные трудности, которые несет в себе параллельное программирование, вы, похоже, просто не вникли в этот вопрос как следует.

Невозможно провести достоверные контрольные испытания при разработке ПО, но наш успех от использования анализа кода был настолько отчетливым, что я могу позволить себе просто заявить: не использовать анализ кода - безответственно! Автоматические консольные логи о падениях содержат объективные данные, которые ясно показывают, что Rage, даже будучи по многим показателям первопроходцем, оказался намного стабильнее и здоровее, чем большинство самых современных игр. Запуск Rage на PC, к сожалению, провалился - готов поспорить, что AMD не используют статический анализ при разработке своих графических драйверов.

Вот вам готовый рецепт: если в вашей версии Visual Studio есть встроенный /analyze, включите его и попробуйте поработать так. Если бы меня попросили выбрать из множества инструментов один, я бы остановился именно на этом решении от Microsoft. Всем остальным, кто работает в Visual Studio, я советую хотя бы попробовать PVS-Studio в демо-режиме. Если вы разрабатываете коммерческое ПО, приобретение инструментов статического анализа будет одним из лучших способов вложения средств.

И напоследок комментарий из твиттера.


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

Обзор кода (code review) – один из самых старых и надежных методов выявления дефектов. Он заключается в совместном внимательном чтении исходного кода и высказывании рекомендаций по его улучшению. В процессе чтения кода выявляются ошибки или участки кода, которые могут стать ошибочными в будущем. Также считается, что автор кода во время обзора не должен давать объяснений, как работает та или иная часть программы. Алгоритм работы должен быть понятен непосредственно из текста программы и комментариев. Если это условие не выполняется, то код должен быть доработан.

Как правило, обзор кода хорошо работает, так как программисты намного легче замечают ошибки в чужом коде. Более подробно с методикой обзора кода можно познакомиться в замечательной книге Стива Макконнелла "Совершенный код" (Steve McConnell, "Code Complete") .

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

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

Задачи, решаемые программами статического анализа кода можно разделить на 3 категории:

  • Выявление ошибок в программах. Подробнее про это будет рассказано ниже.
  • Рекомендации по оформлению кода. Некоторые статические анализаторы позволяют проверять, соответствует ли исходный код, принятому в компании стандарту оформления кода. Имеется в виду контроль количества отступов в различных конструкциях, использование пробелов/символов табуляции и так далее.
  • Подсчет метрик . Метрика программного обеспечения - это мера, позволяющая получить численное значение некоторого свойства программного обеспечения или его спецификаций. Существует разнообразных метрик, которые можно подсчитать, используя те ли иные инструменты.

Другие преимущества статического анализа кода:

  • Полное покрытие кода. Статические анализаторы проверяют даже те фрагменты кода, которые получают управление крайне редко. Такие участки кода, как правило, не удается протестировать другими методами. Это позволяет находить дефекты в обработчиках редких ситуаций, в обработчиках ошибок или в системе логирования.
  • Статический анализ не зависит от используемого компилятора и среды, в которой будет выполняться скомпилированная программа. Это позволяет находить скрытые ошибки, которые могут проявить себя только через несколько лет. Например, это ошибки . Такие ошибки могут проявить себя при смене версии компилятора или при использовании других ключей для оптимизации кода. Другой интересный пример скрытых ошибок приводится в статье " ".
  • Можно легко и быстро обнаруживать опечатки и . Как правило, нахождение этих ошибок другими способами является кране неэффективной тратой времени и усилий. Обидно после часа отладки обнаружить, что ошибка заключается в выражении вида "strcmp(A, A)". Обсуждая типовые ошибки, про такие ляпы, как правило, не вспоминают. Но на практике на их выявление тратится существенное время.

Недостатки статического анализа кода

  • Статический анализ, как правило, слаб в диагностике утечек памяти и параллельных ошибок. Чтобы выявлять подобные ошибки, фактически необходимо виртуально выполнить часть программы. Это крайне сложно реализовать. Также подобные алгоритмы требуют очень много памяти и процессорного времени. Как правило, статические анализаторы ограничиваются диагностикой простых случаев. Более эффективным способом выявления утечек памяти и параллельных ошибок является использование инструментов динамического анализа.
  • Программа статического анализа предупреждает о подозрительных местах. Это значит, что на самом деле код, может быть совершенно корректен. Это называется ложно-позитивными срабатываниями. Понять, указывает анализатор на ошибку или выдал ложное срабатывание, может только программист. Необходимость просматривать ложные срабатывания отнимает рабочее время и ослабляет внимание к тем участкам кода, где в действительности содержатся ошибки.

Ошибки, обнаруживаемые статическими анализаторами весьма разнообразны. Вот, например, которые реализованы в инструменте PVS-Studio. Некоторые анализаторы специализируются на определенной области или типах дефектов. Другие, поддерживают определенные стандарты кодирование, например MISRA-C:1998, MISRA-C:2004, Sutter-Alexandrescu Rules, Meyers-Klaus Rules и так далее.

Доработка бизнес-приложений мировых разработчиков (CRM, ERP, биллинг и др.) с учётом особенностей и специфических требований рабочих процессов компании и разработка собственных бизнес-приложений с нуля — стандарт для российских предприятий. При этом в процессе внедрения и эксплуатации бизнес-приложений возникают угрозы информационной безопасности, связанные с ошибками программирования (переполнение буфера, неинициализированные переменные и др.) и наличием функций, которые приводят к обходу встроенных механизмов защиты. Такие функции встраиваются на этапе тестирования и отладки приложения, но по причине отсутствия процедур контроля безопасности кода остаются в рабочей версии бизнес-приложения. Также нельзя гарантировать, что на этапе создания бизнес-приложения разработчики умышленно не встроят дополнительный код, приводящий к наличию недекларированных возможностей (программных закладок).

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

ЭЛВИС-ПЛЮС предлагает систему анализа исходных кодов на основе продуктов мирового лидера в этой области — HP Fortify Static Code Analyzer (SCA) , а также российских компаний — InfoWatch Appercut и Positive Technologies Application Inspector .

Цели создания Системы
  • Снижение уровня рисков прямых финансовых потерь и репутационных рисков, реализующихся вследствие атак на бизнес-приложения, содержащие ошибки или закладки.
  • Повышение уровня защищённости корпоративной информационной системы посредством организации контроля, автоматизации и централизации управления процессами анализа исходного кода бизнес-приложений.
Задачи, решаемые Системой
  • Статистический анализ программного обеспечения на наличие ошибок/уязвимостей, производимый без выполнения приложений на уровне исходных кодов.
  • Динамический анализ программного обеспечения на наличие ошибок/уязвимостей, производимый после сборки и запуска приложения.
  • Реализация технологий безопасной разработки бизнес-приложений во время создания и на протяжении всего жизненного цикла приложения путем встраивания Системы в среду разработки приложений.
Архитектура и основные функции системы (на примере продукта HP Fortify SCA)

Статический анализ, также известный как статическое тестирование безопасности приложений (SAST - Static Application Security Testing):

  • Определяет потенциальные уязвимости приложения непосредственно на уровне кода.
  • Помогает идентифицировать проблемы ещё в процессе разработки.

Динамический анализ, также известный как динамическое тестирование безопасности приложений (DAST - Dynamic Application Security Testing):

  • Обнаруживает уязвимости в запущенных веб-приложениях и веб-сервисах путём моделирования полноценных сценариев атак.
  • Проверяет, можно ли на практике использовать конкретную уязвимость.
  • Ускоряет реализацию корректирующих мер, позволяя понять, какие проблемы необходимо решить в первую очередь и почему.

Система на базе HP Fortify Software Security Center позволяет реализовать автоматизированные процессы обнаружения, приоритизации, устранения, отслеживания, проверки и управления уязвимостями в ПО бизнес-приложений. Инструменты HP Fortify могут встраиваться в интегрированные среды разработки (IDE - Integrated Development Environment), средства контроля качества (QA - Quality assurance) и системы отслеживания ошибок.

Основные функции Системы
  • Внедрение и автоматизация процесса обнаружения уязвимостей прикладного ПО (ППО) на различных стадиях разработки (создание, отладка, тестирование, эксплуатация, модификация).
  • Обнаружение уязвимостей в развернутых веб-приложениях на различных стадиях разработки ППО (создание, отладка, тестирование, эксплуатация, модификация).
  • Настройка условий и критериев обнаружения уязвимостей в ППО.
  • Поддержка современных языков программирования для определения угроз безопасности ППО (C/C++, Java, JSP, ASP, .NET, JavaScript, PHP, COBOL, PL/SQL, ABAP, VB6 и др.).
  • Поддержка различных инструментов разработки для интеграции со средой разработки ППО.
  • Формирование отчётов о проблемах безопасности проверяемого ППО с ранжированием найденных уязвимостей по степени риска.
  • Отправка практических рекомендаций по устранению обнаруженных уязвимостей в коде в среду разработки ППО.
  • Обновление базы уязвимостей для определения актуальных угроз безопасности ППО на основе предоставляемой вендором информации.
Преимущества от внедрения Системы
  • Снижение затрат на разработку, тестирование и исправление ошибок в бизнес-приложениях.
  • Снижение затрат на восстановление работоспособности взломанных бизнес-приложений.
  • Повышение эффективности работы подразделения, ответственного за обеспечение ИБ в компании.

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

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

Некоторые люди считают программные метрики и обратное проектирование формами статического анализа.

В последнее время статический анализ всё больше используется в верификации свойств ПО, используемого в компьютерных системах высокой надёжности.

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

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 часть указателя будет потеряна; нужен size_t

  • Ошибки в повторяющемся коде. Многие программы исполняют несколько раз одно и то же с разными аргументами. Обычно повторяющиеся фрагменты не пишут с нуля, а размножают и исправляют.

Dest.x = src.x + dx; dest.y = src.y + dx; // Ошибка, надо dy!

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 ) ;

Std:: string s; ... s .empty () ; // код ничего не делает; вероятно, вы хотели s.clear()?

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

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

  • Coverity
  • lint и lock_lint, входящие в состав Sun Studio
  • T-SQL Analyzer - инструмент, который может просматривать программные модули в базах данных под управлением Microsoft SQL Server 2005 или 2008 и обнаруживать потенциальные проблемы, связанные с низким качеством кода.
  • АК-ВС

См. также

  • Формальная семантика ЯП
  • Анализ программного обеспечения
  • Постепенная деградация
  • SPARK - ЯП

Примечания

Ссылки


Wikimedia Foundation . 2010 .

Смотреть что такое "Статический анализ кода" в других словарях:

    - (англ. Dynamic program analysis) анализ программного обеспечения, выполняемый при помощи выполнения программ на реальном или виртуальном процессоре (анализ, выполняемый без запуска программ называется статический анализ кода). Утилиты… … Википедия

    Анализ потока управления это статический анализ кода для определения порядка выполнения программы. Порядок выполнения выражается в виде графа потока управления. Для многих языков граф потока управления явно прослеживается в исходном коде… … Википедия

    У этого термина существуют и другие значения, см. BLAST (значения). BLAST Тип Инструменты статического анализа Разработчик Dirk Beyer, Thomas Henzinger, Ranjit Jhala, Rupak Majumdar, Berkeley Операционная система Linux, Microsoft Windows… … Википедия

    В следующие таблицы включены пакеты программ, которые являются интегрированными средствами разработки. Отдельные компиляторы и отладчики не упомянуты. Возможно, в английском разделе есть более свежая информация. Содержание 1 ActionScript 2 Ада 3 … Википедия

    Отладка этап разработки компьютерной программы, на котором обнаруживают, локализуют и устраняют ошибки. Чтобы понять, где возникла ошибка, приходится: узнавать текущие значения переменных; выяснять, по какому пути выполнялась… … Википедия

    Тип Статический анализатор кода Разработчик лаборатория BiPro Написана на С++ Операционная система Кроссплатформенное Языки интерфейса английский … Википедия


Аннотация

Статический анализ - это способ проверки исходного кода программы на корректность. Процесс статического анализа состоит из трех этапов. Сначала анализируемый код разбивается на лексемы - константы, идентификаторы, и т. д. Эта операция выполняется лексером. Затем лексемы передаются синтаксическому анализатору, который выстраивает по этим лексемам дерево кода. Наконец, проводится статический анализ построенного дерева. В данной обзорной статье приведено описание трех методов статического анализа: анализ с обходом дерева кода, анализ потока данных и анализ потока данных с выбором путей.

Введение

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

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

Статический анализ позволяет проверять исходный код программы до ее выполнения. В частности, любой компилятор проводит статический анализ при компиляции. Однако, в больших реальных проектах зачастую возникает необходимость проверить весь код на предмет соответствия некоторым дополнительным требованиям. Эти требования могут быть весьма разнообразны, начиная от правил именования переменных и заканчивая мобильностью (например, код должен благополучно выполняться на платформах х86 и х64). Наиболее распространенными требованиями являются:

  • Надежность - меньшее количество ошибок в тестируемой программе.
  • Удобство сопровождения - более понятный код, который легко изменять и усовершенствовать.
  • Мобильность - гибкость тестируемой программы при запуске на различных платформах.
  • Удобочитаемость - сокращение времени, необходимого для понимания кода.

Требования можно разбить на правила и рекомендации. Правила, в отличие от рекомендаций, обязательны для выполнения. Аналогом правил и рекомендаций являются ошибки и предупреждения, выдаваемые анализаторами кода, встроенными в стандартные компиляторы.

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

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

Процесс анализа

Процесс статического анализа состоит из двух основных шагов: создания дерева кода (также называемого ) и анализа этого дерева.

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

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

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

Рассмотрим примерный алгоритм определения типа лексемы.

Если первый символ лексемы является цифрой, лексема считается числом, если этот символ является знаком "минус", то это - отрицательное число. Если лексема является числом, она может быть числом целым или дробным. Если в числе содержится буква E, определяющая экспоненциальное представление, или десятичная точка, число считается дробным, в противном случае - целым. Заметим, что при этом может возникнуть лексическая ошибка - если в анализируемом исходном коде содержится лексема "4xyz", лексер сочтет ее целым числом 4. Это породит синтаксическую ошибку, которую сможет выявить синтаксический анализатор. Однако подобные ошибки могут обнаруживаться и лексером.

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

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

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

В то время как лексер понимает лишь синтаксис языка, синтаксический анализатор также распознает и контекст. Например, объявим функцию на языке Си:

Int Func(){return 0;}

Лексер обработает эту строку и разобьет ее на лексемы как показано в таблице 1:

Таблица 1 - Лексемы строки "int Func(){return 0};".

Строка будет распознана как 8 корректных лексем, и эти лексемы будут переданы синтаксическому анализатору.

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

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

Однако процесс построения дерева кода не сводится к простому представлению лексем в виде дерева. Рассмотрим этот процесс подробнее.

Дерево кода

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

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

При разработке вершин дерева в первую очередь обычно определяется уровень модульности. Иными словами, определяется, будут ли все конструкции языка представлены вершинами одного типа, различаемыми по значениям. В качестве примера рассмотрим представление бинарных арифметических операций. Один вариант - использовать для всех бинарных операций одинаковые вершины, одним из атрибутов которых будет тип операции, например, "+". Другой вариант - использовать для разных операций вершины различного типа. В объектно-ориентированном языке это могут быть классы вроде AddBinary, SubstractBinary, MultipleBinary, и т. п., наследуемые от абстрактного базового класса Binary.

В качестве примера разберем два выражения: 1 + 2 * 3 + 4 * 5 и 1+ 2 * (3 + 4) * 5 (см. рисунок 1).

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

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

Методы статического анализа

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

Хотя все три перечисленные выше метода статического анализа используют дерево кода, построенное синтаксическим анализатором, эти методы различаются по своим задачам и алгоритмам.

Анализ с обходом дерева, как видно из названия, выполняется путем обхода дерева кода и проведения проверок на предмет соответствия кода принятому стандарту кодирования, указанному в виде набора правил и рекомендаций. Именно этот тип анализа проводят компиляторы.

Анализ потока данных можно описать как процесс сбора информации об использовании, определении и зависимостях данных в анализируемой программе. При анализе потока данных используется граф потока команд, генерируемый на основе дерева кода. Этот граф представляет все возможные пути выполнения данной программы: вершины обозначают "прямолинейные", без каких бы то ни было переходов, фрагменты кода, а ребра - возможную передачу управления между этими фрагментами. Поскольку анализ выполняется без запуска проверяемой программы, точно определить результат ее выполнения невозможно. Иными словами, невозможно выяснить, по какому именно пути будет передаваться управление. Поэтому алгоритмы анализа потока данных аппроксимируют возможное поведение, например, рассматривая обе ветви оператора if-then-else, или выполняя с определенной точностью тело цикла while. Ограничение точности существует всегда, поскольку уравнения потока данных записываются для некоторого набора переменных, и количество этих переменных должно быть ограничено, поскольку мы рассматриваем лишь программы с конечным набором операторов. Следовательно, для количества неизвестных всегда существует некий верхний предел, дающий ограничение точности. С точки зрения графа потока команд при статическом анализе все возможные пути выполнения программы считаются действительными. Из-за этого допущения при анализе потока данных можно получать лишь приблизительные решения для ограниченного набора задач .

Описанный выше алгоритм анализа потока данных не различает путей, поскольку все возможные пути, вне зависимости от того реальны они, или нет, будут ли они выполняться часто, или редко, все равно приводят к решению. На практике, однако, выполняется лишь малая часть потенциально возможных путей. Более того, самый часто выполняемый код, как правило, составляет еще меньшее подмножество всех возможных путей. Логично сократить анализируемый граф потока команд и уменьшить таким образом объем вычислений, анализируя лишь некоторое подмножество возможных путей. Анализ с выбором путей проводится по сокращенному графу потока команд, в котором нет невозможных путей и путей, не содержащих "опасного" кода. Критерии выбора путей различны в различных анализаторах. Например, анализатор может рассматривать лишь пути, содержащие объявления динамических массивов, считая такие объявления "опасными" согласно настройкам анализатора.

Заключение

Число методов статического анализа и самих анализаторов возрастает из года в год, и это означает, что интерес к статическим анализаторам кода растет. Причина заинтересованности заключается в том, что разрабатываемое программное обеспечение становится все более и более сложным и, следовательно, проверять код вручную становится невозможно.

В этой статье было приведено краткое описание процесса статического анализа и различных методов проведения такого анализа.

Библиографический список

  • Dirk Giesen Philosophy and practical implementation of static analyzer tools . -Electronic data. -Dirk Giesen, cop. 1998.
  • James Alan Farrell Compiler Basics . -Electronic data. -James Alan Farrell, cop 1995. -Access mode: http://www.cs.man.ac.uk/~pjj/farrell/compmain.html
  • Joel Jones Abstract syntax tree implementation idioms . -Proceedings of the 10th Conference on Pattern Languages of Programs 2003, cop 2003.
  • Ciera Nicole Christopher Evaluating Static Analysis Frameworks .- Ciera Nicole, cop. 2006.
  • Leon Moonen A Generic Architecture for Data Flow Analysis to Support Reverse Engineering . - Proceedings of the 2nd International Workshop on the Theory and Practice of Algebraic Specifications, cop. 1997.
Проблемы