Статический анализ кода
Upcoming SlideShare
Loading in...5
×
 

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

on

  • 1,282 views

Андрей Карпов...

Андрей Карпов

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

Statistics

Views

Total Views
1,282
Views on SlideShare
1,265
Embed Views
17

Actions

Likes
0
Downloads
4
Comments
0

2 Embeds 17

http://www.tuladev.net 10
http://tuladev.net 7

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

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

  • Статический анализ кодаКарпов Андрей Николаевичк.ф.-м.н., MVP,технический директорООО «СиПроВер»Сайт: www.viva64.comE-Mail: karpov@viva64.com
  • Методы повышения качества кода• Доказательство корректности программы• Обзоры кода• Юнит-тесты (TDD)• Регрессионное тестирование• Анализ покрытия различных путей выполенения• Динамический анализ• Статический анализ• Ручное тестирование• Нагрузочное тестирование• …
  • Чем раньше – тем лучше
  • Что такое статический анализ кодаСтатический анализ можно рассматривать как болеедешевый и автоматизированный способ выполнитьобзор кода (code-review).Преимущества:• раннее выявление дефектов;• статический анализатор не устаёт;• анализ всех ветвей программы;• хорошее распараллеливание анализа;• выявление ошибок такого типа, о которых программист даже не подозревает.
  • Инструменты статического анализа• Cppcheck — бесплатный;• Статический анализ входящий в Visual Studio;• Статический анализ входящий в Intel Parallel Studio;• PC-Lint — $389 за одну лицензию или $3500 – за 10, неограниченно по времени;• PVS-Studio — €3500 за 5 лицензий, год использования;• Klocwork — €30000 за пакет «сервер + 20 клиентов» за год использования;• Coverity — дорого.
  • Дорого… Почему не только TDD?• в тестах тоже можно ошибиться;• проверка мест, редко получающих управление;• обнаружение плавающих ошибок (undefined behavior, гейзенбаги);• не на все варианты кода можно написать юнит-тест: – сложные счетные алгоритмы; – большой объем потребляемой памяти; – пользовательский интерфейс; – другое.
  • В тестах тоже можно ошибитьсяvoid checkFormatConversion::Test(...){ eLynxSDK ... static struct { bool _b1, _b2; } ms_2boolean[] = { { false, false }, { false, true }, { true, false }, { true, true } }; const int b2size = sizeof(ms_2boolean) / sizeof(ms_2boolean); ...}V501 There are identical sub-expressions sizeof (ms_2boolean) tothe left and to the right of the / operator.
  • Проверка мест, редко получающих управлениеvoid idBrushBSP::FloodThroughPortals_r( idBrushBSPNode *node, ....){ ... if ( node->occupied ) { common->Error( "FloodThroughPortals_r: node already occupiedn"); } if ( !node ) { common->Error("FloodThroughPortals_r: NULL noden"); } ...} V595 The node pointer was utilized before it was verified against nullptr. Check lines: 1421, 1424.
  • Обнаружение плавающих ошибок (undefined behavior, гейзенбаги) sagabool CLine_Simplification::Simplify( CSG_Shape *pLine, int iPart, bool *Keep){ ... Keep[iFloater--] = iAnchor == 0 && iFloater == pLine->Get_Point_Count(iPart) - 1; ...}V567 Undefined behavior. The iFloater variable is modified whilebeing used twice between sequence points.
  • Не на все варианты кода можно написать юнит-тест: сложные счетные алгоритмыПримеры:• Численное моделирование• Статический анализ кода
  • Не на все варианты кода можно написать юнит-тест: большой объем потребляемой памяти#include <stdlib.h>void test(){ const size_t Gbyte = 1024 * 1024 * 1024; size_t i; char *Pointers[3]; // Allocate for (i = 0; i != 3; ++i) Pointers[i] = (char *)malloc(Gbyte); // Use for (i = 0; i != 3; ++i) Pointers[i][0] = 1; // Free for (i = 0; i != 3; ++i) free(Pointers[i]);}
  • присутствует объявление функции malloc Pointers[i] = (char *)malloc(Gbyte); mov rcx,qword ptr [Gbyte] call qword ptr [__imp_malloc (14000A518h)] mov rcx,qword ptr [i] mov qword ptr Pointers[rcx*8],raxотсутствует объявление функции malloc Pointers[i] = (char *)malloc(Gbyte); mov rcx,qword ptr [Gbyte] call malloc (1400011A6h) cdqe mov rcx,qword ptr [i] mov qword ptr Pointers[rcx*8],rax
  • Не на все варианты кода можно написать юнит-тест: пользовательский интерфейсint JoiningProc(HWND hwnd,UINT uMsg, Fennec Media Project WPARAM wParam,LPARAM lParam){ ... OPENFILENAME lofn; memset(&lofn, 0, sizeof(lofn)); ... lofn.lpstrFilter = uni("All Files (*.*)0*.*"); ...}V540 Member lpstrFilter should point to string terminated bytwo 0 characters.
  • Не на все варианты кода можно написать юнит-тест: другоеvoid AccessibleContainsAccessible(...){ ... auto_ptr<VARIANT> child_array(new VARIANT[child_count]); ...}V554 Incorrect use of auto_ptr. The memory allocated with new []will be cleaned using delete‘.
  • Игра – найди ошибку!
  • Попробуйте найти ошибку. Задача N1.(Пока вы ещё не устали. А анализатор не устаёт!) Dolphinvoid drawShadedTexSubQuad(..., const MathUtil::Rectangle<float>* rDest, ...){ ... if (stsq_observer || memcmp(rDest, &tex_sub_quad_data.rdest, sizeof(rDest))!=0 || tex_sub_quad_data.u1!=u1 || tex_sub_quad_data.v1!=v1 || tex_sub_quad_data.u2!=u2 || tex_sub_quad_data.v2!=v2 || tex_sub_quad_data.G != G) ...}
  • Попробуйте найти ошибку. Задача N1.(Пока вы ещё не устали. А анализатор не устаёт!) Dolphinvoid drawShadedTexSubQuad(..., const MathUtil::Rectangle<float>* rDest, ...){ ... if (stsq_observer || memcmp(rDest, &tex_sub_quad_data.rdest, sizeof(rDest))!=0 || tex_sub_quad_data.u1!=u1 || tex_sub_quad_data.v1!=v1 || tex_sub_quad_data.u2!=u2 || tex_sub_quad_data.v2!=v2 || tex_sub_quad_data.G != G) ...} V579 The memcmp function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument.
  • Попробуйте найти ошибку. Задача N2.bool ots_gdef_parse(...) { ... const unsigned gdef_header_end = static_cast<unsigned>(8) + gdef->version_2 ? static_cast<unsigned>(2) : static_cast<unsigned>(0); ...}
  • Попробуйте найти ошибку. Задача N2.bool ots_gdef_parse(...) { ... const unsigned gdef_header_end = static_cast<unsigned>(8) + gdef->version_2 ? static_cast<unsigned>(2) : static_cast<unsigned>(0); ...}V502 Perhaps the ?: operator works in a different way than it was expected. The ?:operator has a lower priority than the + operator.
  • Попробуйте найти ошибку. Задача N3.int EditStreamPadSilence(....) vscap{ ... if (hr = AVIFileGetStream(pfileSilence, &paviSilence, streamtypeAUDIO , 0) != AVIERR_OK) { ErrMsg("Unable to load silence stream"); return hr; } ...}
  • Попробуйте найти ошибку. Задача N3.int EditStreamPadSilence(....) vscap{ ... if (hr = AVIFileGetStream(pfileSilence, &paviSilence, streamtypeAUDIO , 0) != AVIERR_OK) { ErrMsg("Unable to load silence stream"); return hr; } ...}V593 Consider reviewing the expression of the A = B != C kind. Theexpression is calculated as following: A = (B != C).
  • Попробуйте найти ошибку. Задача N4.void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats ) { ... memset( &statex, sizeof( statex ), 0 ); ...}
  • Попробуйте найти ошибку. Задача N4.void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats ) { ... memset( &statex, sizeof( statex ), 0 ); ...}V575 The memset function processes 0 elements. Inspect the third argument.
  • Попробуйте найти ошибку. Задача N5.void CAST256::Base::UncheckedSetKey(const byte *userKey, unsigned int keylength, const NameValuePairs &){ AssertValidKeyLength(keylength); word32 kappa[8]; ... memset(kappa, 0, sizeof(kappa));}
  • Попробуйте найти ошибку. Задача N5.void CAST256::Base::UncheckedSetKey(const byte *userKey, unsigned int keylength, const NameValuePairs &){ AssertValidKeyLength(keylength); word32 kappa[8]; ... memset(kappa, 0, sizeof(kappa));}V597 The compiler could delete the memset function call, which isused to flush kappa buffer. The RtlSecureZeroMemory() functionshould be used to erase the private data.
  • Попробуйте найти ошибку. Задача N6.#define FILE_ATTRIBUTE_DIRECTORY 0x00000010bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) { ... info->is_directory = file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0; ...}
  • Попробуйте найти ошибку. Задача N6.#define FILE_ATTRIBUTE_DIRECTORY 0x00000010bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) { ... info->is_directory = file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0; ...} V564 The & operator is applied to bool type value. Youve probably forgotten to include parentheses or intended to use the && operator.
  • Попробуйте найти ошибку. Задача N7. void BCMenu::InsertSpaces(void) { BCmenu if(IsLunaMenuStyle()) if(!xp_space_accelerators)return; else if(!original_space_accelerators)return; ... }
  • Попробуйте найти ошибку. Задача N7. void BCMenu::InsertSpaces(void) { BCmenu if(IsLunaMenuStyle()) if(!xp_space_accelerators) return; else if(!original_space_accelerators) return; ... }V563 It is possible that this else branch must apply to the previous ifstatement.
  • Попробуйте найти ошибку. Задача N8.BOOL TortoiseBlame::OpenFile(const TCHAR *fileName){ ... char * utf8CheckBuf = lineptr; while ((bUTF8)&&(*utf8CheckBuf)) { if ((*utf8CheckBuf == 0xC0)|| (*utf8CheckBuf == 0xC1)|| (*utf8CheckBuf >= 0xF5)) { bUTF8 = false; break; } ...}
  • Попробуйте найти ошибку. Задача N8.BOOL TortoiseBlame::OpenFile(const TCHAR *fileName){ ... char * utf8CheckBuf = lineptr; while ((bUTF8)&&(*utf8CheckBuf)) { if ((*utf8CheckBuf == 0xC0)|| (*utf8CheckBuf == 0xC1)|| (*utf8CheckBuf >= 0xF5)) { bUTF8 = false; break; } ...}V547 Expression * utf8CheckBuf == 0xC0 is always false. Thevalue range of signed char type: [-128, 127].
  • Попробуйте найти ошибку. Задача N9.inlinevoid elxLuminocity(const PixelRGBi& iPixel, LuminanceCell< PixelRGBi >& oCell){ oCell._luminance = 2220 * iPixel._red + 7067 * iPixel._blue + eLynxSDK 0713 * iPixel._green; oCell._pixel = iPixel;}
  • Попробуйте найти ошибку. Задача N9.inlinevoid elxLuminocity(const PixelRGBi& iPixel, LuminanceCell< PixelRGBi >& oCell){ oCell._luminance = 2220 * iPixel._red + 7067 * iPixel._blue + eLynxSDK 0713 * iPixel._green; oCell._pixel = iPixel;}V536 Be advised that the utilized constant value is represented by anoctal form. Oct: 0713, Dec: 459.
  • Попробуйте найти ошибку. Задача N10.STDMETHODIMP CShellExt::Initialize(....){ ... ignoredprops.empty(); for (int p=0; p<props.GetCount(); ++p) { if (props.GetItemName(p).compare(SVN_PROP_IGNORE)==0) { std::string st = props.GetItemValue(p); ignoredprops = UTF8ToWide(st.c_str()); // remove all escape chars () std::remove(ignoredprops.begin(), ignoredprops.end(), ); break; } } ...}
  • Попробуйте найти ошибку. Задача N10.STDMETHODIMP CShellExt::Initialize(....){ ... ignoredprops.empty(); for (int p=0; p<props.GetCount(); ++p) { if (props.GetItemName(p).compare(SVN_PROP_IGNORE)==0) { std::string st = props.GetItemValue(p); ignoredprops = UTF8ToWide(st.c_str()); // remove all escape chars () std::remove(ignoredprops.begin(), ignoredprops.end(), ); break; } } ... V530 The return value of function empty/remove is} required to be utilized.
  • Мифы о статическом анализе• статический анализатор это продукт разового применения;• профессиональные разработчики не допускают глупых ошибок;• динамический анализ лучше чем статический;• можно составить маленькую программу, чтобы оценить инструмент.
  • Миф: статический анализатор это продукт разового применения• «я проверил и нашел мало ошибок»;• аналогия с предупреждениями компилятора;• ROI;
  • Миф: профессиональныеразработчики не допускают глупых ошибокIdleState CalculateIdleState( unsigned int idle_threshold){ ... DWORD current_idle_time = 0; ... // Will go -ve if we have been idle for a // long time (2gb seconds). if (current_idle_time < 0) current_idle_time = INT_MAX; ...}V547 Expression current_idle_time < 0 is always false. Unsignedtype value is never < 0.
  • Миф: динамический анализ лучшечем статический (или valgrind спасёт мир)int Notepad_plus::getHtmlXmlEncoding(....) const{ ... if (langT != L_XML && langT != L_HTML && langT == L_PHP) return -1; ...} V590 Consider inspecting this expression. The expression is excessive or contains a misprint.
  • Миф: можно составить маленькую программу, чтобы оценить инструмент nsresult PresShell::SetResolution(float aXResolution, float aYResolution) { if (!(aXResolution > 0.0 && aXResolution > 0.0)) { return NS_ERROR_ILLEGAL_VALUE; } ... }V501 There are identical sub-expressions to the left and to the right ofthe && operator: aXResolution > 0.0 && aXResolution > 0.0 Почему никто не составляет такие примеры?
  • Выводы• Си++ живее всех живых и надо как-то справляться с проектами;• Статический анализ всё актуальнее, так как размеры программ растут.
  • Дополнительная информация• Анализатор PVS-Studio: http://www.viva64.com/ru/pvs-studio/• Twitter: https://twitter.com/Code_Analysis• E-Mail: karpov@viva64.com• Тел.: +7 (4872) 38-59-95 (GMT + 03:00)