Статический анализ кода
DESCRIPTION
Андрей КарповВы узнаете, что такое статический анализ кода и историю его развития. Узнаете, как эффективно применять инструменты статического анализа в своей работе, увидите практические примеры использования этой методологии. Доклад ориентирован на программистов, использующих языки Си/Си++, но будет полезен всемTRANSCRIPT
![Page 1: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/1.jpg)
Статический анализ кода
Карпов Андрей Николаевичк.ф.-м.н., MVP,технический директорООО «СиПроВер»Сайт: www.viva64.comE-Mail: [email protected]
![Page 2: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/2.jpg)
Методы повышения качества кода
• Доказательство корректности программы• Обзоры кода• Юнит-тесты (TDD)• Регрессионное тестирование• Анализ покрытия различных путей выполенения• Динамический анализ• Статический анализ• Ручное тестирование• Нагрузочное тестирование• …
![Page 3: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/3.jpg)
Чем раньше – тем лучше
![Page 4: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/4.jpg)
Что такое статический анализ кода
Статический анализ можно рассматривать как более дешевый и автоматизированный способ выполнить обзор кода (code-review).
Преимущества:
• раннее выявление дефектов;• статический анализатор не устаёт;• анализ всех ветвей программы;• хорошее распараллеливание анализа;• выявление ошибок такого типа, о которых
программист даже не подозревает.
![Page 5: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/5.jpg)
Инструменты статического анализа
• Cppcheck — бесплатный;• Статический анализ входящий в Visual Studio;• Статический анализ входящий в Intel Parallel Studio;• PC-Lint — $389 за одну лицензию или $3500 – за 10,
неограниченно по времени;• PVS-Studio — €3500 за 5 лицензий, год
использования;• Klocwork — €30000 за пакет «сервер + 20 клиентов»
за год использования;• Coverity — дорого.
![Page 6: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/6.jpg)
Дорого… Почему не только TDD?• в тестах тоже можно ошибиться;• проверка мест, редко получающих управление;• обнаружение плавающих ошибок (undefined
behavior, гейзенбаги);• не на все варианты кода можно написать юнит-
тест:– сложные счетные алгоритмы;– большой объем потребляемой памяти;– пользовательский интерфейс;– другое.
![Page 7: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/7.jpg)
В тестах тоже можно ошибитьсяvoid checkFormatConversion::Test(...){ ... static struct { bool _b1, _b2; } ms_2boolean[] = { { false, false }, { false, true }, { true, false }, { true, true } }; const int b2size = sizeof(ms_2boolean) / sizeof(ms_2boolean); ...}
eLynxSDK
V501 There are identical sub-expressions 'sizeof (ms_2boolean)' to the left and to the right of the '/' operator.
![Page 8: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/8.jpg)
Проверка мест, редко получающих управление
V595 The 'node' pointer was utilized before it was verified against nullptr. Check lines: 1421, 1424.
void idBrushBSP::FloodThroughPortals_r( idBrushBSPNode *node, ....){ ... if ( node->occupied ) { common->Error( "FloodThroughPortals_r: node already occupied\n"); } if ( !node ) { common->Error("FloodThroughPortals_r: NULL node\n"); } ...}
![Page 9: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/9.jpg)
Обнаружение плавающих ошибок (undefined behavior, гейзенбаги)
bool 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 while being used twice between sequence points.
saga
![Page 10: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/10.jpg)
Не на все варианты кода можно написать юнит-тест: сложные
счетные алгоритмы
Примеры:• Численное моделирование• Статический анализ кода
![Page 11: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/11.jpg)
Не на все варианты кода можно написать юнит-тест: большой объем потребляемой памяти
#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]);}
![Page 12: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/12.jpg)
присутствует объявление функции 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)cdqemov rcx,qword ptr [i]mov qword ptr Pointers[rcx*8],rax
![Page 13: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/13.jpg)
Не на все варианты кода можно написать юнит-тест: пользовательский интерфейс
Fennec Media Projectint JoiningProc(HWND hwnd,UINT uMsg, 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 by two 0 characters.
![Page 14: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/14.jpg)
Не на все варианты кода можно написать юнит-тест: другое
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‘.
![Page 15: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/15.jpg)
Игра – найди ошибку!
![Page 16: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/16.jpg)
Попробуйте найти ошибку. Задача N1. (Пока вы ещё не устали. А анализатор
не устаёт!)
void 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) ...}
Dolphin
![Page 17: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/17.jpg)
Попробуйте найти ошибку. Задача N1. (Пока вы ещё не устали. А анализатор
не устаёт!)
void 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) ...}
Dolphin
V579 The memcmp function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument.
![Page 18: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/18.jpg)
Попробуйте найти ошибку. Задача 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); ...}
![Page 19: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/19.jpg)
Попробуйте найти ошибку. Задача N2.
V502 Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the '+' operator.
bool ots_gdef_parse(...) { ... const unsigned gdef_header_end = static_cast<unsigned>(8) + gdef->version_2 ? static_cast<unsigned>(2) : static_cast<unsigned>(0); ...}
![Page 20: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/20.jpg)
Попробуйте найти ошибку. Задача N3.int EditStreamPadSilence(....){ ... if (hr = AVIFileGetStream(pfileSilence, &paviSilence, streamtypeAUDIO , 0) != AVIERR_OK) { ErrMsg("Unable to load silence stream"); return hr; } ...}
vscap
![Page 21: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/21.jpg)
Попробуйте найти ошибку. Задача N3.int EditStreamPadSilence(....){ ... if (hr = AVIFileGetStream(pfileSilence, &paviSilence, streamtypeAUDIO , 0) != AVIERR_OK) { ErrMsg("Unable to load silence stream"); return hr; } ...}
vscap
V593 Consider reviewing the expression of the 'A = B != C' kind. The expression is calculated as following: 'A = (B != C)'.
![Page 22: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/22.jpg)
Попробуйте найти ошибку. Задача N4.
void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats ) { ... memset( &statex, sizeof( statex ), 0 ); ...}
![Page 23: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/23.jpg)
Попробуйте найти ошибку. Задача N4.
void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats ) { ... memset( &statex, sizeof( statex ), 0 ); ...}
V575 The 'memset' function processes '0' elements. Inspect the third argument.
![Page 24: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/24.jpg)
Попробуйте найти ошибку. Задача N5.
void CAST256::Base::UncheckedSetKey(const byte *userKey, unsigned int keylength, const NameValuePairs &){ AssertValidKeyLength(keylength); word32 kappa[8]; ... memset(kappa, 0, sizeof(kappa));}
![Page 25: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/25.jpg)
Попробуйте найти ошибку. Задача 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 is used to flush 'kappa' buffer. The RtlSecureZeroMemory() function should be used to erase the private data.
![Page 26: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/26.jpg)
Попробуйте найти ошибку. Задача N6.
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010
bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) { ... info->is_directory = file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0; ...}
![Page 27: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/27.jpg)
Попробуйте найти ошибку. Задача N6.
V564 The '&' operator is applied to bool type value. You've probably forgotten to include parentheses or intended to use the '&&' operator.
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010
bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) { ... info->is_directory = file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0; ...}
![Page 28: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/28.jpg)
Попробуйте найти ошибку. Задача N7.
void BCMenu::InsertSpaces(void){ if(IsLunaMenuStyle()) if(!xp_space_accelerators)return; else if(!original_space_accelerators)return; ...}
BCmenu
![Page 29: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/29.jpg)
Попробуйте найти ошибку. Задача N7.
V563 It is possible that this 'else' branch must apply to the previous 'if' statement.
void BCMenu::InsertSpaces(void){ if(IsLunaMenuStyle()) if(!xp_space_accelerators) return; else if(!original_space_accelerators) return; ...}
BCmenu
![Page 30: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/30.jpg)
Попробуйте найти ошибку. Задача N8.BOOL TortoiseBlame::OpenFile(const TCHAR *fileName){ ... char * utf8CheckBuf = lineptr; while ((bUTF8)&&(*utf8CheckBuf)) { if ((*utf8CheckBuf == 0xC0)|| (*utf8CheckBuf == 0xC1)|| (*utf8CheckBuf >= 0xF5)) { bUTF8 = false; break; } ...}
![Page 31: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/31.jpg)
Попробуйте найти ошибку. Задача 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. The value range of signed char type: [-128, 127].
![Page 32: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/32.jpg)
Попробуйте найти ошибку. Задача N9.inline void elxLuminocity(const PixelRGBi& iPixel, LuminanceCell< PixelRGBi >& oCell){ oCell._luminance = 2220 * iPixel._red + 7067 * iPixel._blue + 0713 * iPixel._green; oCell._pixel = iPixel;}
eLynxSDK
![Page 33: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/33.jpg)
Попробуйте найти ошибку. Задача N9.inline void elxLuminocity(const PixelRGBi& iPixel, LuminanceCell< PixelRGBi >& oCell){ oCell._luminance = 2220 * iPixel._red + 7067 * iPixel._blue + 0713 * iPixel._green; oCell._pixel = iPixel;}
eLynxSDK
V536 Be advised that the utilized constant value is represented by an octal form. Oct: 0713, Dec: 459.
![Page 34: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/34.jpg)
Попробуйте найти ошибку. Задача 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; } } ...}
![Page 35: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/35.jpg)
Попробуйте найти ошибку. Задача 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.
![Page 36: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/36.jpg)
Мифы о статическом анализе
• статический анализатор это продукт разового применения;
• профессиональные разработчики не допускают глупых ошибок;
• динамический анализ лучше чем статический;
• можно составить маленькую программу, чтобы оценить инструмент.
![Page 37: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/37.jpg)
Миф: статический анализатор это продукт разового применения
• «я проверил и нашел мало ошибок»;• аналогия с предупреждениями
компилятора;• ROI;
![Page 38: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/38.jpg)
Миф: профессиональные разработчики не допускают глупых ошибок
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. Unsigned type value is never < 0.
![Page 39: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/39.jpg)
Миф: динамический анализ лучше чем статический (или 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.
![Page 40: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/40.jpg)
Миф: можно составить маленькую программу, чтобы оценить инструмент
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 of the '&&' operator: aXResolution > 0.0 && aXResolution > 0.0
Почему никто не составляет такие примеры?
![Page 41: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/41.jpg)
Выводы
• Си++ живее всех живых и надо как-то справляться с проектами;
• Статический анализ всё актуальнее, так как размеры программ растут.
![Page 42: Статический анализ кода](https://reader035.vdocuments.mx/reader035/viewer/2022062312/556cc9c7d8b42aba548b5050/html5/thumbnails/42.jpg)
Дополнительная информация
• Анализатор PVS-Studio:http://www.viva64.com/ru/pvs-studio/
• Twitter: https://twitter.com/Code_Analysis• E-Mail: [email protected]• Тел.: +7 (4872) 38-59-95 (GMT + 03:00)