Analisis kode statis paling efektif saat membuat perubahan pada proyek, karena bug selalu lebih sulit diperbaiki di masa mendatang daripada mencegahnya terjadi di tahap awal. Kami terus memperluas opsi untuk menggunakan PVS-Studio dalam sistem pengembangan berkelanjutan dan menunjukkan cara menyiapkan analisis permintaan tarik menggunakan agen yang dihosting sendiri di Microsoft Azure DevOps, menggunakan contoh game Minetest.
Secara singkat tentang apa yang kita hadapi
Minetest adalah mesin game lintas platform open source yang berisi sekitar 200.000 baris kode C, C ++ dan Lua. Ini memungkinkan Anda membuat mode permainan yang berbeda di ruang voxel. Mendukung multipemain, dan banyak mod komunitas. Repositori proyek dihosting di sini: https://github.com/minetest/minetest .
Alat berikut digunakan untuk menyiapkan pencarian kesalahan biasa:
PVS-Studio adalah penganalisis kode statis di C, C ++, C #, dan Java untuk menemukan kesalahan dan cacat keamanan.
Azure DevOps adalah platform berbasis cloud yang menyediakan kemampuan untuk mengembangkan, menjalankan aplikasi, dan menyimpan data di server jarak jauh.
Anda dapat menggunakan mesin virtual Windows dan Linux untuk melakukan tugas pengembangan di Azure. Namun, menjalankan agen pada perangkat keras lokal memiliki beberapa keuntungan yang signifikan:
- Localhost mungkin memiliki lebih banyak sumber daya daripada Azure VM;
- Agen tidak "menghilang" setelah menyelesaikan tugasnya;
- Kemampuan untuk menyesuaikan lingkungan secara langsung, dan kontrol yang lebih fleksibel atas proses pembuatan;
- Menyimpan file perantara secara lokal memiliki efek positif pada kecepatan build;
- Anda dapat menyelesaikan lebih dari 30 tugas per bulan secara gratis.
Bersiap menggunakan agen yang dihosting sendiri
Proses memulai di Azure dijelaskan secara mendetail di artikel " PVS-Studio Menuju Awan: Azure DevOps ", jadi saya akan langsung membuat agen yang dihosting sendiri.
Agar agen memiliki hak untuk terhubung ke kumpulan proyek, mereka memerlukan Token Akses khusus. Anda bisa mendapatkannya di halaman "Personal Access Tokens", di menu "User settings".

Setelah mengklik "Token baru", Anda perlu menentukan nama dan memilih Baca & kelola Kumpulan Agen (Anda mungkin perlu memperluas daftar lengkap melalui "Tampilkan semua cakupan").

Anda perlu menyalin token, karena Azure tidak akan lagi menampilkannya, dan Anda harus membuat yang baru.

Kontainer Docker berbasis Windows Server Core akan digunakan sebagai agen. Host adalah komputer kerja saya pada Windows 10 x64 dengan Hyper-V.
Pertama, Anda perlu menambah jumlah ruang disk yang tersedia untuk kontainer Docker.
Di Windows, Anda perlu memodifikasi file 'C: \ ProgramData \ Docker \ config \ daemon.json' sebagai berikut:
{
"registry-mirrors": [],
"insecure-registries": [],
"debug": true,
"experimental": false,
"data-root": "d:\\docker",
"storage-opts": [ "size=40G" ]
}
Untuk membuat image Docker untuk agen dengan sistem build dan semua yang Anda butuhkan, di direktori 'D: \ docker-agent', tambahkan file Docker dengan konten berikut:
# 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
Hasilnya adalah sistem build berbasis MSBuild untuk C ++, dengan Chocolatey untuk menginstal PVS-Studio, CMake, dan Git. Untuk manajemen pustaka yang nyaman tempat proyek bergantung, Vcpkg dibangun. Dan juga versi terbaru dari Azure Pipelines Agent diunduh.
Untuk menginisialisasi agen dari file Docker ENTRYPOINT, skrip PowerShell 'entrypoint.ps1' dipanggil, yang mana Anda perlu menambahkan URL "organisasi" proyek, token kumpulan agen, dan parameter lisensi 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
}
Perintah untuk membangun citra dan memulai agen:
docker build -t azure-agent -m 4GB .
docker run -id --name my-agent -m 4GB --cpu-count 4 azure-agent

Agen sedang berjalan dan siap melakukan tugas.

Menjalankan analisis pada agen yang dihosting sendiri
Untuk analisis PR, pipeline baru dibuat dengan skrip berikut:

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'
Skrip ini akan berjalan ketika PR diterima dan akan dijalankan pada agen yang ditugaskan ke kumpulan default. Anda hanya perlu memberinya izin untuk bekerja dengan pangkalan ini.


Skrip menyimpan daftar file yang diubah yang diperoleh menggunakan git diff. Kemudian dependensi diperbarui, solusi proyek dihasilkan melalui CMake, dan dibuat.
Jika build berhasil, analisis file yang diubah akan dimulai (tandai '-f diff-files.txt'), dengan mengabaikan project tambahan yang dibuat oleh CMake (pilih hanya project yang diperlukan dengan flag '-S minetest'). Untuk mempercepat pencarian tautan antara header dan file sumber C ++, cache khusus dibuat, yang akan disimpan dalam direktori terpisah (tandai '-DC: \ caches').
Dengan demikian, kami sekarang dapat menerima laporan tentang analisis perubahan dalam proyek.


Seperti yang dikatakan di awal artikel, bonus menyenangkan menggunakan agen yang dihosting sendiri adalah akselerasi yang nyata dari eksekusi tugas karena penyimpanan lokal file perantara.

Beberapa bug ditemukan di Minetest
Hasil
penimpaan V519 Variabel 'color_name' diberi nilai dua kali berturut-turut. Mungkin ini salah. Periksa baris: 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;
....
}
Fungsi ini harus mengurai nama warna dengan parameter transparansi (misalnya, Hijau # 77 ) dan mengembalikan kodenya. Bergantung pada hasil pemeriksaan kondisi, hasil pemisahan baris atau salinan argumen fungsi diteruskan ke variabel color_name . Namun, bukan string yang dihasilkan itu sendiri yang diubah menjadi huruf kecil, tetapi argumen asli. Akibatnya, tidak dapat ditemukan dalam kamus warna jika parameter transparansi ada. Kami dapat memperbaiki baris ini seperti ini:
color_name = lowercase(color_name);
Pemeriksaan kondisi yang tidak
perlu V547 Ekspresi 'terdekat_emergefull_d == -1' selalu benar. 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 ....
}
The nearest_emergefull_d variabel tidak berubah selama operasi lingkaran, dan pemeriksaan yang tidak mempengaruhi eksekusi algoritma. Bisa jadi ini adalah hasil dari copy-paste yang tidak akurat, atau mereka lupa melakukan perhitungan dengannya.
V560 Bagian dari ekspresi kondisional selalu salah: 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;
}
....
}
Nilai variabel ' y ' diperiksa sebelum iterasi loop berikutnya. Perbandingan selanjutnya, sebaliknya, akan selalu menghasilkan nilai salah dan, secara umum, tidak mempengaruhi hasil uji kondisi.
Hilangnya pemeriksaan penunjuk
V595 dari The 'm_client' penunjuk TELAH Digunakan sebelum ITU diverifikasi Terhadap nullptr. Periksa baris: 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);
}
Sebelum mengakses m_client pointer, itu diperiksa jika tidak nol menggunakan menegaskan makro . Tapi ini hanya berlaku untuk build debug. Tindakan pencegahan seperti itu, saat membangun hingga rilis, diganti dengan dummy, dan ada risiko dereferensi penunjuk nol.
Sedikit atau tidak?
V616 Konstanta bernama '(FT_RENDER_MODE_NORMAL)' dengan nilai 0 digunakan dalam operasi bitwise. 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; // <=
}
Makro FT_LOAD_TARGET_NORMAL meluas ke nol, dan bitwise "atau" tidak akan menyetel tanda apa pun di load_flags , cabang lain dapat dihapus.
Pembulatan integer divisi
V636 Ekspresi 'rect.getHeight () / 16' secara implisit dilemparkan dari tipe 'int' ke tipe 'float'. Pertimbangkan untuk menggunakan cor tipe eksplisit untuk menghindari hilangnya bagian pecahan. Contoh: 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);
}
Getters rect mengembalikan nilai integer. Hasil pembagian bilangan bulat dituliskan ke variabel floating point, dan bagian pecahannya hilang. Sepertinya ada tipe data yang tidak cocok dalam penghitungan ini.
Pernyataan percabangan urutan yang mencurigakan
V646 Pertimbangkan Memeriksa logika aplikasi. Mungkin kata kunci 'lain' tidak ada. 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()) { // <=
....
}
....
}
Berikut adalah urutan else-if dalam algoritma pembuatan pohon. Di tengah, berikutnya jika blok adalah pada baris yang sama dengan penutupan kurung dari sebelumnya lain . Mungkin kodenya bekerja dengan benar: sebelum ini jika -a, blok trunk dibuat, dan kemudian daunnya; atau mungkin mereka melewatkan yang lain . Tentunya ini hanya bisa dikatakan oleh pengembangnya.
Pemeriksaan alokasi memori yang salah
V668 Tidak ada gunanya menguji penunjuk 'awan' terhadap nol, karena memori dialokasikan menggunakan operator 'baru'. Pengecualian akan dibuat jika terjadi kesalahan alokasi memori. 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;
}
}
}
Jika new gagal membuat objek, pengecualian std :: bad_alloc akan dilempar dan harus ditangani oleh blok coba-tangkap . Dan memeriksa formulir ini tidak berguna.
Membaca array di luar
batas V781 Nilai indeks 'i' diperiksa setelah digunakan. Mungkin ada kesalahan dalam logika program. 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);
}
Elemen array diakses sebelum memeriksa indeks, yang dapat menyebabkan kesalahan. Mungkin ada baiknya menulis ulang loop seperti ini:
for (i=0; i < n; ++i) // <=
if (!array[i] || !other[i] || array[i] != other[i])
return false;
Kesalahan Lainnya
Artikel ini membahas tentang menganalisis permintaan tarik di Azure DevOps dan tidak dimaksudkan untuk memberikan gambaran umum rinci tentang kesalahan dalam proyek Minetest. Berikut ini beberapa cuplikan kode yang menurut saya menarik. Kami menyarankan agar penulis proyek tidak mengikuti artikel ini untuk memperbaiki kesalahan dan melakukan analisis yang lebih menyeluruh terhadap peringatan yang akan dikeluarkan oleh PVS-Studio.
Kesimpulan
Berkat konfigurasi yang fleksibel dalam mode baris perintah, analisis PVS-Studio dapat disematkan dalam berbagai skenario CI / CD. Dan memanfaatkan sumber daya yang tersedia dengan benar akan terbayar dalam peningkatan produktivitas.
Perlu dicatat bahwa mode pemeriksaan permintaan tarik hanya tersedia di penganalisis edisi Enterprise. Untuk mendapatkan demo lisensi Enterprise, harap tunjukkan di komentar saat meminta lisensi di halaman download . Detail selengkapnya tentang perbedaan antara lisensi dapat ditemukan di halaman pesanan PVS-Studio .

Jika Anda ingin berbagi artikel ini dengan audiens berbahasa Inggris, silakan gunakan tautan terjemahan: Alexey Govorov. PVS-Studio: menganalisis permintaan tarik di Azure DevOps menggunakan agen yang dihosting sendiri .