Imagine you add a third-party library and suddenly some diagnostic rules of the static analyzer stop working. In this article, we'll look at one of the reasons why this can happen and offer some effective strategies for fixing the issue.
Why some diagnostic rules of the static analyzer may disappear when one enables a third-party library? The single and obvious answer is that the library disables the rules on its own. Why? In fact, the library developers may disable the rules for their own project, without considering that it may affect all developers who use the library as well.
Our product started as a tool for analyzing C and C++ code. I think it's no secret that the main issue with static analyzers is false positives, and PVS-Studio is no exception. To address this issue, we offer several methods of false positive suppression.
The first mechanism was introduced in PVS-Studio 3.40 (released on November 23, 2009). A user only needed to add a comment of the following type at the end of the code line where the analyzer issued a warning:
//-Vxxxx
The xxxx
is the number of the diagnostic rule. Once the comment had been added, the next time the analysis was run, the warning for that line was filtered out of the resulting report. You can find a similar solution in other products such as Clang-Tidy (// NOLINT
, // NOLINTNEXTLINE
).
This mechanism proved to be very effective and everything worked perfectly fine. As time went on, users had mastered it. After a while we started getting feedback that the analyzer was issuing false positives for code generated when macros were expanded. To reduce the number of false positives when using macros, we came up with the following solution in PVS-Studio 4.13 (released on February 11, 2011):
//-V:MACRO_NAME:xxxx
MACRO_NAME
is the name of the function-like macro and xxxx
is the number of the diagnostic rule.
Unlike the previous approach, the new one didn't require binding to a line of source code. Where was the right place for the comment then? The best option would be to put it in a separate file of the analyzer settings. However, this feature appeared only in PVS-Studio 6.04 (released on May 16, 2016). Up to that point, there were two possible locations for the comment:
Let's remember this crucial moment, we'll come back to it.
Since then, the mechanism has undergone numerous enhancements:
//-V::C++
, //-V::C#
, ...//-V::GA
, //-V::OP
, ....//-V::number:level
.//-V::number::{substring}
.However, one thing remained unchanged: users could still write such comments both in header files and in compiled files. Unfortunately, the solution adopted 13 years ago carried a fatal error. While writing such a comment in a compiled file is relatively harmless, in the case of header files, it can very easily shoot you in the foot. Moreover, this thing is easy to overlook.
Note. The issue is specific to the C and C++ code analyzers. You can fine-tune other analyzers only using special files.
How can this shoot you in the feet? Let's look at an example from Unreal Engine. It contains an interesting file called MicrosoftPlatformCodeAnalysis.h
, where various settings are specified:
//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
The developers of Unreal Engine have done a great job of suppressing false positives in macros and other code, disabling a certain list of diagnostic rules for their own use:
//-V::505,542,581,601,623,668,677,690,704,719,720,730,735,751,....
A lot of companies use Unreal Engine to develop games. When they use PVS-Studio to analyze their own projects, the suppress file may be implicitly included into their compiled files. For example, it's enough to enable the basic CoreMinimal.h
header file for this to happen.
This means the settings from the third-party component are now influencing the user's analysis. In case of Unreal Engine projects, most of the diagnostic rules will simply be disabled. As a result, devs won't get warnings from these rules in the report, even though they'd like to. This is a serious issue that needs to be addressed.
Obviously, diagnostic rules in third-party libraries are disabled for a reason—primarily to avoid false positives. The root issue lies in their occurrence. Static analyzer developers can fix it by resolving the underlying issues. We, the PVS-Studio developers, work hard to eliminate false positives and enhance the quality of diagnostic rules.
To avoid the issue, all the user has to do is ensure that the settings don't spread from the source code of third-party components, mainly from header files. However, such a fix would break backward compatibility. It also seems that library developers who use PVS-Studio might be reluctant to manually edit source code and move settings to separate files.
A possible solution is to develop a migrator that will automatically process source code and make the necessary changes. However, creating it requires careful thought to ensure that the context of a program isn't altered. For example, the migrator must be capable of distinguishing between regular comments in code and string literals that contain such comments.
Clearly, the task isn't easy, but perhaps we can come to such a solution in the future.
One possible solution is to ignore settings from the third-party code. We needed a feature that makes it possible to ignore disabled diagnostic rules from third-party libraries in our projects. Our original plan was to extend the functionality of the ‑‑exclude-path
flag, which excludes specified files and directories from the analysis—by adding the option to prevent the settings from being applied. However, we abandoned this idea due to concerns about breaking backwards compatibility for users.
As a result, we decided to add a special flag into the C and C++ analyzer core and also into the pvs-studio-analyzer
utility:
--analysis-paths mode=path
mode
is a set of the following values:skip-analysis
excludes specified files and directories from the analysis;skip-settings
ignores reading settings from specified files and directories;skip
combines the features of the skip-analysis
and skip-settings
modes.path
represents files and directories to which the settings will be applied.Here are some examples:
--analysis-paths skip-analysis=*/third-party/*
--analysis-paths skip-settings=*/third-party/*
--analysis-paths skip=*/third-party/*
We have also provided the capability to write this flag to the pvsconfig
configuration files in the following way:
//V_ANALYSIS_PATHS mode=path
Here are the options of how you can use it:
//V_ANALYSIS_PATHS skip-analysis=*/third-party/*
//V_ANALYSIS_PATHS skip-settings=*/third-party/*
//V_ANALYSIS_PATHS skip=*/third-party/*
Using the ;
character, you can set several values in one flag:
--analysis-paths skip-analysis=*/third-party/*;skip-settings=*/test/*
Or in pvsconfig
:
//V_ANALYSIS_PATHS skip-analysis=*/third-party/*;skip-settings=*/test/*
Let's see how you can leverage the new mode with Unreal Engine. Two scenarios for analyzing Unreal Engine projects exist: you can use UnrealBuildTool or the compilation monitoring utility (CLMonitoring).
Analyzing via the UnrealBuildTool utility: for your convenience, UnrealBuildTool now automatically passes the ‑‑analysis-paths
flag with the correct modes and paths to the analyzer if you build a project with the -StaticAnalyzerProjectOnly
flag. This flag runs the user project analysis, ignoring the Unreal Engine core module. The feature will become available in PVS-Studio 7.34 and Unreal Engine 5.5.2.
Analyzing via monitoring: create a file that has the pvsconfig
extension with the following configuration:
//V_ANALYSIS_PATHS skip-settings=*\UE*\Engine\Source\*
In the C and C++ Compiler Monitoring UI
application, before you start the monitoring process, specify the path to the pvsconfig
file in the input field:
If you're using the CLMonitoring
console utility, pass the -c
flag with the path to the pvsconfig
file:
CLMonitor.exe monitor
%YOUR_BUILD_COMMAND%
CLMonitor.exe analyze -l "path/to/report.plog" ^
-c "path/to/settings.pvsconfig"
We've looked into an important issue related to the disappearance of PVS-Studio diagnostic rules when third-party libraries are enabled. In the past, this limitation prevented developers from detecting potential errors in their code, which negatively affected code quality in projects. It also posed challenges for newcomers exploring the analyzer, as not all diagnostic capabilities of PVS-Studio were fully accessible.
The introduction of new features to ignore settings from third-party code offers developers greater control over code analysis. We hope these enhancements will make the development process more efficient and secure.
Click here to try PVS-Studio for free.