Skip to content
C++
Analysis & Formatting
Updated 2026-07-14T00:00:00.000Z

PVS-Studio — Deep Static Analysis for C++

PVS-Studio reference: running analysis, suppressing false positives, CI integration, and understanding diagnostic categories V501–V2xxx.

TL;DR

PVS-Studio is a commercial static analyzer with a free tier for open-source projects. It performs deep interprocedural analysis to catch real bugs that survive code review and unit tests: copy-paste errors, null dereferences, integer overflow, use-after-free, and more.

  • Deeper analysis than clang-tidy or cppcheck — follows data flow across function boundaries
  • MISRA C/C++ and OWASP compliance checking built in
  • Free license available for open-source, academic, and community projects
  • SARIF output for native GitHub Code Scanning integration

Licensing and Free Tier

License typeConditions
CommercialPaid per developer seat; supports all features
Open-sourceFree; request at pvs-studio.com/open-source-license
AcademicFree for universities and non-commercial research
TrialAnalyzes everything but limits output to first 10 warnings per file

To activate an open-source or trial license:

bash
pvs-studio-analyzer credentials YOUR_NAME YOUR_KEY -o pvs-studio.lic

The license file is picked up automatically from the current directory or ~/.config/PVS-Studio/.


Installation

Linux (.deb / .rpm)

bash
# Debian/Ubuntu
wget -q https://files.pvs-studio.com/pvs-studio-latest.deb
sudo dpkg -i pvs-studio-latest.deb

# RHEL/Fedora
wget -q https://files.pvs-studio.com/pvs-studio-latest.rpm
sudo rpm -i pvs-studio-latest.rpm

After installation the key tools are on PATH:

  • pvs-studio-analyzer — runs analysis
  • plog-converter — converts binary .log output to text, HTML, XML, SARIF, etc.

Windows

Download the Visual Studio plugin or standalone installer from pvs-studio.com/downloads. The plugin adds an integrated PVS-Studio menu to Visual Studio 2019/2022.

For command-line use on Windows, PVS-Studio_Cmd.exe accepts a .sln or .vcxproj file:

powershell
PVS-Studio_Cmd.exe ^
    --target MyProject.sln ^
    --configuration Release ^
    --output pvs.log

macOS

bash
# Download the .pkg from pvs-studio.com/downloads and install, then:
brew install bear  # needed for Makefile-based projects

Docker

bash
docker pull pvsstudio/pvs-studio:latest

# Mount your source and run inside the container
docker run --rm \
    -v $(pwd):/workspace \
    -w /workspace \
    pvsstudio/pvs-studio:latest \
    pvs-studio-analyzer analyze \
        -f build/compile_commands.json \
        -o pvs.log \
        -j$(nproc)

Basic Analysis with CMake

CMake's compile_commands.json is the preferred input — it provides exact compiler flags per translation unit.

bash
# Step 1: generate compile_commands.json
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build

# Step 2: run analysis
pvs-studio-analyzer analyze \
    -f build/compile_commands.json \
    -o pvs-report.log \
    -j$(nproc)

# Step 3: convert to human-readable text
plog-converter \
    -a GA:1,2,3 \
    -t errorfile \
    -o pvs-report.txt \
    pvs-report.log

# Or produce an HTML report (self-contained, good for local review)
plog-converter \
    -a GA:1,2,3 \
    -t html \
    -o pvs-report.html \
    pvs-report.log

The -a GA:1,2,3 flag selects General Analysis at all three confidence levels. To limit to high-confidence warnings only, use -a GA:1.


Analysis with Makefile / Bear

When a project uses plain make (no CMake), use bear to intercept the build and generate compile_commands.json:

bash
# Install bear
sudo apt install bear        # Debian/Ubuntu
brew install bear            # macOS

# Intercept the build
bear -- make -j$(nproc)

# compile_commands.json is now in the current directory
pvs-studio-analyzer analyze \
    -f compile_commands.json \
    -o out.log \
    -j$(nproc)

plog-converter -a GA:1,2 -t errorfile -o out.txt out.log

Analysis with compile_commands.json (direct)

If your build system already produces compile_commands.json (Meson, Bazel with --experimental_generate_json_compilation_database, etc.) just point PVS-Studio at it:

bash
pvs-studio-analyzer analyze \
    -f path/to/compile_commands.json \
    -o pvs.log

Understanding Diagnostic Categories

PVS-Studio diagnostics follow the pattern VXXX where the leading digit(s) identify the ruleset:

RangeRulesetWhat it catches
V1xxGeneral Analysis (GA)Logic errors, dead code, questionable constructs
V2xxGeneral Analysis (GA)Null dereferences, uninitialized memory, OOB
V3xx64-bit migrationPointer truncation, sign-extension issues
V5xxMISRA C/C++MISRA rule violations
V6xxOWASPSecurity-relevant patterns (injection, format string)
V7xxGeneral Analysis (GA)Copy-paste detection, identical branches
V10xxGeneral Analysis (GA)Lifetime, use-after-free, dangling references
V20xxCustomers' requestsDiagnostics added at user request

Each diagnostic also carries a level (1–3) reflecting confidence:

  • Level 1 — high confidence; almost always a real bug
  • Level 2 — medium confidence; likely a bug but context-dependent
  • Level 3 — low confidence; informational, review manually

Common Findings with Examples

V501 — Identical sub-expressions

cpp
// Typo: both sides of == compare x to itself
if (a.x == a.x)   // should be a.x == b.x
    return true;

V512 — memset on a non-POD type

cpp
std::string name;
memset(&name, 0, sizeof(name));  // UB: stomps internal state of std::string

V522 — Potential null pointer dereference after dynamic_cast

cpp
Base* base = getBase();
auto* derived = dynamic_cast<Derived*>(base);
derived->process();  // derived is null if cast fails

Fix:

cpp
if (auto* derived = dynamic_cast<Derived*>(base))
    derived->process();

V547 — Expression is always true/false

cpp
unsigned int x = getValue();
if (x >= 0)          // always true for unsigned
    doSomething();

V601 — Implicit bool-to-int conversion in return

cpp
int enabled() {
    return flag == STATE_ACTIVE;  // returns 0 or 1, may be unintentional
}

V611 — Memory allocated with new[], freed with delete

cpp
int* arr = new int[64];
delete arr;    // UB: should be delete[]

V776 — Potentially infinite loop (flag not volatile)

cpp
bool stop = false;

void worker() {
    while (!stop);   // optimizer may cache stop in a register
}

Fix: use std::atomic<bool> or volatile bool.

V1004 — Pointer used outside the null-check scope

cpp
if (ptr != nullptr) {
    log("ptr is valid");
}
ptr->doWork();   // ptr used here, outside the guard — may be null

V2010 — Suspicious integer shift

cpp
int64_t mask = 1 << 40;   // 1 is int (32-bit); shift > 31 is UB
// Fix: 1LL << 40

Suppressing False Positives

Inline suppression (single line)

cpp
int result = legacyFunc(); //-V610

Block suppression

cpp
//-V::512,611
char buf[64];
memset(buf, 0, sizeof(buf));
//+V::512,611

Suppress file (pvs-studio.suppress)

Generated by pvs-studio-analyzer suppress; stores per-warning fingerprints so suppressed findings are not shown even when line numbers shift:

cpp
// DO NOT EDIT — auto-generated suppress file
V501:/home/user/project/src/algo.cpp:42
V522:/home/user/project/src/render.cpp:188

Apply at conversion time:

bash
plog-converter \
    -a GA:1,2 \
    -t errorfile \
    --suppress-file pvs-studio.suppress \
    -o filtered.txt \
    pvs.log

CMake-level exclusion of third-party directories

Exclude paths entirely so PVS-Studio never analyzes vendored code:

bash
pvs-studio-analyzer analyze \
    -f build/compile_commands.json \
    -o pvs.log \
    --exclude-path third_party/ \
    --exclude-path build/_deps/ \
    -j$(nproc)

Baseline / Suppress File Workflow

On large existing codebases, create a baseline on the first run to silence all pre-existing warnings. From that point on, only new warnings introduced by fresh commits are reported — useful for incremental adoption.

bash
# 1. First-ever run: capture all existing warnings
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build
pvs-studio-analyzer analyze -f build/compile_commands.json -o baseline.log -j$(nproc)

# 2. Create suppress file from all current findings
pvs-studio-analyzer suppress --project=. baseline.log
# Writes pvs-studio.suppress

# 3. Commit pvs-studio.suppress to version control
git add pvs-studio.suppress
git commit -m "chore: add PVS-Studio baseline suppress file"

# 4. Subsequent CI runs: only new warnings appear
pvs-studio-analyzer analyze -f build/compile_commands.json -o new.log -j$(nproc)
pvs-studio-analyzer filter-suppressed pvs-studio.suppress new.log ci.log
plog-converter -a GA:1,2 -t errorfile -o ci-report.txt ci.log

As you fix pre-existing warnings, remove them from the suppress file to ratchet quality up over time.


CI Integration — GitHub Actions

Basic job

yaml
name: Static Analysis

on: [push, pull_request]

jobs:
  pvs-studio:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install PVS-Studio
        run: |
          wget -q https://files.pvs-studio.com/pvs-studio-latest.deb
          sudo dpkg -i pvs-studio-latest.deb

      - name: Configure and build
        run: |
          cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
          cmake --build build -j$(nproc)

      - name: Activate license
        run: |
          pvs-studio-analyzer credentials \
            "${{ secrets.PVS_NAME }}" \
            "${{ secrets.PVS_KEY }}" \
            -o pvs-studio.lic

      - name: Run PVS-Studio
        run: |
          pvs-studio-analyzer analyze \
            -f build/compile_commands.json \
            -o pvs.log \
            -j$(nproc)

      - name: Convert to text report
        run: |
          plog-converter -a GA:1,2 -t errorfile -o pvs-report.txt pvs.log
          cat pvs-report.txt

      - name: Upload report artifact
        uses: actions/upload-artifact@v4
        with:
          name: pvs-studio-report
          path: pvs-report.txt

With GitHub Code Scanning (SARIF)

SARIF output lets GitHub display PVS-Studio findings inline on pull requests under the Security tab:

yaml
      - name: Convert to SARIF
        run: |
          plog-converter \
            -a GA:1,2 \
            -t sarif \
            -o pvs.sarif \
            pvs.log

      - name: Upload SARIF to GitHub Code Scanning
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: pvs.sarif
        if: always()   # upload even when analysis finds errors

Incremental check (new warnings only)

yaml
      - name: Filter suppressed baseline
        run: |
          pvs-studio-analyzer filter-suppressed \
            pvs-studio.suppress pvs.log filtered.log
          plog-converter -a GA:1,2 -t errorfile -o filtered.txt filtered.log
          # Fail CI if any new warnings remain
          grep -q "No messages generated" filtered.txt || \
            (cat filtered.txt && exit 1)

CMake Integration (Analysis as Build Step)

You can wire PVS-Studio into CMake so that cmake --build triggers analysis automatically:

cmake
# CMakeLists.txt
find_program(PVS_STUDIO_ANALYZER pvs-studio-analyzer)
find_program(PLOG_CONVERTER plog-converter)

if(PVS_STUDIO_ANALYZER AND PLOG_CONVERTER)
    option(ENABLE_PVS_STUDIO "Run PVS-Studio on every build" OFF)

    if(ENABLE_PVS_STUDIO)
        set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

        add_custom_target(pvs_analysis ALL
            COMMAND ${PVS_STUDIO_ANALYZER} analyze
                -f ${CMAKE_BINARY_DIR}/compile_commands.json
                -o ${CMAKE_BINARY_DIR}/pvs.log
                -j ${CMAKE_BUILD_PARALLEL_LEVEL}
            COMMAND ${PLOG_CONVERTER}
                -a GA:1,2 -t errorfile
                -o ${CMAKE_BINARY_DIR}/pvs-report.txt
                ${CMAKE_BINARY_DIR}/pvs.log
            DEPENDS ${PROJECT_NAME}
            COMMENT "Running PVS-Studio static analysis..."
        )
    endif()
endif()

Enable with:

bash
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DENABLE_PVS_STUDIO=ON
cmake --build build

Output Format Reference

plog-converter supports several -t targets:

TargetDescription
errorfileGCC-style file:line: warning: ... (works in most editors)
htmlSelf-contained HTML report with filtering UI
xmlXML (can be consumed by Jenkins Warnings NG plugin)
sarifSARIF 2.1.0 (GitHub Code Scanning, Azure DevOps)
csvComma-separated; good for spreadsheet review
tasksQt Creator task list format
teamcityTeamCity service messages

Filter by analysis group and level with -a:

bash
# GA level 1 and 2 only (recommended for CI gate)
plog-converter -a GA:1,2 -t errorfile -o report.txt pvs.log

# GA + MISRA level 1 only
plog-converter -a GA:1,MISRA:1 -t errorfile -o report.txt pvs.log

# Everything
plog-converter -a GA:1,2,3,64:1,2,3,OP:1,2,3,CS:1,2,3,MISRA:1,2,3 \
    -t html -o report.html pvs.log

Comparison: PVS-Studio vs clang-tidy vs cppcheck

FeaturePVS-Studioclang-tidycppcheck
Analysis depthDeep (interprocedural, data-flow)Moderate (AST + CFG)Moderate (token + CFG)
False positive rateLowMediumLow–Medium
SpeedModerateFastFast
CostCommercial (free OSS tier)FreeFree
MISRA C/C++ supportYesNoPartial (addon)
OWASP supportYesNoNo
SARIF outputYesVia wrapperVia converter
IDE integrationVS, CLion, Rider, QtCreatorVS Code, CLion, EmacsCLion, VS Code
Interprocedural analysisYesLimitedLimited
CI integrationYesYesYes
Copy-paste detectionYes (V7xx)NoNo

Choose PVS-Studio when:

  • You need MISRA or OWASP compliance reports
  • The codebase is large and mature (interprocedural analysis pays off)
  • False positive noise from clang-tidy or cppcheck is too high

Combine all three for best coverage: clang-tidy catches modernization opportunities during development, cppcheck is fast and free, PVS-Studio runs in CI for deep coverage.


Practical Tips

Start with level 1 only. Run plog-converter -a GA:1 in CI first. Fix everything it finds before enabling level 2. Level 3 is mostly informational.

Exclude generated code. Protobuf, Qt MOC, and similar generated headers produce many false positives. Use --exclude-path or add them to the suppress file during baseline creation.

Use --incremental mode for fast PR checks. PVS-Studio can cache analysis results per translation unit and only re-analyze files that changed:

bash
pvs-studio-analyzer analyze \
    -f build/compile_commands.json \
    -o pvs.log \
    --incremental \
    --incremental-db pvs-cache/ \
    -j$(nproc)

Investigate V1xx before V3xx. V3xx (64-bit issues) matters for porting; if you are targeting only 64-bit platforms already, defer those until you need them.

Treat suppresses as debt. Track suppress file size in CI (wc -l pvs-studio.suppress) and set a ratchet: the suppress file must not grow larger on main.


See Also

  • clang-tidy — fast AST-based checks, style enforcement, modernize transforms
  • cppcheck — free, no compilation needed, good for legacy code
  • sanitizers — runtime detection of UB, memory errors, data races
  • static-analysis — overview comparing multiple tools
  • address-sanitizer — memory error detection at runtime
Edit on GitHubUpdated 2026-07-14T00:00:00.000Z