Представьте, что вы подключаете стороннюю библиотеку, и внезапно некоторые диагностические правила статического анализатора перестают работать. В этой статье мы рассмотрим одну из причин, по которой это может происходить, и предложим эффективные стратегии для решения этой проблемы.

Суть проблемы
Почему некоторые диагностические правила статического анализатора могут исчезать при подключении сторонней библиотеки? Один очевидный ответ: когда библиотека самостоятельно отключает эти правила. Но зачем это делается? На самом деле разработчики библиотеки отключают правила для своего проекта, не учитывая, что в результате они могут быть отключены и у всех разработчиков, использующих библиотеку.
Историческая справка о механизме подавления ложных срабатываний в 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 можно по этой ссылке.
