Как улучшить качество и надёжность кодовой базы? Один из ответов на этот вопрос — использование статического анализа. В данной статье мы исследуем, как эта методология может улучшить качество кодовой базы на примере проекта CodeLite. CodeLite — это бесплатная интегрированная среда разработки (IDE) для различных языков программирования, таких как C, C++, PHP и JavaScript. Она разработана для облегчения процесса разработки программного обеспечения и предлагает широкий спектр функций и инструментов. Основные возможности CodeLite: Редактор кода: CodeLite предоставляет удобный и мощный редактор кода с подсветкой синтаксиса, автодополнением, быстрыми навигационными возможностями и другими полезными инструментами. Отладчик: IDE включает в себя отладчик, который позволяет отслеживать выполнение программы, устанавливать точки останова и анализировать состояние переменных. Система сборки: CodeLite поддерживает несколько систем сборки, включая Make, CMake и другие. Это облегчает компиляцию и сборку проектов. Интеграция с Git: IDE имеет встроенную поддержку системы контроля версий Git, что позволяет разработчикам удобно работать с репозиториями Git прямо из среды разработки. Поддержка дополнительных расширений: CodeLite поддерживает плагины, которые позволяют пользователю расширять функциональность IDE в соответствии с собственными потребностями. Переносимость: CodeLite доступен для операционных систем Windows, macOS и Linux, что делает его удобным выбором для разработчиков, работающих на различных платформах. CodeLite является проектом с открытым исходным кодом, что позволяет разработчикам участвовать в его развитии и вносить вклад в улучшение IDE. Он непрерывно обновляется и развивается с целью предоставить мощные инструменты программистам. Прошло достаточно много времени с тех пор, как в блоге PVS-Studio была опубликована предыдущая статья про анализ кода проекта CodeLite. Интересно узнать, какие новые ошибки удастся найти. Анализатор выдал достаточно много предупреждений в результате проверки CodeLite версии 17.0.0. Поэтому в статье будут рассмотрены только те фрагменты кода, которые привлекли моё внимание при просмотре части сообщений. Если авторы проекта заинтересуются этой статьёй, то предлагаю им самостоятельно проверить проект, чтобы более детально изучить список предупреждений. Можно воспользоваться триальной версией. Если понравится и захочется использовать на регулярной основе, то для открытых проектов имеется возможность получения бесплатной лицензии. Предупреждение анализатора: V554 Incorrect use of unique_ptr. The memory allocated with 'new []' will be cleaned using 'delete'. clSocketBase.cpp:282:1 Анализатор обнаружил ситуацию, когда использование умного указателя приведёт к неопределённому поведению. В коде шаблон класса std::unique_ptr инстанцируется типом char. Из-за этого выбирается специализация, в деструкторе которой для освобождения объекта используется оператор delete. Однако умному указателю передаётся массив, выделенный через оператор *new[]. *Почему в С++ массивы нужно удалять через delete[] и почему возникает неопределённое поведение, можно прочитать здесь. Чтобы исправить ошибку, нужно инстацировать шаблон класса std::unique_ptr типом char[]: Предупреждение анализатора: V762 It is possible a virtual function was overridden incorrectly. See third argument of function 'Update' in derived class 'clProgressDlg' and base class 'wxGenericProgressDialog'. progress_dialog.h:47:1, progdlgg.h:44:1 Анализатор обнаружил ошибочное переопределение виртуальной функции. Вот как функция выглядит в базовом классе: А вот как в наследнике: Исходя из совпадения первых двух параметров функций, можно сделать вывод, что действительно хотели переопределить виртуальную функцию. Однако параметры по умолчанию также являются частью сигнатуры. Поэтому функция clProgressDlg::Update на самом деле не переопределяет, а скрывает виртуальную функцию wxGenericProgressDialog::Update. Корректное объявление виртуальной функции должно быть такое: Чтобы избежать таких ошибок, начиная с C++11, можно и даже нужно использовать спецификатор override: Теперь компилятор выдаст ошибку, если виртуальная функция ничего не переопределяет из базового класса. Вот ещё похожие места: V762 It is possible a virtual function was overridden incorrectly. See second argument of function 'Pulse' in derived class 'clProgressDlg' and base class 'wxGenericProgressDialog'. progress_dialog.h:48:1, progdlgg.h:45:1 V762 It is possible a virtual function was overridden incorrectly. See qualifiers of function 'WantsErrors' in derived class 'DbgVarObjUpdate' and base class 'DbgCmdHandler'. dbgcmd.h:504:1, dbgcmd.h:59:1 V762 It is possible a virtual function was overridden incorrectly. See first argument of function 'OnURL' in derived class 'ChangeLogPage' and base class 'ChangeLogPageBase'. changelogpage.h:51:1, subversion2_ui.h:351:1 V762 It is possible a virtual function was overridden incorrectly. See qualifiers of function 'GetStatusBar' in derived class 'MainFrameBase' and base class 'wxFrameBase'. gui.h:161:1, frame.h:119:1 V762 It is possible a virtual function was overridden incorrectly. See qualifiers of function 'GetMenuBar' in derived class 'MainFrameBase' and base class 'wxFrameBase'. gui.h:162:1, frame.h:85:1 V762 It is possible a virtual function was overridden incorrectly. See sixth argument of function 'InsertPage' in derived class 'clGTKNotebook' and base class 'wxNotebook'. GTKNotebook.hpp:69:1, notebook.h:87:1 V762 It is possible a virtual function was overridden incorrectly. See fifth argument of function 'CreateLinkTargets' in derived class 'BuilderGnuMakeOneStep' and base class 'BuilderGNUMakeClassic'. builder_gnumake_onestep.h:60:1, builder_gnumake.h:75:1 V762 It is possible a virtual function was overridden incorrectly. See sixth argument of function 'CreateLinkTargets' in derived class 'BuilderGnuMakeOneStep' and base class 'BuilderGNUMakeClassic'. builder_gnumake_onestep.h:60:1, builder_gnumake.h:75:1 V762 It is possible a virtual function was overridden incorrectly. See first argument of function 'DeleteAllItems' in derived class 'clDataViewListCtrl' and base class 'clTreeCtrl'. clDataViewListCtrl.h:147:1, clTreeCtrl.h:434:1  Предупреждение анализатора: V595 The 'dbgr' pointer was utilized before it was verified against nullptr. Check lines: 349, 351. simpletable.cpp:349:1, simpletable.cpp:351:1 Анализатор заметил в коде ситуацию, когда указатель сначала разыменовывается, а уже затем этот же указатель проверяется на значение NULL. Вот ещё похожие места: V595 The 'win' pointer was utilized before it was verified against nullptr. Check lines: 1115, 1127. DiffSideBySidePanel.cpp:1115:1, DiffSideBySidePanel.cpp:1127:1 V595 The 'm_vsb' pointer was utilized before it was verified against nullptr. Check lines: 212, 224. clScrolledPanel.cpp:212:1, clScrolledPanel.cpp:224:1 V595 The 'ms_instance' pointer was utilized before it was verified against nullptr. Check lines: 24, 25. php_parser_thread.cpp:24:1, php_parser_thread.cpp:25:1 V595 The 'tok' pointer was utilized before it was verified against nullptr. Check lines: 2070, 2094. checkmemoryleak.cpp:2070:1, checkmemoryleak.cpp:2094:1 V595 The 'parent' pointer was utilized before it was verified against nullptr. Check lines: 1006, 1008. checkuninitvar.cpp:1006:1, checkuninitvar.cpp:1008:1 V595 The 'tok1' pointer was utilized before it was verified against nullptr. Check lines: 9368, 9369. tokenize.cpp:9368:1, tokenize.cpp:9369:1 V595 The 'pResult' pointer was utilized before it was verified against nullptr. Check lines: 522, 526. SqliteDatabaseLayer.cpp:522:1, SqliteDatabaseLayer.cpp:526:1  Предупреждение анализатора: V766 An item with the same key ''.'' has already been added. wxCodeCompletionBoxManager.cpp:19:1 Видите здесь неладное? Из-за такого количества одинарных кавычек глаз вполне может не заметить, что здесь повторно добавляется символ '.'. Возможно, что здесь забыли добавить какой-то другой символ. Либо это просто случайный дубликат, и его можно убрать. Еще 1 похожее место: V766 An item with the same key '"MSYS2/GCC"' has already been added. compiler.cpp:621:1, compiler.cpp:620:1 Предупреждение анализатора: V501 There are identical sub-expressions 'result.second.empty()' to the left and to the right of the '||' operator. RemotyNewWorkspaceDlg.cpp:19:1 Разработчик очепятался, и в итоге слева и справа от оператора || расположены одинаковые подвыражения. Помимо поля second надо было проверить также поле first. Порой предупреждение анализатора позволяет косвенно найти другие странности в коде. Например, в функцию ::clRemoteFileSelector передаётся некорректный строковый литерал :) Исправленный код: Анализатор нашел еще 2 похожих места: V501 There are identical sub-expressions '!sshSettings.IsRemoteUploadEnabled()' to the left and to the right of the '||' operator. PhpSFTPHandler.cpp:104:1 V501 There are identical sub-expressions 'output.Contains("username for")' to the left and to the right of the '||' operator. git.cpp:1715:1 Предупреждение анализатора: V1043 A global object variable 'GMON_FILENAME_OUT' is declared in the header. Multiple copies of it will be created in all translation units that include this header file. static.h:41:1 Анализатор обнаружил объявление константного экземпляра класса wxString в заголовочном файле. Согласно стандарту C++, константы, объявленные в каком-либо пространстве имён, имеют внутреннее связывание. При включении такого файла через #include произойдёт создание множественных копий объекта. В зависимости от используемого стандарта C++, можно избежать такого поведения двумя способами. Начиная с C++17, можно объявить переменную со спецификатором inline. Это нововведение очень полезно при написании header-only библиотек. До C++17 придётся в заголовочном файле объявить переменную со спецификатором extern, а определение вынести в компилируемый файл: Вот ещё похожие места: V1043 A global object variable 'DOT_FILENAME_PNG' is declared in the header. Multiple copies of it will be created in all translation units that include this header file. static.h:42:1 V1043 A global object variable 'snippetSet' is declared in the header. Multiple copies of it will be created in all translation units that include this header file. swGlobals.h:41:1 V1043 A global object variable 's_plugName' is declared in the header. Multiple copies of it will be created in all translation units that include this header file. scGlobals.h:37:1 V1043 A global object variable 'svnNO_FILES_TO_DISPLAY' is declared in the header. Multiple copies of it will be created in all translation units that include this header file. subversion_strings.h:29:1 V1043 A global object variable 'CPPCHECK_DEFAULT_COMMAND' is declared in the header. Multiple copies of it will be created in all translation units that include this header file. cppchecksettingsdlg.h:31:1 V1043 A global object variable 'CWE119' is declared in the header. Multiple copies of it will be created in all translation units that include this header file. checkbufferoverrun.h:48:1 V1043 A global object variable 'CWE398' is declared in the header. Multiple copies of it will be created in all translation units that include this header file. checkexceptionsafety.h:37:1 V1043 A global object variable 'emptyString' is declared in the header. Multiple copies of it will be created in all translation units that include this header file. config.h:23:1 V1043 A global object variable 'DEFAULT_AUI_DROPDOWN_FUNCTION' is declared in the header. Multiple copies of it will be created in all translation units that include this header file. wxc_widget.h:27:1 ....  Предупреждение анализатора: V773 Visibility scope of the 'imageList' pointer was exited without releasing the memory. A memory leak is possible. acceltabledlg.cpp:61:1, acceltabledlg.cpp:47:1 Анализатор обнаружил потенциально возможную утечку памяти. Похоже, что переменную imageList забыли куда-то передать. Вот ещё места, которые выглядят подозрительно: V773 Visibility scope of the 'pDump' pointer was exited without releasing the memory. A memory leak is possible. ErdCommitWizard.cpp:273:1, ErdCommitWizard.cpp:219:1 V773 The function was exited without releasing the 'argv' pointer. A memory leak is possible. unixprocess_impl.cpp:288:1, unixprocess_impl.cpp:286:1 V773 The function was exited without releasing the 'child' pointer. A memory leak is possible. compilersfoundmodel.cpp:135:1, compilersfoundmodel.cpp:127:1 V773 The function was exited without releasing the 'child' pointer. A memory leak is possible. xdebuglocalsviewmodel.cpp:135:1, xdebuglocalsviewmodel.cpp:127:1 Предупреждение анализатора: V649 There are two 'if' statements with identical conditional expressions. The first 'if' statement contains function return. This means that the second 'if' statement is senseless. Check lines: 372, 375. clTreeCtrlModel.cpp:375:1, clTreeCtrlModel.cpp:372:1 Обратите внимание на первый if. Поток управления перейдёт на следующий if только если from != nullptr && to != nullptr. Это значит, что поток управления не зайдёт внутрь ни одного из последующих if. На самом деле первая проверка должна была быть такой: Анализатор нашёл ещё 1 похожее место: V649 There are two 'if' statements with identical conditional expressions. The first 'if' statement contains function return. This means that the second 'if' statement is senseless. Check lines: 1998, 2000. ShapeCanvas.cpp:2000:1, ShapeCanvas.cpp:1998:1 Предупреждение анализатора: V668 There is no sense in testing the 'pDump' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. ErdCommitWizard.cpp:220:1 В коде значение указателя, возвращаемого оператором new, сравнивается с нулём. Это бессмысленная операция. На момент проверки указатель всегда валидный. Если память выделить не удалось, то будет сгенерировано исключение std::bad_alloc. Если исключения отключены, то будет вызван std::abort. В любом случае, значение указателя pDump всегда будет ненулевым. Вот ещё похожие места: V668 There is no sense in testing the 'pLabel' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. ErdForeignKey.cpp:42:1 V668 There is no sense in testing the 'pBitmap' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. ErdTable.cpp:244:1 V668 There is no sense in testing the 'm_pLabel' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. ErdView.cpp:100:1 V668 There is no sense in testing the 'col' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. TableSettings.cpp:96:1 V668 There is no sense in testing the 'pOutFile' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. dumpclass.cpp:66:1 V668 There is no sense in testing the 'm_proc' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. async_executable_cmd.cpp:182:1 V668 There is no sense in testing the 'm_pEngine' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. spellcheck.cpp:144:1 V668 There is no sense in testing the 'pResultSet' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. SqliteDatabaseLayer.cpp:199:1 V668 There is no sense in testing the 'buffer' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. ShapeDataObject.cpp:65:1 V668 There is no sense in testing the 'node' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. XmlSerializer.cpp:357:1  Предупреждение анализатора: V587 An odd sequence of assignments of this kind: A = B; B = A;. Check lines: 483, 484. SqlCommandPanel.cpp:484:1, SqlCommandPanel.cpp:483:1 Анализатор обнаружил странное взаимное присваивание переменных. Возможно, разработчики собирались поменять эти переменные местами, а в итоге приравняли к одному значению*.* Предупреждение анализатора: V590 Consider inspecting the 'where != std::string::npos && where == 0' expression. The expression is excessive or contains a misprint. dbgcmd.cpp:60:1 Код удаляет двойную кавычку, если строка начинается с этого символа. Согласно стандарту, std::string::npos всегда равен максимальному значению, представимому типом size_t. Т.е. std::string::npos никогда не будет равен 0. На самом деле, код можно сделать эффективнее: Ещё 1 похожее место: V590 Consider inspecting the 'where != std::string::npos && where == 0' expression. The expression is excessive or contains a misprint. dbgcmd.cpp:70:1 Предупреждение анализатора: V523 The 'then' statement is equivalent to the 'else' statement. mainbook.cpp:450:1, mainbook.cpp:442:1 Анализатор обнаружил ситуацию, когда истинная и ложная ветка оператора if полностью совпадают. В отчёте также были ещё 8 похожих мест: V523 The 'then' statement is equivalent to the 'else' statement. art_metro.cpp:289:1, art_metro.cpp:287:1 V523 The 'then' statement is equivalent to the 'else' statement. clStatusBar.cpp:151:1, clStatusBar.cpp:147:1 V523 The 'then' statement is equivalent to the 'else' statement. php_workspace_view.cpp:1001:1, php_workspace_view.cpp:999:1 V523 The 'then' statement is equivalent to the 'else' statement. art_metro.cpp:334:1, art_metro.cpp:332:1 V523 The 'then' statement is equivalent to the 'else' statement. symboldatabase.cpp:1299:1, symboldatabase.cpp:1297:1 V523 The 'then' statement is equivalent to the 'else' statement. tokenize.cpp:10387:1, tokenize.cpp:10381:1 V523 The 'then' statement is equivalent to the 'else' statement. parser.hpp:155:1, parser.hpp:151:1 V523 The 'then' statement is equivalent to the 'else' statement. sizer_flags_list_view.cpp:125:1, sizer_flags_list_view.cpp:122:1 Предупреждение анализатора: V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 203, 213. new_quick_watch_dlg.cpp:203:1, new_quick_watch_dlg.cpp:213:1 В данном примере в if и в else if передаётся одинаковое условие item.IsOk(). Мы имеем дело с логической ошибкой. Анализатор нашёл ещё 2 похожих места: V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 53, 55. symbol_tree.cpp:53:1, symbol_tree.cpp:55:1 V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 82, 84. symbol_tree.cpp:82:1, symbol_tree.cpp:84:1 Предупреждение анализатора: V614 Uninitialized buffer 'buf' used. Consider checking the first actual argument of the 'Write' function. wxSerialize.cpp:1039:1 Анализатор обнаружил использование неинициализированной переменной buf, что может привести к непредсказуемым результатам. Вот ещё похожие места: V614 Potentially uninitialized pointer 'm_item' used. wxc_aui_tool_stickiness.cpp:8:1 V614 Potentially uninitialized variable 'err' used. cppcheck.cpp:175:1 V614 The 'p' smart pointer is utilized immediately after being declared or reset. It is suspicious that no value was assigned to it. connection_impl.hpp:2200:1 Предупреждение анализатора: V728 An excessive check can be simplified. The '||' operator is surrounded by opposite expressions '!false' and 'false'.  clDebuggerBreakpoint.cpp:26:1 Анализатор обнаружил код, который можно упростить. Слева и справа от оператора '||' стоят противоположные по смыслу выражения. Данный код является избыточным, и его можно упростить, сократив количество проверок: Анализатор нашёл ещё 17 похожих мест, вот некоторые из них: V728 An excessive check can be simplified. The '||' operator is surrounded by opposite expressions '!matcher' and 'matcher'.  ssh_account_info.cpp:108:1 V728 An excessive check can be simplified. The '(A && !B) || (!A && B)' expression is equivalent to the 'bool(A) != bool(B)' expression. assignedfilesmodel.cpp:310:1 V728 An excessive check can be simplified. The '||' operator is surrounded by opposite expressions 'data->m_wxcWidget->IsSizer()' and '!data->m_wxcWidget->IsSizer()'.  wxguicraft_main_view.cpp:530:1 В заключение хочу отметить, что данная статья является моей первой попыткой анализа кода с использованием PVS-Studio, и я получил ценный опыт в области статического анализа кода. Ошибки, выявленные в проекте CodeLite, подтолкнули меня к обращению внимания на важность проведения такого анализа. Авторы программы могут продолжать развивать и совершенствовать код на основе результатов анализа, чтобы предложить пользователю еще более высокое качество программы. Традиционно в конце статьи мы предлагаем попробовать анализатор PVS-Studio. Для Open Source проектов мы также предоставляем бесплатную лицензию. Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Evgenii Feklin. PVS-Studio vs CodeLite: a battle for the perfect code.  Программист и разработчик игр