يكون تحليل الكود الثابت أكثر فاعلية عند إجراء تغييرات على مشروع ، نظرًا لأن إصلاح الأخطاء يكون دائمًا أكثر صعوبة في المستقبل من منع حدوثها في المراحل المبكرة. نواصل توسيع خيارات استخدام PVS-Studio في أنظمة التطوير المستمر وإظهار كيفية إعداد تحليل طلبات السحب باستخدام وكلاء مستضافين ذاتيًا في Microsoft Azure DevOps ، باستخدام مثال لعبة Minetest.
باختصار حول ما نتعامل معه
Minetest عبارة عن محرك ألعاب متعدد المنصات مفتوح المصدر يحتوي على حوالي 200000 سطر من كود C و C ++ و Lua. يسمح لك بإنشاء أوضاع لعب مختلفة في مساحة فوكسل. يدعم تعدد اللاعبين ، والكثير من تعديلات المجتمع. يتم استضافة مستودع المشروع هنا: https://github.com/minetest/minetest .
تم استخدام الأدوات التالية لإعداد بحث منتظم عن الأخطاء:
PVS-Studio هو محلل أكواد ثابت في C و C ++ و C # و Java للعثور على الأخطاء وعيوب الأمان.
Azure DevOps هو نظام أساسي قائم على السحابة يوفر القدرة على تطوير التطبيقات وتشغيلها وتخزين البيانات على الخوادم البعيدة.
يمكنك استخدام الأجهزة الظاهرية لنظامي التشغيل Windows و Linux لتنفيذ مهام التطوير في Azure. ومع ذلك ، فإن تشغيل الوكلاء على الأجهزة المحلية له عدة مزايا مهمة:
- يمكن أن يكون لدى المضيف المحلي موارد أكثر من Azure VM ؛
- لا "يختفي" الوكيل بعد انتهاء مهمته ؛
- القدرة على تخصيص البيئة بشكل مباشر ، والمزيد من التحكم المرن في عمليات البناء ؛
- تخزين الملفات الوسيطة محليًا له تأثير إيجابي على سرعة البناء ؛
- يمكنك إكمال أكثر من 30 مهمة شهريًا مجانًا.
الاستعداد لاستخدام وكيل مستضاف ذاتيًا
تم وصف عملية البدء في Azure بالتفصيل في المقالة " PVS-Studio Goes to the Clouds: Azure DevOps " ، لذا سأنتقل مباشرة إلى إنشاء وكيل مستضاف ذاتيًا.
لكي يكون للوكلاء الحق في الاتصال بمجمعات المشاريع ، فإنهم يحتاجون إلى رمز وصول خاص. يمكنك الحصول عليه من صفحة "رموز الوصول الشخصية" ، في قائمة "إعدادات المستخدم".
بعد النقر على "رمز مميز جديد" ، تحتاج إلى تحديد اسم وتحديد قراءة مجموعات الوكيل وإدارتها (قد تحتاج إلى توسيع القائمة الكاملة من خلال "إظهار كافة النطاقات").
تحتاج إلى نسخ الرمز المميز ، حيث لن يعرضه Azure بعد الآن ، وسيتعين عليك إنشاء رمز جديد.
سيتم استخدام حاوية Docker القائمة على Windows Server Core كعامل. المضيف هو كمبيوتر العمل الخاص بي على Windows 10 x64 مع Hyper-V.
أولاً ، تحتاج إلى توسيع مقدار مساحة القرص المتوفرة لحاويات 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
ستكون النتيجة نظام بناء يعتمد على MSBuild لـ C ++ ، مع Chocolatey لتثبيت PVS-Studio و CMake و Git. للإدارة المريحة للمكتبات التي يعتمد عليها المشروع ، تم إنشاء Vcpkg. وكذلك يتم تنزيل أحدث إصدار من Azure Pipelines Agent.
لتهيئة الوكيل من ملف 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
الوكيل قيد التشغيل وجاهز لأداء المهام.
تشغيل التحليل على وكيل ذاتي الاستضافة
لتحليل العلاقات العامة ، يتم إنشاء خط أنابيب جديد بالبرنامج النصي التالي:
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'
سيتم تشغيل هذا البرنامج النصي عند استلام العلاقات العامة وسيتم تنفيذه على الوكلاء المعينين إلى التجمع الافتراضي. ما عليك سوى منحه إذنًا للعمل مع هذا المجمع.
يحفظ البرنامج النصي قائمة الملفات المتغيرة التي تم الحصول عليها باستخدام git diff. ثم يتم تحديث التبعيات ، ويتم إنشاء حل المشروع من خلال CMake ، ويتم بناؤه.
إذا كان البناء ناجحًا ، فسيبدأ تحليل الملفات التي تم تغييرها (علامة '-f diff-files.txt') ، متجاهلاً المشاريع الإضافية التي أنشأها CMake (حدد المشروع المطلوب فقط مع علامة "-S minetest"). لتسريع البحث عن الروابط بين ملفات الرأس وملفات المصدر C ++ ، يتم إنشاء ذاكرة تخزين مؤقت خاصة ، والتي سيتم تخزينها في دليل منفصل (علامة '-DC: \ caches').
وبالتالي ، يمكننا الآن تلقي تقارير حول تحليل التغييرات في المشروع.
كما قيل في بداية المقال ، فإن المكافأة السارة لاستخدام الوكلاء المستضافين ذاتيًا هي تسريع ملحوظ في تنفيذ المهام بسبب التخزين المحلي للملفات الوسيطة.
تم العثور على بعض الأخطاء في Minetest
الكتابة فوق النتيجة
V519 يتم تعيين قيم لمتغير "color_name" مرتين على التوالي. ربما هذا خطأ. تحقق من الأسطر: 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 أن التعبير "الأقرب_emergefull_d == -1" دائمًا صحيح. زبونيفاس. 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 ....
}
لا يتغير متغير الأقرب_emergefull_d أثناء عملية الحلقة ، ولا يؤثر فحصه على تنفيذ الخوارزمية. إما أن هذا ناتج عن لصق نسخ غير دقيق ، أو أنهم نسوا إجراء بعض العمليات الحسابية به.
V560 جزء من التعبير الشرطي خطأ دائمًا: y> max_spawn_y. 262.cpp
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 " قبل التكرار التالي للحلقة. ستعود المقارنة اللاحقة والعكسية دائمًا إلى خطأ ولا تؤثر بشكل عام على نتيجة اختبار الحالة.
تحقق فقدان المؤشر
V595 من مؤشر "m_client" تم استخدامه من قبل تم التحقق من IT WAS مقابل 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 ، يتم فحصه إذا لم يكن فارغًا باستخدام ماكرو التأكيد . لكن هذا ينطبق فقط على بناء التصحيح. يتم استبدال هذا الاحتياط ، عند البناء في الإصدار ، بدمية ، وهناك خطر إلغاء الإشارة إلى مؤشر فارغ.
بت أم لا قليلا؟
V616 يتم استخدام الثابت "(FT_RENDER_MODE_NORMAL)" المسمى بالقيمة 0 في عملية البت. 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 ، ويمكن إزالة فرع else .
تقريب القسمة الصحيحة
V636 تم تحويل التعبير "rect.getHeight () / 16" ضمنيًا من النوع "int" إلى النوع "float". ضع في اعتبارك استخدام نوع صريح من النوع المصبوب لتجنب فقد جزء كسري. مثال: 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);
}
ترجع الأحرف المستقيمة قيمًا صحيحة. تتم كتابة نتيجة قسمة الأعداد الصحيحة إلى متغير النقطة العائمة ، ويتم فقد الجزء الكسري. يبدو أن هناك أنواع بيانات غير متطابقة في هذه الحسابات.
عبارات
تفريع التسلسل المشبوه V646 ضع في الاعتبار فحص منطق التطبيق. من المحتمل أن تكون الكلمة الرئيسية "else" مفقودة. تريجن. 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 في خوارزمية توليد الشجرة. في الوسط، وبعد ذلك إذا كتلة على نفس الخط مع إغلاق قوسين من سابقة آخر . ربما تعمل الشفرة بشكل صحيح: قبل هذا إذا -a ، يتم إنشاء كتل الجذع ، ثم الأوراق ؛ أو ربما فاتهم الآخر . بالتأكيد لا يمكن قول هذا إلا من قبل المطور.
فحص تخصيص الذاكرة غير الصحيح
V668 لا معنى لاختبار مؤشر "السحب" مقابل القيمة الفارغة ، حيث تم تخصيص الذاكرة باستخدام عامل التشغيل "الجديد". سيتم إنشاء الاستثناء في حالة حدوث خطأ في تخصيص الذاكرة. اللعبة. 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;
}
}
}
في حالة جديدة فشل لإنشاء كائن، و الأمراض المنقولة جنسيا :: bad_alloc سيتم طرح استثناء ، ويجب أن يتم التعامل معها من قبل محاولة اللحاق كتلة. والتحقق من هذا النموذج غير مجدي.
قراءة مصفوفة خارج
الحدود V781 يتم فحص قيمة الفهرس "i" بعد استخدامها. ربما هناك خطأ في منطق البرنامج. 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 من المحلل. للحصول على ترخيص Enterprise التجريبي ، يرجى الإشارة إليه في التعليقات عند طلب ترخيص على صفحة التنزيل . يمكنك معرفة المزيد حول الاختلاف بين التراخيص في صفحة طلب PVS-Studio .
إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فيرجى استخدام رابط الترجمة: Alexey Govorov. PVS-Studio: تحليل طلبات السحب في Azure DevOps باستخدام وكلاء مستضافين ذاتيًا .