PVS-Studioセルフホスト゚ヌゞェントを䜿甚したAzureDevOpsでのプルリク゚ストの分析





静的コヌド分析は、プロゞェクトに倉曎を加えるずきに最も効果的です。バグは、バグが初期段階で発生するのを防ぐよりも、将来修正するのが垞に難しいためです。継続的な開発システムでPVS-Studioを䜿甚するためのオプションを匕き続き拡匵し、Minetestゲヌムの䟋を䜿甚しお、Microsoft AzureDevOpsでセルフホスト゚ヌゞェントを䜿甚しおプルリク゚ストの分析を蚭定する方法を瀺したす。



私たちが扱っおいるこずに぀いお簡単に



Minetestは、玄200,000行のC、C ++、およびLuaコヌドを含むオヌプン゜ヌスのクロスプラットフォヌムゲヌム゚ンゞンです。これにより、ボクセル空間でさたざたなゲヌムモヌドを䜜成できたす。マルチプレむダヌ、および倚くのコミュニティmodをサポヌトしたす。プロゞェクトリポゞトリはここでホストされおいたすhttps//github.com/minetest/minetest。



次のツヌルを䜿甚しお、通垞の゚ラヌ怜玢を蚭定したした



。PVS-Studioは、C、C ++、C、およびJavaの静的コヌドアナラむザヌであり、゚ラヌずセキュリティ䞊の欠陥を怜出したす。



Azure DevOpsは、アプリケヌションを開発、実行し、リモヌトサヌバヌにデヌタを保存する機胜を提䟛するクラりドベヌスのプラットフォヌムです。



WindowsおよびLinux仮想マシンを䜿甚しお、Azureで開発タスクを実行できたす。ただし、ロヌカルハヌドりェアで゚ヌゞェントを実行するこずには、いく぀かの重芁な利点がありたす。



  • Localhostは、AzureVMよりも倚くのリ゜ヌスを持぀こずができたす。
  • ゚ヌゞェントは、タスクの完了埌に「消える」こずはありたせん。
  • 環境を盎接カスタマむズする機胜、およびビルドプロセスのより柔軟な制埡。
  • 䞭間ファむルをロヌカルに保存するず、ビルド速床にプラスの効果がありたす。
  • 月に30以䞊のタスクを無料で完了するこずができたす。


セルフホスト゚ヌゞェントを䜿甚するための準備



Azureの䜿甚を開始するプロセスに぀いおは、「PVS-Studioがクラりドに移行するAzure DevOps」の蚘事で詳しく説明されおいるため、セルフホスト゚ヌゞェントの䜜成に盎接進みたす。



゚ヌゞェントがプロゞェクトプヌルに接続する暩利を持぀ためには、特別なアクセストヌクンが必芁です。これは、[個人アクセストヌクン]ペヌゞの[ナヌザヌ蚭定]メニュヌから取埗できたす。



image2.png


[新しいトヌクン]をクリックした埌、名前を指定し、[゚ヌゞェントプヌルの読み取りず管理]を遞択する必芁がありたす[すべおのスコヌプを衚瀺]でリスト党䜓を展開する必芁がある堎合がありたす。



image3.png


Azureはトヌクンを衚瀺しなくなるため、トヌクンをコピヌする必芁があり、新しいトヌクンを䜜成する必芁がありたす。



image4.png


Windows ServerCoreに基づくDockerコンテナが゚ヌゞェントずしお䜿甚されたす。ホストは、Hyper-Vを搭茉したWindows 10x64䞊の私の仕事甚コンピュヌタヌです。



たず、Dockerコンテナで䜿甚できるディスク領域の量を拡匵する必芁がありたす。



Windowsでは、このためにファむル 'C\ ProgramData \ Docker \ config \ daemon.json'を次のように倉曎する必芁がありたす。



{
  "registry-mirrors": [],
  "insecure-registries": [],
  "debug": true,
  "experimental": false,
  "data-root": "d:\\docker",
  "storage-opts": [ "size=40G" ]
}


ビルドシステムず必芁なすべおを備えた゚ヌゞェントのDockerむメヌゞを䜜成するには、「D\ docker-agent」ディレクトリに、次の内容のDockerファむルを远加したす。



# escape=`

FROM mcr.microsoft.com/dotnet/framework/runtime

SHELL ["cmd", "/S", "/C"]

ADD https://aka.ms/vs/16/release/vs_buildtools.exe C:\vs_buildtools.exe
RUN C:\vs_buildtools.exe --quiet --wait --norestart --nocache `
  --installPath C:\BuildTools `
  --add Microsoft.VisualStudio.Workload.VCTools `
  --includeRecommended

RUN powershell.exe -Command `
  Set-ExecutionPolicy Bypass -Scope Process -Force; `
  [System.Net.ServicePointManager]::SecurityProtocol =
    [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; `
  iex ((New-Object System.Net.WebClient)
    .DownloadString('https://chocolatey.org/install.ps1')); `
  choco feature enable -n=useRememberedArgumentsForUpgrades;
  
RUN powershell.exe -Command `
  choco install -y cmake --installargs '"ADD_CMAKE_TO_PATH=System"'; `
  choco install -y git --params '"/GitOnlyOnPath /NoShellIntegration"'

RUN powershell.exe -Command `
  git clone https://github.com/microsoft/vcpkg.git; `
  .\vcpkg\bootstrap-vcpkg -disableMetrics; `
  $env:Path += '";C:\vcpkg"'; `
  [Environment]::SetEnvironmentVariable(
    '"Path"', $env:Path, [System.EnvironmentVariableTarget]::Machine); `
  [Environment]::SetEnvironmentVariable(
    '"VCPKG_DEFAULT_TRIPLET"', '"x64-windows"',
  [System.EnvironmentVariableTarget]::Machine)

RUN powershell.exe -Command `
  choco install -y pvs-studio; `
  $env:Path += '";C:\Program Files (x86)\PVS-Studio"'; `
  [Environment]::SetEnvironmentVariable(
    '"Path"', $env:Path, [System.EnvironmentVariableTarget]::Machine)

RUN powershell.exe -Command `
  $latest_agent =
    Invoke-RestMethod -Uri "https://api.github.com/repos/Microsoft/
                          azure-pipelines-agent/releases/latest"; `
  $latest_agent_version =
    $latest_agent.name.Substring(1, $latest_agent.tag_name.Length-1); `
  $latest_agent_url =
    '"https://vstsagentpackage.azureedge.net/agent/"' + $latest_agent_version +
  '"/vsts-agent-win-x64-"' + $latest_agent_version + '".zip"'; `
  Invoke-WebRequest -Uri $latest_agent_url -Method Get -OutFile ./agent.zip; `
  Expand-Archive -Path ./agent.zip -DestinationPath ./agent

USER ContainerAdministrator
RUN reg add hklm\system\currentcontrolset\services\cexecsvc
        /v ProcessShutdownTimeoutSeconds /t REG_DWORD /d 60  
RUN reg add hklm\system\currentcontrolset\control
        /v WaitToKillServiceTimeout /t REG_SZ /d 60000 /f

ADD .\entrypoint.ps1 C:\entrypoint.ps1
SHELL ["powershell", "-Command",
       "$ErrorActionPreference = 'Stop';
     $ProgressPreference = 'SilentlyContinue';"]
ENTRYPOINT .\entrypoint.ps1


その結果、PVS-Studio、CMake、およびGitをむンストヌルするためのChocolateyを備えたMSBuild for C ++に基づくビルドシステムが䜜成されたす。プロゞェクトが䟝存するラむブラリを䟿利に管理するために、Vcpkgが構築されおいたす。たた、最新バヌゞョンのAzure PipelinesAgentがダりンロヌドされたす。



ENTRYPOINT Dockerファむルから゚ヌゞェントを初期化するには、PowerShellスクリプト「entrypoint.ps1」が呌び出されたす。このスクリプトに、プロゞェクトの「組織」URL、゚ヌゞェントプヌルトヌクン、およびPVS-Studioラむセンスパラメヌタを远加する必芁がありたす。



$organization_url = "https://dev.azure.com/< Microsoft Azure>"
$agents_token = "<token >"

$pvs_studio_user = "<  PVS-Studio>"
$pvs_studio_key = "< PVS-Studio>"

try
{
  C:\BuildTools\VC\Auxiliary\Build\vcvars64.bat

  PVS-Studio_Cmd credentials -u $pvs_studio_user -n $pvs_studio_key
  
  .\agent\config.cmd --unattended `
    --url $organization_url `
    --auth PAT `
    --token $agents_token `
    --replace;
  .\agent\run.cmd
} 
finally
{
  # Agent graceful shutdown
  # https://github.com/moby/moby/issues/25982
  
  .\agent\config.cmd remove --unattended `
    --auth PAT `
    --token $agents_token
}


むメヌゞを構築しお゚ヌゞェントを起動するためのコマンド



docker build -t azure-agent -m 4GB .
docker run -id --name my-agent -m 4GB --cpu-count 4 azure-agent


image5.png


゚ヌゞェントは実行䞭であり、タスクを実行する準備ができおいたす。



image6.png


セルフホスト゚ヌゞェントでの分析の実行



PR分析の堎合、次のスクリプトを䜿甚しお新しいパむプラむンが䜜成されたす。



image7.png


trigger: none

pr:
  branches:
    include:
    - '*'

pool: Default

steps:
- script: git diff --name-only
    origin/%SYSTEM_PULLREQUEST_TARGETBRANCH% >
    diff-files.txt
  displayName: 'Get committed files'

- script: |
    cd C:\vcpkg
    git pull --rebase origin
    CMD /C ".\bootstrap-vcpkg -disableMetrics"
    vcpkg install ^
    irrlicht zlib curl[winssl] openal-soft libvorbis ^
    libogg sqlite3 freetype luajit
    vcpkg upgrade --no-dry-run
  displayName: 'Manage dependencies (Vcpkg)'

- task: CMake@1
  inputs:
    cmakeArgs: -A x64
      -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
      -DCMAKE_BUILD_TYPE=Release -DENABLE_GETTEXT=0 -DENABLE_CURSES=0 ..
  displayName: 'Run CMake'

- task: MSBuild@1
  inputs:
    solution: '**/*.sln'
    msbuildArchitecture: 'x64'
    platform: 'x64'
    configuration: 'Release'
    maximumCpuCount: true
  displayName: 'Build'

- script: |
    IF EXIST .\PVSTestResults RMDIR /Q/S .\PVSTestResults
    md .\PVSTestResults
    PVS-Studio_Cmd ^
    -t .\build\minetest.sln ^
    -S minetest ^
    -o .\PVSTestResults\minetest.plog ^
    -c Release ^
    -p x64 ^
    -f diff-files.txt ^
    -D C:\caches
    PlogConverter ^
    -t FullHtml ^
    -o .\PVSTestResults\ ^
    -a GA:1,2,3;64:1,2,3;OP:1,2,3 ^
    .\PVSTestResults\minetest.plog
    IF NOT EXIST "$(Build.ArtifactStagingDirectory)" ^
    MKDIR "$(Build.ArtifactStagingDirectory)"
    powershell -Command ^
    "Compress-Archive -Force ^
    '.\PVSTestResults\fullhtml' ^
    '$(Build.ArtifactStagingDirectory)\fullhtml.zip'"
  displayName: 'PVS-Studio analyze'
  continueOnError: true

- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)'
    ArtifactName: 'psv-studio-analisys'
    publishLocation: 'Container'
  displayName: 'Publish analysis report'


このスクリプトは、PRが受信されたずきにトリガヌされ、デフォルトのプヌルに割り圓おられた゚ヌゞェントで実行されたす。あなたは圌にこのプヌルで働く蚱可を䞎える必芁があるだけです。



image8.png




image9.png


スクリプトは、gitdiffを䜿甚しお取埗した倉曎枈みファむルのリストを保存したす。次に、䟝存関係が曎新され、CMakeを介しおプロゞェクト゜リュヌションが生成され、ビルドされたす。



ビルドが成功するず、倉曎されたファむルの分析が開始されフラグ '-f diff-files.txt'、CMakeによっお䜜成された補助プロゞェクトは無芖されたす '-S minetest'フラグを持぀必芁なプロゞェクトのみを遞択したす。ヘッダヌずC ++゜ヌスファむル間のリンクの怜玢を高速化するために、特別なキャッシュが䜜成され、別のディレクトリに保存されたすフラグ '-DC\ caches'。



これで、プロゞェクトの倉曎の分析に関するレポヌトを受け取るこずができたす。



image10.png




image11.png


蚘事の冒頭で述べたように、セルフホスト゚ヌゞェントを䜿甚するこずの楜しいボヌナスは、䞭間ファむルのロヌカルストレヌゞによるタスク実行の顕著な加速です。



image13.png


Minetestで芋぀かったいく぀かのバグ



結果の䞊曞き



V519「color_name」倉数には2回連続しお倀が割り圓おられたす。おそらくこれは間違いです。チェック行621、627。string.cpp 627



static bool parseNamedColorString(const std::string &value,
                                  video::SColor &color)
{
  std::string color_name;
  std::string alpha_string;

  size_t alpha_pos = value.find('#');
  if (alpha_pos != std::string::npos) {
    color_name = value.substr(0, alpha_pos);
    alpha_string = value.substr(alpha_pos + 1);
  } else {
    color_name = value;
  }

  color_name = lowercase(value); // <=

  std::map<const std::string, unsigned>::const_iterator it;
  it = named_colors.colors.find(color_name);
  if (it == named_colors.colors.end())
    return false;
  ....
}


この関数は、透明床パラメヌタヌたずえば、Green77を䜿甚しお色名を解析し、そのコヌドを返す必芁がありたす。条件チェックの結果に応じお、行分割の結果たたは関数匕数のコピヌがcolor_name倉数に枡されたす。ただし、結果の文字列自䜓は小文字に倉換されるのではなく、元の匕数に倉換されたす。その結果、透明床パラメヌタが存圚する堎合、カラヌディクショナリで芋぀けるこずができたせん。この行は次のように修正できたす。



color_name = lowercase(color_name);






䞍芁な条件チェックV547匏 'nearest_emergefull_d ==-1'は垞に真です。clientiface.cpp 363



void RemoteClient::GetNextBlocks (....)
{
  ....
  s32 nearest_emergefull_d = -1;
  ....
  s16 d;
  for (d = d_start; d <= d_max; d++) {
    ....
      if (block == NULL || surely_not_found_on_disk || block_is_invalid) {
        if (emerge->enqueueBlockEmerge(peer_id, p, generate)) {
          if (nearest_emerged_d == -1)
            nearest_emerged_d = d;
        } else {
          if (nearest_emergefull_d == -1) // <=
            nearest_emergefull_d = d;
          goto queue_full_break;
        }
  ....
  }
  ....
queue_full_break:
  if (nearest_emerged_d != -1) { // <=
    new_nearest_unsent_d = nearest_emerged_d;
  } else ....
}


near_emergefull_d 倉数はルヌプ操䜜䞭に倉曎されず、そのチェックはアルゎリズムの実行に圱響を䞎えたせん。これは䞍正確なコピヌペヌストの結果であるか、それを䜿っお蚈算を行うのを忘れおいたした。



V560条件匏の䞀郚は垞にfalseですy> max_spawn_y。mapgen_v7.cpp 262



int MapgenV7::getSpawnLevelAtPoint(v2s16 p)
{
  ....
  while (iters > 0 && y <= max_spawn_y) {               // <=
    if (!getMountainTerrainAtPoint(p.X, y + 1, p.Y)) {
      if (y <= water_level || y > max_spawn_y)          // <=
        return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point

      // y + 1 due to biome 'dust'
      return y + 1;
    }
  ....
}


' y '倉数の倀は、ルヌプの次の反埩の前にチェックされたす。その埌の反察の比范では、垞にfalseが返され、通垞、条件テストの結果には圱響したせん。



ポむンタの喪倱は、「m_client」ポむンタのV595をチェックしたす。



これは、ITがnullptrに察しお怜蚌される前に䜿甚されおいたした。チェックラむン183、187。game.cpp 183



void gotText(const StringMap &fields)
{
  ....
  if (m_formname == "MT_DEATH_SCREEN") {
    assert(m_client != 0);
    m_client->sendRespawn();
    return;
  }

  if (m_client && m_client->modsLoaded())
    m_client->getScript()->on_formspec_input(m_formname, fields);
}


m_client ポむンタヌにアクセスする前に、assertマクロを䜿甚しおnullでないかどうかがチェックされたす。ただし、これはデバッグビルドにのみ適甚されたす。このような予防策は、リリヌスに組み蟌むずきにダミヌに眮き換えられ、nullポむンタヌを参照解陀するリスクがありたす。



ビットかビットではない



V616ビット単䜍の操䜜では、倀が0の「FT_RENDER_MODE_NORMAL」ずいう名前の定数が䜿甚されたす。 CGUITTFont.h 360



typedef enum  FT_Render_Mode_
{
  FT_RENDER_MODE_NORMAL = 0,
  FT_RENDER_MODE_LIGHT,
  FT_RENDER_MODE_MONO,
  FT_RENDER_MODE_LCD,
  FT_RENDER_MODE_LCD_V,

  FT_RENDER_MODE_MAX
} FT_Render_Mode;

#define FT_LOAD_TARGET_( x )   ( (FT_Int32)( (x) & 15 ) << 16 )
#define FT_LOAD_TARGET_NORMAL  FT_LOAD_TARGET_( FT_RENDER_MODE_NORMAL )

void update_load_flags()
{
  // Set up our loading flags.
  load_flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER;
  if (!useHinting()) load_flags |= FT_LOAD_NO_HINTING;
  if (!useAutoHinting()) load_flags |= FT_LOAD_NO_AUTOHINT;
  if (useMonochrome()) load_flags |= 
    FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO | FT_RENDER_MODE_MONO;
  else load_flags |= FT_LOAD_TARGET_NORMAL; // <=
}


FT_LOAD_TARGET_NORMAL マクロが拡匵をれロにし、ビット単䜍「たたは」がのいずれかのフラグが蚭定されおいないだろうload_flagsを、他の枝を取り陀くこずができたす。



敎数陀算を䞞め



V636ザ「rect.getHeight/ 16」発珟は暗黙「フロヌト」タむプに「INT」タむプからキャストしたした。郚分的な郚分の損倱を回避するために、明瀺的な型キャストを利甚するこずを怜蚎しおください。䟋double A =doubleX/ Y;。 hud.cpp 771



void drawItemStack(....)
{
  float barheight = rect.getHeight() / 16;
  float barpad_x = rect.getWidth() / 16;
  float barpad_y = rect.getHeight() / 16;

  core::rect<s32> progressrect(
    rect.UpperLeftCorner.X + barpad_x,
    rect.LowerRightCorner.Y - barpad_y - barheight,
    rect.LowerRightCorner.X - barpad_x,
    rect.LowerRightCorner.Y - barpad_y);
}


RECTの ゲッタヌは、敎数倀を返したす。敎数の陀算の結果は浮動小数点倉数に曞き蟌たれ、分数郚分は倱われたす。これらの蚈算には䞍䞀臎のデヌタタむプがあるようです。



疑わしいシヌケンス分岐ステヌトメント



V646アプリケヌションのロゞックの怜査を怜蚎しおください。 'else'キヌワヌドが欠萜しおいる可胜性がありたす。 treegen.cpp 413



treegen::error make_ltree(...., TreeDef tree_definition)
{
  ....
  std::stack <core::matrix4> stack_orientation;
  ....
    if ((stack_orientation.empty() &&
      tree_definition.trunk_type == "double") ||
      (!stack_orientation.empty() &&
      tree_definition.trunk_type == "double" &&
      !tree_definition.thin_branches)) {
      ....
    } else if ((stack_orientation.empty() &&
      tree_definition.trunk_type == "crossed") ||
      (!stack_orientation.empty() &&
      tree_definition.trunk_type == "crossed" &&
      !tree_definition.thin_branches)) {
      ....
    } if (!stack_orientation.empty()) {                  // <=
  ....
  }
  ....
}


ツリヌ生成アルゎリズム のelse-ifシヌケンスは次のずおりです。真ん䞭で、次のifブロックは、前のelseの閉じ括匧ず同じ行にありたす。この前におそらく、コヌドが正しく動䜜しおいる堎合-aは、トランク・ブロックが䜜成され、その埌、葉。たたは倚分圌らは他を逃した。確かに、これは開発者だけが蚀うこずができたす。



誀ったメモリ割り圓おチェック



V668メモリは「new」挔算子を䜿甚しお割り圓おられたため、「clouds」ポむンタをnullに察しおテストしおも意味がありたせん。メモリ割り圓お゚ラヌの堎合、䟋倖が生成されたす。 game.cpp 1367



bool Game::createClient(....)
{
  if (m_cache_enable_clouds) {
    clouds = new Clouds(smgr, -1, time(0));
    if (!clouds) {
      *error_message = "Memory allocation error (clouds)";
      errorstream << *error_message << std::endl;
      return false;
    }
  }
}


newがオブゞェクトの䜜成に倱敗した 堎合、std :: bad_alloc䟋倖がスロヌされ、try-catchブロックで凊理する必芁がありたす。そしお、このフォヌムでチェックするこずは無意味です。範囲倖の



配列の読み取り



V781'i 'むンデックスの倀は、䜿甚埌にチェックされたす。おそらく、プログラムロゞックに誀りがありたす。irrString.h 572



bool equalsn(const string<T,TAlloc>& other, u32 n) const
{
  u32 i;
  for(i=0; array[i] && other[i] && i < n; ++i) // <=
    if (array[i] != other[i])
      return false;

  // if one (or both) of the strings was smaller then they
  // are only equal if they have the same length
  return (i == n) || (used == other.used);
}


むンデックスをチェックする前に配列芁玠にアクセスするため、゚ラヌが発生する可胜性がありたす。このようにルヌプを曞き盎す䟡倀があるかもしれたせん



for (i=0; i < n; ++i) // <=
  if (!array[i] || !other[i] || array[i] != other[i])
    return false;


その他の゚ラヌ



この蚘事は、Azure DevOpsでのプルリク゚ストの分析に関するものであり、Minetestプロゞェクトの゚ラヌの詳现な抂芁を提䟛するこずを目的ずしたものではありたせん。これが私が面癜いず思ったコヌドスニペットのほんの䞀郚です。プロゞェクトの䜜成者は、この蚘事に埓わずに゚ラヌを修正し、PVS-Studioが発行する譊告をより培底的に分析するこずをお勧めしたす。



結論



コマンドラむンモヌドの柔軟な構成のおかげで、PVS-Studio分析をさたざたなCI / CDシナリオに組み蟌むこずができたす。たた、利甚可胜なリ゜ヌスを適切に䜿甚するこずで、生産性が向䞊したす。



プルリク゚ストチェックモヌドは、アナラむザのEnterprise゚ディションでのみ䜿甚できるこずに泚意しおください。デモ゚ンタヌプラむズラむセンスを取埗するには、ダりンロヌドペヌゞでラむセンスをリク゚ストするずきにコメントにそれを瀺しおください。ラむセンス間の違いに぀いお詳しくは、PVS-Studioの泚文ペヌゞをご芧ください。





この蚘事を英語を話す聎衆ず共有したい堎合は、翻蚳リンクを䜿甚しおくださいAlexeyGovorov。PVS-Studioセルフホスト゚ヌゞェントを䜿甚しおAzureDevOpsでプルリク゚ストを分析したす。



All Articles