Уявіть, що ви додаєте бібліотеку третьої сторони, і раптом деякі діагностичні правила статичного аналізатора перестають працювати. У цій статті ми розглянемо одну з причин, чому це може статися, і запропонуємо ефективні стратегії для виправлення цієї проблеми.
Опис проблеми
Чому деякі діагностичні правила статичного аналізатора можуть зникати після активації бібліотеки третьої сторони? Єдина і очевидна відповідь — бібліотека вимикає ці правила сама. Чому? Насправді розробники бібліотеки можуть вимикати правила для свого проєкту, не враховуючи, що це може вплинути на всіх розробників, які використовують цю бібліотеку.
Фонові відомості про механізм пригнічення хибних спрацьовувань у PVS-Studio
Наш продукт починав як інструмент для аналізу коду на C та C++. Думаю, це не секрет, що основною проблемою статичних аналізаторів є хибні спрацьовування, і PVS-Studio не є винятком. Щоб вирішити цю проблему, ми пропонуємо кілька методів пригнічення хибних спрацьовувань.
Перший механізм був введений у PVS-Studio 3.40 (випущено 23 листопада 2009 року). Користувачеві потрібно було лише додати коментар наступного типу в кінець рядка коду, де аналізатор видав попередження:
//-Vxxxx
xxxx
— це номер діагностичного правила. Після додавання коментаря, наступного разу при запуску аналізу, попередження для цього рядка фільтрувалося з отриманого звіту. Схоже рішення можна знайти в інших продуктах, таких як Clang-Tidy (// NOLINT
, // NOLINTNEXTLINE
).
Цей механізм виявився дуже ефективним, і все працювало чудово. З часом користувачі освоїли його. Через деякий час ми почали отримувати відгуки, що аналізатор видає хибні спрацьовування для коду, який генерується при розширенні макросів. Для зменшення кількості хибних спрацьовувань при використанні макросів, ми придумали таке рішення в PVS-Studio 4.13 (випущено 11 лютого 2011 року):
//-V:MACRO_NAME:xxxx
MACRO_NAME
— це назва макросу, схожого на функцію, а xxxx
— номер діагностичного правила.
На відміну від попереднього підходу, новий не вимагав прив'язки до рядка вихідного коду. Де ж тоді було розташовувати коментар? Найкращим варіантом було б помістити його в окремий файл налаштувань аналітора. Однак ця функція з'явилася тільки в PVS-Studio 6.04 (випущено 16 травня 2016 року). До цього часу існували два можливих місця для коментаря:
· безпосередньо у компільованому файлі, що перевіряється;
· в одному з заголовкових файлів, який включено в перевіряний компільований файл.
Давайте запам'ятаємо цей важливий момент, повернемося до нього.
З того часу механізм зазнав численних удосконалень:
· вимкнення мовоспецифічного аналізу: //-V::C++
, //-V::C#
, …
· вимкнення групи діагностичних правил: //-V::GA
, //-V::OP
, ….
· виключення попереджень певного рівня з результатів аналізу: //-V::number:level
.
· виключення попереджень за підрядком у повідомленні: //-V::number::{substring}
.
· та інші.
Однак одне залишилося незмінним: користувачі все ще могли писати такі коментарі як у заголовкових файлах, так і в компільованих файлах. На жаль, рішення, прийняте 13 років тому, мало фатальну помилку. Поки написання такого коментаря в компільованому файлі є відносно безпечним, то у випадку заголовкових файлів це може дуже легко призвести до неприємностей. Більше того, це легко можна не помітити.
Примітка. Проблема специфічна для аналізаторів коду на C та C++. Інші аналізатори можна налаштувати лише за допомогою спеціальних файлів.
Приклад з реального світу
Як це може призвести до проблем? Давайте розглянемо приклад з Unreal Engine.
Він містить цікавий файл під назвою [MicrosoftPlatformCodeAnalysis.h](https://github.com/EpicGames/UnrealEngine/blob/ue5-main/Engine/Source/Runtime/Core/Public/Microsoft/MicrosoftPlatformCodeAnalysis.h)
, де зазначені різні налаштування:
//PVS-Studio налаштування:
//-V::505,542,581,601,623,668,677,690,704,719,720,730,735,751,....
//-V:TRYCOMPRESSION:519,547
//-V:check(:501,547,560,605
//-V:checkSlow(:547
//-V:checkf(:510
//-V:checkfSlow(:547
//-V:dtAssert(:568
//-V:rcAssert(:568
//-V:GET_FUNCTION_NAME_CHECKED:521
//-V:ENABLE_TEXT_ERROR_CHECKING_RESULTS:560
//-V:ENABLE_LOC_TESTING:560,617
//-V:WITH_EDITOR:560
//-V:UE_LOG_ACTIVE:560
//-V:verify:501
//-V:%n:609
//-V:UE_BUILD_SHIPPING:501
//-V:WITH_EDITOR:501
//-V:TestTrueExpr:501
//-V:PLATFORM_:517,547
//-V:ensureMsgf:562
//-V:WindowsMinorVersion:547
//-V:Import.XObject:547,560
//-V:MotionControllerComponent:547,560
//-V:AddUninitialized(sizeof(void*)/:514
//-V:TestTrue:678
//-V:SetViewTarget:678
//-V:Slot:607
//-V:RESIDENCY_CHECK_RESULT:607
//-V:bHitTesting:581
//-V:OptionalType:580
//-V:GetNextNode:681
//-V:ConvertToAbsolutePathForExternalAppFor:524
//-V:CopySingleValue:524
//-V:bTimeLimitReached:560
//-V:bRedirectionAllowed:560
//-V:NumFailures:560
//-V:bAllowInstantToolTips:560
//-V:bIsRealTime:560
//-V:Position:519
//-V:DynamicParameterValue[ParameterIndex]:557
//-V:ViewIndex:557
//-V:DeviceIndex:557
//-V:Interpolation:560
//-V:storePortals:560
//-V:bDefaultShouldBeMaximized:560
//-V:bAllowPerfHUD:560
//-V:bUseClientStorage:560
//-V:bCalculateThisMapping:560
//-V:bDebugSelectedTaskOnly:560
//-V:bDebugSelectedTaskOnly:560
//-V:bIsPreview:560
//-V:bSupportsFastClear:560
//-V:bUseAPILibaries:560
//-V:bUseCachedBlobs:560
//-V:bWireframe:560
//-V:Num():560
//-V:PLATFORM_MAC:560
//-V:Particle->Size.Z:570
//-V:ComponentMaskParameter:601
//-V:Format(:601
//-V:SelectedEmitter:519
//-V:MAX_VERTS_PER_POLY:512
//-V:127:547
//-V:0x7F:547
//-V:WARN_COLOR:547
//-V:<<:614
//-V:FT_LOAD_TARGET_NORMAL:616
//-V:OPENGL_PERFORMANCE_DATA_INVALID:564
//-V:HLSLCC_VersionMajor:616
//-V:bIgnoreFieldReferences:519
//-V:CachedQueryInstance:519
//-V:MeshContext:519
//-V:bAffectedByMarquee:519
//-V:CopyCompleteValueFromScriptVM:524
//-V:OnStopWatchingPin:524
//-V:GetMinChildNodes:524
//-V:FromWorldMatrix:524
//-V:RemoveSelectedActorsFromSelectedLayer_CanExecute:524
//-V:NotifyLevelRemovedFromWorld:524
//-V:SPAWN_INIT:595
//-V:BEGIN_UPDATE_LOOP:595
//-V:OPENGL_PERFORMANCE_DATA_INVALID:560
//-V:bSkipTranslationTrack:560
//-V:NumSelected>0:581
//-V:bTryPerTrackBitwiseCompression:581
//-V:DataStripped:581
//-V:FromInt:601
//-V:UE_CLOG(:501,560
//-V:UE_LOG(:501,510,560
//-V:UGL_REQUIRED_VOID:501
//-V:AnimScriptInstance:595
//-V:Driver:595
//-V:PSceneAsync->lockWrite:595
//-V:Context.World():595
//-V:UNIT_LOG:595
//-V:ensure(:595
//-V:ALLOCATE_VERTEX_DATA_TEMPLATE:501
//-V:UGL_REQUIRED:501
//-V:DEBUG_LOG_HTTP:523
//-V:GIsEditor:560
//-V:bHasEditorToken:560
//-V:GEventDrivenLoaderEnabled:501
//-V:WALK_TO_CHARACTER:519
//-V:IMPLEMENT_AI_INSTANT_TEST:773
//-V:ENABLE_VERIFY_GL:564
//-V:INC_MEMORY_STAT_BY:568
//-V:DEC_MEMORY_STAT_BY:568
//-V:Key():568
//-V:Modify:762
//-V:GetTransitionList:762
//-V:Execute:768
//-V:LAUNCHERSERVICES_SHAREABLEPROJECTPATHS:768
//-V:SELECT_STATIC_MESH_VERTEX_TYPE:622
//-V:GET_FUNCTION_NAME_CHECKED:685
//-V:This(:678
//-V:state->error:649
//-V:ProjModifiers:616
//-V:PERF_DETAILED_PER_CLASS_GC_STATS:686
//-V:FMath:656
//-V:->*:607
//-V:GENERATED_UCLASS_BODY:764
//-V:CalcSegmentCostOnPoly:764
//-V:DrawLine:764
//-V:vrapi_SubmitFrame:641
//-V:VertexData:773
//-V:Linker:678
//-V:self:678
//-V:AccumulateParentID:678
//-V:FindChar:679
Розробники Unreal Engine зробили чудову роботу щодо придушення помилкових спрацьовувань у макросах та іншому коді, вимикаючи певний список діагностичних правил для власного використання:
//-V::505,542,581,601,623,668,677,690,704,719,720,730,735,751,....
Багато компаній використовують Unreal Engine для розробки ігор. Коли вони використовують PVS-Studio для аналізу своїх проєктів, файл придушення може бути неявно включений у їх зібрані файли.
Наприклад, достатньо включити основний заголовочний файл [CoreMinimal.h](https://dev.epicgames.com/documentation/en-us/unreal-engine/iwyu?application_version=4.27)
, щоб це сталося.
Це означає, що налаштування з компоненту третьої сторони тепер впливають на аналіз користувача. У випадку з проєктами Unreal Engine більшість діагностичних правил буде просто вимкнено. Як результат, розробники не отримають попереджень за цими правилами в звіті, навіть якщо вони хотіли б. Це серйозна проблема, яку потрібно вирішити.
Вирішення проблеми
Виправлення помилкових спрацьовувань
Очевидно, діагностичні правила у бібліотеках третьої сторони вимкнені не без причини — головним чином для уникнення помилкових спрацьовувань. Корінь проблеми полягає в їх виникненні. Розробники статичних аналізаторів можуть виправити це, вирішивши основні проблеми. Ми, розробники PVS-Studio, активно працюємо над усуненням помилкових спрацьовувань і покращенням якості діагностичних правил.
Заборонити читання налаштувань аналізу з вихідного коду
Щоб уникнути цієї проблеми, користувачеві достатньо просто переконатися, що налаштування не поширюються з вихідного коду компонентів третьої сторони, головним чином з заголовочних файлів. Однак таке виправлення порушило б зворотну сумісність. Крім того, розробники бібліотек, які використовують PVS-Studio, можуть не захотіти вручну редагувати вихідний код і переносити налаштування в окремі файли.
Можливим рішенням є розробка мігрувальника, який автоматично оброблятиме вихідний код і вноситиме необхідні зміни. Однак створення такого інструменту вимагає ретельного планування, щоб гарантувати, що контекст програми не буде змінено. Наприклад, мігрувальник повинен вміти відрізняти звичайні коментарі в коді від рядкових літералів, які містять такі коментарі.
Зрозуміло, завдання не з легких, але, можливо, в майбутньому ми знайдемо таке рішення.
Ігнорування налаштувань з коду третьої сторони
Одним із можливих рішень є ігнорування налаштувань з коду третьої сторони. Нам потрібна функція, яка дозволяє ігнорувати вимкнені діагностичні правила з бібліотек третьої сторони в наших проєктах. Нашим початковим планом було розширити функціональність прапорця [— exclude-path](https://pvs-studio.com/en/docs/manual/6640/)
, який виключає вказані файли та директорії з аналізу, додавши опцію, щоб запобігти застосуванню налаштувань. Однак ми відмовилися від цієї ідеї через побоювання порушити зворотну сумісність для користувачів.
В результаті ми вирішили додати спеціальний прапорець до ядра аналізатора C і C++ а також до утиліти pvs-studio-analyzer
:
--analysis-paths mode=path
mode
— набір таких значень:skip-analysis
виключає вказані файли та директорії з аналізу;skip-settings
ігнорує читання налаштувань з вказаних файлів і директорій;skip
поєднує функціональність режимівskip-analysis
іskip-settings
.path
представляє файли та директорії, до яких застосовуються налаштування.
Ось кілька прикладів:
- analysis-paths skip-analysis=*/third-party/*
- analysis-paths skip-settings=*/third-party/*
- analysis-paths skip=*/third-party/*
Ми також надали можливість записувати цей прапорець у конфігураційні файли pvsconfig
наступним чином:
//V_ANALYSIS_PATHS mode=path
Ось варіанти, як ви можете його використовувати:
//V_ANALYSIS_PATHS skip-analysis=*/third-party/*
//V_ANALYSIS_PATHS skip-settings=*/third-party/*
//V_ANALYSIS_PATHS skip=*/third-party/*
За допомогою символу ;
можна встановити кілька значень в одному прапорці:
--analysis-paths skip-analysis=*/third-party/*;skip-settings=*/test/*
Або в pvsconfig
:
//V_ANALYSIS_PATHS skip-analysis=*/third-party/*;skip-settings=*/test/*
Тепер подивимося, як можна скористатися новим режимом з Unreal Engine.
Існують два сценарії для аналізу проектів Unreal Engine: ви можете використовувати UnrealBuildTool або утиліту моніторингу компіляції (CLMonitoring).
Аналіз через утиліту UnrealBuildTool: для вашої зручності UnrealBuildTool тепер автоматично передає прапорець — analysis-paths
з правильними режимами та шляхами до аналізатора, якщо ви будуєте проект з прапорцем [-StaticAnalyzerProjectOnly](https://pvs-studio.com/en/docs/manual/0043/#analyze_project_only)
. Цей прапорець виконує аналіз проекту користувача, ігноруючи основний модуль Unreal Engine. Ця функція буде доступна в PVS-Studio 7.34 та Unreal Engine 5.5.2.
Аналіз через моніторинг: створіть файл з розширенням pvsconfig
з такою конфігурацією:
//V_ANALYSIS_PATHSource*
У додатку C and C++ Compiler Monitoring UI
перед початком процесу моніторингу вкажіть шлях до файлу pvsconfig
у відповідному полі вводу:
Якщо ви використовуєте консольну утиліту CLMonitoring
, передайте прапорець -c
з шляхом до файлу pvsconfig
:
CLMonitor.exe monitor
%YOUR_BUILD_COMMAND%
CLMonitor.exe analyze -l "path/to/report.plog" ^
-c "path/to/settings.pvsconfig"
Висновок
Ми розглянули важливу проблему, пов'язану з відсутністю правил діагностики PVS-Studio при підключених сторонніх бібліотеках. Раніше це обмеження заважало розробникам виявляти потенційні помилки в коді, що негативно впливало на якість коду в проектах. Це також створювало труднощі для новачків, які вивчали аналізатор, оскільки не всі діагностичні можливості PVS-Studio були повністю доступні.
Впровадження нових можливостей для ігнорування налаштувань стороннього коду дає розробникам більше контролю над аналізом коду. Сподіваємося, що ці покращення зроблять процес розробки більш ефективним та безпечним.
Натисніть тут, щоб спробувати PVS-Studio безкоштовно.
Перекладено з: How third-party libraries change code analysis rules