﻿# Как сторонние библиотеки меняют правила анализа кода

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

![1201_ThirdParty_ru/image1.png](https://import.viva64.com/docx/blog/1201_ThirdParty_ru/image1.png)

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

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

## Историческая справка о механизме подавления ложных срабатываний в PVS\-Studio

Наш продукт начинал с анализа кода на языках C и C\+\+\. Я думаю, ни для кого не секрет, что основная проблема статических анализаторов — в наличии ложных срабатываний, и наш продукт не исключение\. Для её решения предлагаются различные способы подавления ложных срабатываний\.

Первый механизм появился в PVS\-Studio версии 3\.40 \(выпущена 23 ноября 2009 года\)\. Нужно было всего лишь добавить [комментарий](https://pvs-studio.ru/ru/docs/manual/0017/#ID7E38BC743F) следующего вида в конец строки с кодом, на который анализатор генерирует предупреждение:

```cpp
//-Vxxxx
```

Где `xxxx` — номер диагностического правила\. Как только комментарий был добавлен, при следующем запуске анализа срабатывание на этой строке отфильтровывалось из результирующего отчёта\. Аналогичное решение можно найти и в других продуктах, например, [Clang\-Tidy](https://clang.llvm.org/extra/clang-tidy/#suppressing-undesired-diagnostics) \(`// NOLINT`, `// NOLINTNEXTLINE`\)\.

Этот механизм оказался весьма эффективным, и всё работало идеально\. Шло время, пользователи осваивали этот механизм\. Спустя какое\-то время мы начали получать фидбек, что анализатор выдаёт ложные срабатывания на коде, который получается при раскрытии макросов\. Чтобы уменьшить число ложных срабатываний при использовании макросов, в PVS\-Studio версии 4\.13 \(выпущена 11 февраля 2011 года\) был придуман следующий способ:

```cpp
//-V:MACRO_NAME:xxxx
```

Где `MACRO_NAME` — имя макроса в стиле функции, `xxxx` — номер диагностического правила\.

В отличие от предыдущего подхода новый не требовал привязки к строке исходного кода\. Где же тогда нужно написать этот комментарий? Лучшим вариантом было бы его вынесение в какой\-то отдельный [файл настройки](https://pvs-studio.ru/ru/docs/manual/6630/) анализатора\. Однако такая возможность появилась лишь в версии 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`](https://github.com/EpicGames/UnrealEngine/blob/ue5-main/Engine/Source/Runtime/Core/Public/Microsoft/MicrosoftPlatformCodeAnalysis.h), в котором прописаны различные настройки:

<details>
   <summary>Содержимое MicrosoftPlatformCodeAnalysis\\\.h</summary>

```cpp
//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
```


</details>


Разработчики Unreal Engine проделали большую работу по подавлению ложных срабатываний на макросах и другом коде, а также отключили для себя определённый список диагностических правил:

```cpp
//-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)\.

А это значит, что настройки из third\-party компонента начали влиять на ваш анализ\. В случае Unreal Engine большая часть диагностических правил будет просто отключена\. В результате в отчёте вы не получите предупреждений от них, хотя хотели бы их видеть\. И это уже серьёзная проблема, которую надо решить\.

## Решение проблемы

### Исправление ложных срабатываний 

Очевидно, что диагностические правила в сторонних библиотеках отключены не без причины\. Основная проблема заключается в возникновении ложных срабатываний\. Решить её можно путём исправление этих ложных срабатываний разработчиками статического анализатора\. Мы, как разработчики PVS\-Studio, прикладываем много усилий для устранения ложных срабатываний и улучшения качества диагностик\.

### Запрет на чтение настроек анализа из исходного кода

Чтобы проблема не возникала, достаточно предотвратить распространение настроек из исходного кода third\-party компонентов, преимущественно из заголовочных файлов\. Однако такое исправление сломает обратную совместимость\. К тому же, кажется, что разработчикам библиотек, что используют PVS\-Studio, может не понравиться вручную редактировать исходный код и переносить настройки в отдельные файлы\. 

Возможным решением может стать разработка мигратора, который автоматически обработает исходный код и выполнит необходимые изменения\. Тем не менее его создание требует тщательной проработки, чтобы не изменился контекст программы\. Например, мигратор должен уметь различать обычные комментарии в коде и строковые литералы, содержащие такие комментарии\.

Как видно, задача не из простых, но, возможно, в будущем мы сможем прийти к такому решению\.

### Игнорировать настройки из third\-party кода

Одним из возможных решений является игнорирование настроек из third\-party кода\. Нам нужен функционал, который позволит игнорировать отключенные диагностические правила из сторонних библиотек в наших проектах\. Изначально мы планировали расширить функциональность флага [`--exclude-path`](https://pvs-studio.ru/ru/docs/manual/6640/), который исключает из анализа указанные файлы и каталоги, добавив возможность одновременно запрещать применение настроек\. Однако мы отказались от этой идеи из\-за опасений по поводу нарушения обратной совместимости для пользователей\.

В итоге было принято решение добавить специальный флаг в ядро C и C\+\+ анализатора, а также в утилиту `pvs-studio-analyzer`:

```cpp
--analysis-paths mode=path
```

* `mode` — это набор из следующих значений:
  * `skip-analysis` — исключает из анализа указанные файлы и директории;
  * `skip-settings` — игнорирует чтение настроек из указанных файлов и директорий;
  * `skip` — объединяет функционал режимов `skip-analysis` и `skip-settings`\.
* `path` — это файлы и каталоги, к которым будут применяться настройки\.



Вот примеры:

```cpp
--analysis-paths skip-analysis=*/third-party/*
--analysis-paths skip-settings=*/third-party/*
--analysis-paths skip=*/third-party/*
```

Также мы предоставили возможность прописывать этот флаг в файлы конфигурации `pvsconfig` следующим способом: 

```cpp
//V_ANALYSIS_PATHS mode=path
```

Варианты использования:

```cpp
//V_ANALYSIS_PATHS skip-analysis=*/third-party/*
//V_ANALYSIS_PATHS skip-settings=*/third-party/*
//V_ANALYSIS_PATHS skip=*/third-party/*
```

В один флаг через символ ` ; ` можно задавать несколько значений:

```cpp
--analysis-paths skip-analysis=*/third-party/*;skip-settings=*/test/*
```

Или в `pvsconfig`:

```cpp
//V_ANALYSIS_PATHS skip-analysis=*/third-party/*;skip-settings=*/test/*
```

Рассмотрим, как можно применить новый режим совместно с Unreal Engine\. Существуют два сценария проверки проектов на основе Unreal Engine: через UnrealBuildTool или утилиту мониторинга компиляции \(CLMonitoring\)\.

**Анализ через утилиту UnrealBuildTool**\. Для вашего удобства теперь UnrealBuildTool автоматически передаёт в анализатор флаг `--analysis-paths` с нужными режимами и путями, если вы собираете проект с флагом [`-StaticAnalyzerProjectOnly`](https://pvs-studio.ru/ru/docs/manual/0043/#analyze_project_only), который запускает анализ пользовательских проектов, игнорируя модуль ядра Unreal Engine\. Функционал начнёт работать начиная с релизов PVS\-Studio 7\.34 и Unreal Engine 5\.5\.2\.

**Анализ посредством мониторинга**\. Создайте файл с расширением `pvsconfig` со следующей настройкой:

```cpp
//V_ANALYSIS_PATHS skip-settings=*\UE*\Engine\Source\*
```

В приложении `C and C++ Compiler Monitoring UI` перед запуском мониторинга укажите путь до `pvsconfig` файла в поле ввода:

![1201_ThirdParty_ru/image2.png](https://import.viva64.com/docx/blog/1201_ThirdParty_ru/image2.png)

Если используется консольная утилита `CLMonitoring`, то необходимо передать флаг `-c` с путём до `pvsconfig` файла:

```cpp
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 можно по этой [ссылке](https://pvs-studio.ru/ru/pvs-studio/try-free/)\.