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

Суть проблемы

Почему некоторые диагностические правила статического анализатора могут исчезать при подключении сторонней библиотеки? Один очевидный ответ: когда библиотека самостоятельно отключает эти правила. Но зачем это делается? На самом деле разработчики библиотеки отключают правила для своего проекта, не учитывая, что в результате они могут быть отключены и у всех разработчиков, использующих библиотеку.

Историческая справка о механизме подавления ложных срабатываний в 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 можно по этой ссылке.

Комментарии (0)