Представьте, что вы подключаете стороннюю библиотеку, и внезапно некоторые диагностические правила статического анализатора перестают работать. В этой статье мы рассмотрим одну из причин, по которой это может происходить, и предложим эффективные стратегии для решения этой проблемы.
Суть проблемы
Почему некоторые диагностические правила статического анализатора могут исчезать при подключении сторонней библиотеки? Один очевидный ответ: когда библиотека самостоятельно отключает эти правила. Но зачем это делается? На самом деле разработчики библиотеки отключают правила для своего проекта, не учитывая, что в результате они могут быть отключены и у всех разработчиков, использующих библиотеку.
Историческая справка о механизме подавления ложных срабатываний в PVS-Studio
Наш продукт начинал с анализа кода на языках C и C++. Я думаю, ни для кого не секрет, что основная проблема статических анализаторов — в наличии ложных срабатываний, и наш продукт не исключение. Для её решения предлагаются различные способы подавления ложных срабатываний.
Первый механизм появился в 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
— номер диагностического правила.
В отличие от предыдущего подхода новый не требовал привязки к строке исходного кода. Где же тогда нужно написать этот комментарий? Лучшим вариантом было бы его вынесение в какой-то отдельный файл настройки анализатора. Однако такая возможность появилась лишь в версии 6.04 PVS-Studio (выпущена 16 мая 2016 года). А до того момента было предложено два варианта, где можно расположить этот комментарий:
непосредственно в проверяемом компилируемом файле;
в одном из заголовочных файлов, которые включает проверяемый компилируемый файл.
Запомним этот судьбоносный момент, мы к нему ещё вернёмся.
С тех пор этот механизм расширялся ещё не раз:
отключение анализа конкретного языка:
//-V::C++
,//-V::C#
, ...отключение группы диагностических правил:
//-V::GA
,//-V::OP
, ...исключение из результатов анализа предупреждений определённого уровня:
//-V::number:level
исключение предупреждений по подстроке в сообщении:
//-V::number::{substring}
и др.
Однако неизменным осталось одно — такие комментарии по-прежнему можно было писать как в заголовочных файлах, так и в компилируемых. К сожалению, решение, принятое 13 лет назад, содержало роковую ошибку. Если написание такого комментария в компилируемом файле ещё не выглядит настолько вредным, то в случае с заголовочными файлами это может очень легко выстрелить вам в ноги. И вы легко можете этого не заметить.
Примечание. Проблема существует только для анализатора кода на языках C и C++. Остальные анализаторы можно донастроить только при помощи специальных файлов.
Реальный пример
Как эта ситуация может выстрелить вам в ноги? Рассмотрим пример из Unreal Engine. Там есть интересный файл под названием MicrosoftPlatformCodeAnalysis.h
, в котором прописаны различные настройки:
Содержимое MicrosoftPlatformCodeAnalysis.h
//PVS-Studio settings:
//-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
.
А это значит, что настройки из third-party компонента начали влиять на ваш анализ. В случае Unreal Engine большая часть диагностических правил будет просто отключена. В результате в отчёте вы не получите предупреждений от них, хотя хотели бы их видеть. И это уже серьёзная проблема, которую надо решить.
Решение проблемы
Исправление ложных срабатываний
Очевидно, что диагностические правила в сторонних библиотеках отключены не без причины. Основная проблема заключается в возникновении ложных срабатываний. Решить её можно путём исправление этих ложных срабатываний разработчиками статического анализатора. Мы, как разработчики PVS-Studio, прикладываем много усилий для устранения ложных срабатываний и улучшения качества диагностик.
Запрет на чтение настроек анализа из исходного кода
Чтобы проблема не возникала, достаточно предотвратить распространение настроек из исходного кода third-party компонентов, преимущественно из заголовочных файлов. Однако такое исправление сломает обратную совместимость. К тому же, кажется, что разработчикам библиотек, что используют PVS-Studio, может не понравиться вручную редактировать исходный код и переносить настройки в отдельные файлы.
Возможным решением может стать разработка мигратора, который автоматически обработает исходный код и выполнит необходимые изменения. Тем не менее его создание требует тщательной проработки, чтобы не изменился контекст программы. Например, мигратор должен уметь различать обычные комментарии в коде и строковые литералы, содержащие такие комментарии.
Как видно, задача не из простых, но, возможно, в будущем мы сможем прийти к такому решению.
Игнорировать настройки из third-party кода
Одним из возможных решений является игнорирование настроек из third-party кода. Нам нужен функционал, который позволит игнорировать отключенные диагностические правила из сторонних библиотек в наших проектах. Изначально мы планировали расширить функциональность флага --exclude-path
, который исключает из анализа указанные файлы и каталоги, добавив возможность одновременно запрещать применение настроек. Однако мы отказались от этой идеи из-за опасений по поводу нарушения обратной совместимости для пользователей.
В итоге было принято решение добавить специальный флаг в ядро 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
, который запускает анализ пользовательских проектов, игнорируя модуль ядра Unreal Engine. Функционал начнёт работать начиная с релизов PVS-Studio 7.34 и Unreal Engine 5.5.2.
Анализ посредством мониторинга. Создайте файл с расширением pvsconfig
со следующей настройкой:
//V_ANALYSIS_PATHS skip-settings=*\UE*\Engine\Source\*
В приложении 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 можно по этой ссылке.