Best tools noone uses: PREfast

Начитавшись про правильные практики, наконец заставил себя ознакомиться со словом PREfast и вообще статическими анализаторами.

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

Для начала главный вопрос – зачем это все. Статический анализ кода (static code analysis) нужен для автоматического обнаружения code/data flow багов – утечек памяти, выходов за границы, прочих приятных вещей. Компилятор особо глубоким анализом не занимается, поэтому не может. Программист может, но хер когда занимается. Отсюда потребность в роботе, который одновременно умнее компилятора, и усерднее программиста.

Язык C++ непростой, поэтому анализаторов для него мало. Опен-сорсных нет совсем, кроме разве что Oink и брата его Mozilla Pork. Сложности с ними начинаются уже на стадии сборки, причем под родным Linux. Собрать за часок-другой применения напильника мне его (Pork) удалось; а вот добиться работы на приложенном тривиальном тесте уже нет. В общем, если есть лишний год-другой, можно попытаться собрать под Windows (это развлечет вас первые 2-3 месяца) и затем проанализировать боевой проект (а это остаток года). Платных анализаторов есть, но пересчитать удается по щупальцам одного, максимум двух осьминогов; при этом лидеры типа Coverity, Klocwork итп стоят измеримых денег. Это заметно сразу по сайтам, где невозможно найти ни цены, ни кнопки Buy, только предложения связаться с сейлами. Сразу ясно, что цена вопроса далеко не $1000. И правда, Гугл докладывает о ценнике за инсталляцию на 0.5 MLOC порядка $20000-50000. В год. Каждый год. Поэтому, фактически, PREfast – это единственный доступный нам, нищим (см. бесплатно!), работающий, и безусловно полезный анализатор.

Полезность ощутилась сразу. В ходе первого же тестового прогона “для души и смеха” тот PREfast выявил совершенно, конечно, дебильную утечку – однако с последствиями этой утечки (см. тихие мистические вылеты раз в 2-5 дней) по невероятному совпадению я, несмотря на тривиальность бага, недолгими припадками боролся всю прошлую неделю. Эх, знал бы, что и где искать… да, сразу же нашел бы себе клад из золотых червонцев, самогона полный дом, все гори оно огнем. А пока дальше про статический анализ.

PREfast for Drivers (по слухам, это расширенная и углубленная версия “обычного”) берется в составе Windows Driver Kit (WDK). Где брать WDK с целью припасть к наномегатехнологии – английским по белому и довольно подробно рассказано в этой статье. Теоретически, нужна бесплатная регистрация на live.com, затем жалких 12 веб-форм с пиксель-хантом, и золотой ключик наш. Практически, 12й шаг эпопеи кончается секретным линком, который сегодня вел на WDK_6001_18001.iso (632.9 MB) и работал без всяких регистраций. Еще можно успеть.

Упомянутый “обычный” анализатор, кстати, по слухам встроен в Visual Studio начиная с 2005 – прямо в cl.exe, посредством ключа /analyze и тайных строк в меню собственно среды разработки. Проверить не удалось, тк. Team System под руками не очутилось, а в Professional/Standard/Express ничего подобного не наблюдается. Таким образом, доплатив к $300, которые стоит VS 2008 Standard всего лишь $5200, нужных для покупки VS TS 2008 Development Edition, можно легко обойтись без необходимости качать WDK. В такой вопиющей ситуации – лично я бы спрятал напрочь бесплатный WDK минимум на 120 шагов. Налицо недоработка веб-мастеров.

На случай самоликвидации залинкованной статьи (ну или очередных переделок на сайтах MS) кратко скопирую тут шаги:

  • Если надо, регистрируемся на login.live.com.
  • Идем на connect.microsoft.com, сразу логинимся (Sign In).
  • Идем в Connection Directory (есть подозрение, точное название и URL могут меняться).
  • Ищем слово WDK, жмем ссылку. (Сегодня ссылка такая.)
  • Ищем слово Downloads, жмем ссылку. (Сегодня такая.)
  • Ищем слово WDK, жмем.
  • PROFIT!!!

Затем скачанное придется поставить. Без сэмплов занимает где-то 1.2 GB. Инсталлятор то ли сглючил лично у меня, то ли просто не умеет, но точечно выбрать подкомпоненты не удалось. Впрочем, можно стереть вручную после установки. Например, убиение C:\WinDDK\lib (размером 835 MB) именно на PREfast никак не влияет.

Затем уже почти можно применять. Путей WDK никаких не прописывает. Поэтому первым делом нужен 1-строчный враппер по кличке prefast.cmd в любой директории из %PATH%:

[code]
@C:\WinDDK\tools\pfd\bin\bin\x86\prefast.exe %*
[/code]

В идеальном мире после этого оставалось бы только запустить

[code]
prefast devenv MySolution.sln /Rebuild Debug
[/code]

К несчастью, devenv шибко умнее PREfast, поэтому вызовы компилятора именно из-под devenv PREfast перехватить не осиливает. Шаманские пляски вокруг devenv/vcbuild не помогают, причем не мне одному. Так что “собирать” и анализировать приходится по старинке, пофайлово. Для этого пишем еще один махонький враппер, pfc.cmd:

[code]
@echo off
call "C:\Program Files\Microsoft Visual Studio 8\VC\bin\vcvars32.bat"
set INCLUDE=c:\myown\include;%INCLUDE%
prefast cl /c %*
[/code]

Строчка про INCLUDE нужна, чтобы вручную выставить сконфигурированные в студии include directories. Злоупотребление 3rd party libs в системе, а не самом проекте, ударит в спину еще и здесь. Ура, PREfast наконец готов к использованию! Берем пример из недавнего поста:

[code lang="cpp"]
void testFunc()
{
int array[10];
array[12] = 0;
return;
}
[/code]

И вот оно счастье – выход за границы отловлен. Автоматически!

[code]
C:\Temp\prefast>pfc 1.cpp
Setting environment for using Microsoft Visual Studio 2005 x86 tools.
-----------------------------------------------------------------------------
Microsoft (R) PREfast Version 8.0.50727.80118.
Copyright (C) Microsoft Corporation. All rights reserved.
-----------------------------------------------------------------------------
1.cpp
1.cpp
-----------------------------------------------------------------------------
Removing duplicate defects from the log...
-----------------------------------------------------------------------------
PREfast reported 2 defects during execution of the command.
-----------------------------------------------------------------------------
Enter PREFAST LIST to list the defect log as text within the console.
Enter PREFAST VIEW to display the defect log user interface.
[/code]

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

prefast1.png

prefast2.png

prefast3.png

Удивительно, но есть более-менее приличная документация. Читайте, она хорошая. По личному опыту, на вопросы “почему здесь warning” она дает быстрый и внятный ответ. Например, оказывается, если внутри функции значение вызова strlen() присваивается куда-то, но не используется в операторах сравнения, то будет warning – и такое поведение документировано прямо в статье про тот warning. В принципе, логично – не используешь, не считай. Жалко, что в моем случае это был стратежно задуманный предрасчет таблицы длин. А то бы сразу еще один баг починил.

В местах, где PREfast не догадывается сам – а он анализирует потоки только в пределах одной функции, поэтому догадывается не всегда – ему можно подсказывать. Для этого есть __analysis_assume. Например, вот так изгоняется warning из метода vector::reserve:

[code lang="cpp"]
T * pNew = new T [ m_iLimit ];
__analysis_assume ( m_iLength<=m_iLimit );
for ( int i=0; i pNew[i] = m_pData[i];
[/code]

Забавно, что если __analysis_assume пересунуть перед вызовом new, то warning останется. Видимо, считается, что аргумент могут и поменять ненароком, поэтому надо зорко озираться и инвалидировать assume.

Наконец, про полезные фокусы для снижения шума. (Они тоже есть вот тут, в доке, но кто бы ее читал сначала?) Во-1х, сигнализируем про конкретное поведение нашего перегруженного оператора new(), чтобы не было нытья про NULL или там про exceptions - это опция /new_failure, например /new_failure=never. Во-2х, дефолтный стек драйвера в 1К это как-то маловато, поэтому /stackhogthreshold=10240, или даже /stackhogthreshold=65536. Наконец, в-3х, стОит отшибить дефолтный режим анализа "это драйвер ядра, который должен сохранять FPU state и все такое" - по уставу это делается подключением driverspec.h и затем макросом __user_mode, однако можно обойтись без непонятных внешних заголовков:

[code lang="cpp"]
#if (_MSC_VER >= 1000) && !defined(__midl) && defined(_PREFAST_)
typedef int __declspec("SAL_nokernel") __declspec("SAL_nodriver") __prefast_flag_kernel_driver_mode;
#endif
[/code]

Дешевой вам отладки. (Да, все сэкономленные на отладке или там VSTS средства можно отсылать мне. Пойдут на хостинг блога. По-честному надо в Microsoft, однако у них и так неплохой market cap.)

  • Tsukrov

    Всё чудно, но похоже prefast, таки да, включен в VS 2008 Pro.
    У меня /analyze что-то делает, кидает какие-то предупреждения.

  • ForestMan

    Очень вовремя статья :), я как раз ищу себе анализатор кода.
    Спасибо попробуем.

  • ForestMan

    В VS 2005 Team Suite есть встроенный анализатор кода, который можно включить и настроить на уровне проекта.
    Кто-нибудь использовал его на реальном проекте? Можно ли его сравнивать с PREfast? Он лучше или хуже?

  • ForestMan

    P.S.Тест приведённый в этой статье анализатор из VS 2005 прошёл успешно.

  • Vitaly

    Спасибо за инструкцию

    К слову, Coverity настолько дорогой, что не даже отвечает на письма с запросами trial версии :)

  • $ergi0

    запустил студийный анализатор на своём проэкте. 15 минут жду когда он закончить рассказывать про проблемы в WTL

  • $ergi0

    ага, закончил с WTL и на этом решил, что дело сделано. хммм.

  • 23Skidoo

    gimpel lint ещё есть, он не такой дорогой и поддерживает C++
    http://www.gimpel.com/

  • Tsukrov

    У меня почему-то ругается на код.

    int arr[BIG_SIZE];
    int cnt = 0;

    if(cnt == BIG_SIZE)return;
    arr[cnt++] = 42;

    Вроде всё же в порядке? Это же не arr[++cnt]?

  • dDIMA

    2 Tsukrov:
    Попробуй if (cnt >= BIG_SIZE) return; Возможно, оно предполагает, что cnt может “перепрыгнуть” за BIG_SIZE?

  • shvez

    Андрей, а нельзя разве prefast выдернуть, запаковать и выложить куда-нить в пригодном для использования виде?

  • shodan

    Tsukrov,
    > У меня почему-то ругается на код.
    > int arr[BIG_SIZE];

    А у меня не ругается.
    Полный файл с сорсом и сообщение об ошибке в студию.
    Посмотрим, чем встроенный в VS2008 отличается от WDK-шного!

    > Андрей, а нельзя разве prefast выдернуть, запаковать и выложить куда-нить в пригодном для использования виде?

    Не знаю, не пробовал.

  • http://rageous.livejournal.com Rageous

    VS2005TS SP1 “cl /analyze …” – ругается на всякое, да

  • http://acefsm.livejournal.com acefsm

    http://www.parasoft.com/jsp/templates/ads/google/cplusplusgoogle1.jsp;jsessionid=aaa5KI0kXczTAA?redname=cplusplusgoogle1&referred=cplusplusgoogle1

    C++test enables coding policy enforcement, static analysis, comprehensive code review, and unit and component testing to provide teams a practical way to ensure that their C and C++ code works as expected.

  • gauri

    если кому лень качать wdk — http://rapidshare.com/files/157045475/prefast.rar.html выколупал.
    если будут проблемы — пишите. но вроде как работает нормально.

  • axxie

    Нам удавалось использовать PREfast вместе со студийным проектом. Для этого нужно было запускать devenv с ключом /useenv. Что-то вроде такого:
    prefast.exe devenv driver.sln /Build “Debug|x86″ /useenv

    Правда, это был проект драйвера, но думаю это несущественно.

  • belaz

    > gauri

    убедительная просьба выложить ещё раз – т.к. скачали уже больше 10 раз и заливка протухла…

  • Pingback: highly professional scums » Blog Archive » Рецепты отладки. Зависший 3D редактор.()

  • G-ray

    > gauri

    Поддерживаю, ещё раз, пожалуйста.

  • MihaPro

    > gauri

    Я тоже в очереди, перезалей плиз.

  • std.denis

    [b]gauri[/b],
    на ifolder, пожалуйста. или любой другой, лояльный, файлобомжатник :)

  • gauri
  • belaz
  • Dmitry Tyurev

    Здесь ошибку оно найти уже не может. В топку.

    void WinMain()
    {
    char arr[50];
    int n=0;

    for (int i=0; i

  • shodan

    > Здесь ошибку оно найти уже не может

    Кто-то разве обещал, что оно найдет вообще все ошибки и сразу?

  • Dmitry Tyurev

    Все ошибки, конечно не обещал. :) Но я привёл пример простейшей ошибки, которую казалось бы должен искать любой анализатор. И тот факт, что префаст не ищет даже её, говорит о многом. Жаль только код побился форумом. Андрей, можешь поправить?

  • bulrathi

    А чем плох valgrind? Или речь идет о анализаторе только под Win платформу?

  • ewro

    Есть еще Parasoft BugDetective, и он стоит подешевле KlocWork.
    И кстати его русские разрабатывают ;)

  • booker

    По большому секрету могу сообщить, что Klocwork тоже русские разрабатывают (в Канаде и в России) ;)

  • Pingback: Алёна C++ : Искусство отладки: технологии будущего()