Transcript
Page 1: статический анализ кода

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

Карпов Андрей Николаевичк.ф.-м.н., MVP,технический директорООО «СиПроВер»Сайт: www.viva64.comE-Mail: [email protected]

Page 2: статический анализ кода

Методы повышения качества кода

• Доказательство корректности программы• Обзоры кода• Юнит-тесты (TDD)• Регрессионное тестирование• Анализ покрытия различных путей выполенения• Динамический анализ• Статический анализ• Ручное тестирование• Нагрузочное тестирование• …

Page 3: статический анализ кода

Чем раньше – тем лучше

Page 4: статический анализ кода

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

Статический анализ можно рассматривать как более дешевый и автоматизированный способ выполнить обзор кода (code-review).

Преимущества:

• раннее выявление дефектов;• статический анализатор не устаёт;• анализ всех ветвей программы;• хорошее распараллеливание анализа;• выявление ошибок такого типа, о которых

программист даже не подозревает.

Page 5: статический анализ кода

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

• Cppcheck — бесплатный;• Статический анализ входящий в Visual Studio;• Статический анализ входящий в Intel Parallel Studio;• PC-Lint — $389 за одну лицензию или $3500 – за 10,

неограниченно по времени;• PVS-Studio — €3500 за 5 лицензий, год

использования;• Klocwork — €30000 за пакет «сервер + 20 клиентов»

за год использования;• Coverity — дорого.

Page 6: статический анализ кода

Дорого… Почему не только TDD?• в тестах тоже можно ошибиться;• проверка мест, редко получающих управление;• обнаружение плавающих ошибок (undefined

behavior, гейзенбаги);• не на все варианты кода можно написать юнит-

тест:– сложные счетные алгоритмы;– большой объем потребляемой памяти;– пользовательский интерфейс;– другое.

Page 7: статический анализ кода

В тестах тоже можно ошибиться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: статический анализ кода

Проверка мест, редко получающих управление

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: статический анализ кода

Обнаружение плавающих ошибок (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: статический анализ кода

Не на все варианты кода можно написать юнит-тест: сложные

счетные алгоритмы

Примеры:• Численное моделирование• Статический анализ кода

Page 11: статический анализ кода

Не на все варианты кода можно написать юнит-тест: большой объем потребляемой памяти

#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: статический анализ кода

присутствует объявление функции 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: статический анализ кода

Не на все варианты кода можно написать юнит-тест: пользовательский интерфейс

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: статический анализ кода

Не на все варианты кода можно написать юнит-тест: другое

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: статический анализ кода

Игра – найди ошибку!

Page 16: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача N3.int EditStreamPadSilence(....){ ... if (hr = AVIFileGetStream(pfileSilence, &paviSilence, streamtypeAUDIO , 0) != AVIERR_OK) { ErrMsg("Unable to load silence stream"); return hr; } ...}

vscap

Page 21: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача N4.

void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats ) { ... memset( &statex, sizeof( statex ), 0 ); ...}

Page 23: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача N6.

#define FILE_ATTRIBUTE_DIRECTORY 0x00000010

bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) { ... info->is_directory = file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0; ...}

Page 27: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача N7.

void BCMenu::InsertSpaces(void){ if(IsLunaMenuStyle()) if(!xp_space_accelerators)return; else if(!original_space_accelerators)return; ...}

BCmenu

Page 29: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Попробуйте найти ошибку. Задача 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: статический анализ кода

Мифы о статическом анализе

• статический анализатор это продукт разового применения;

• профессиональные разработчики не допускают глупых ошибок;

• динамический анализ лучше чем статический;

• можно составить маленькую программу, чтобы оценить инструмент.

Page 37: статический анализ кода

Миф: статический анализатор это продукт разового применения

• «я проверил и нашел мало ошибок»;• аналогия с предупреждениями

компилятора;• ROI;

Page 38: статический анализ кода

Миф: профессиональные разработчики не допускают глупых ошибок

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: статический анализ кода

Миф: динамический анализ лучше чем статический (или 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: статический анализ кода

Миф: можно составить маленькую программу, чтобы оценить инструмент

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: статический анализ кода

Выводы

• Си++ живее всех живых и надо как-то справляться с проектами;

• Статический анализ всё актуальнее, так как размеры программ растут.

Page 42: статический анализ кода

Дополнительная информация

• Анализатор 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)


Recommended