Travis CI هي خدمة ويب موزعة لبناء واختبار البرامج التي تستخدم GitHub كاستضافة التعليمات البرمجية المصدر. بالإضافة إلى سيناريوهات التشغيل المذكورة أعلاه، يمكنك إضافة شكرك الخاص إلى خيارات التكوين الشاملة. في هذه المقالة سنقوم بتكوين Travis CI للعمل مع PVS-Studio باستخدام مثال كود PPSSPP.
مقدمة
إعداد ترافيس CI
سنحتاج إلى مستودع على GitHub، حيث يوجد المشروع الذي نحتاجه، بالإضافة إلى مفتاح PVS-Studio (يمكنك الحصول عليه
دعنا نذهب إلى الموقع
للاختبار، قمت بتشعب PPSSPP.
نقوم بتنشيط المستودع الذي نريد جمعه:
في الوقت الحالي، لا يستطيع Travis CI بناء مشروعنا لأنه لا توجد تعليمات للبناء. لذلك حان الوقت للتكوين.
أثناء التحليل، ستكون بعض المتغيرات مفيدة لنا، على سبيل المثال، مفتاح PVS-Studio، والذي سيكون من غير المرغوب فيه تحديده في ملف التكوين. لذلك دعونا نضيف متغيرات البيئة باستخدام إعدادات البناء في Travis CI:
نحن بحاجة إلى:
- PVS_USERNAME - اسم المستخدم
- PVS_KEY - المفتاح
- MAIL_USER - البريد الإلكتروني الذي سيتم استخدامه لإرسال التقرير
- MAIL_PASSWORD - كلمة مرور البريد الإلكتروني
الأخيران اختياريان. سيتم استخدام هذه لإرسال النتائج عن طريق البريد. إذا كنت تريد توزيع التقرير بطريقة أخرى، فلن تحتاج إلى الإشارة إليها.
لذلك، أضفنا متغيرات البيئة التي نحتاجها:
الآن لنقم بإنشاء ملف .travis.yml ووضعه في جذر المشروع. كان لدى PPSSPP بالفعل ملف تكوين لـ Travis CI، ومع ذلك، كان كبيرًا جدًا وغير مناسب تمامًا للمثال، لذلك كان علينا تبسيطه بشكل كبير وترك العناصر الأساسية فقط.
أولاً، دعنا نشير إلى اللغة، وإصدار Ubuntu Linux الذي نريد استخدامه في الجهاز الظاهري، والحزم اللازمة للإنشاء:
language: cpp
dist: xenial
addons:
apt:
update: true
packages:
- ant
- aria2
- build-essential
- cmake
- libgl1-mesa-dev
- libglu1-mesa-dev
- libsdl2-dev
- pv
- sendemail
- software-properties-common
sources:
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:ubuntu-sdk-team/ppa'
جميع الحزم المدرجة مطلوبة حصريًا لـ PPSSPP.
الآن نشير إلى مصفوفة التجميع:
matrix:
include:
- os: linux
compiler: "gcc"
env: PPSSPP_BUILD_TYPE=Linux PVS_ANALYZE=Yes
- os: linux
compiler: "clang"
env: PPSSPP_BUILD_TYPE=Linux
المزيد عن القسم البخور. في Travis CI، هناك طريقتان لإنشاء خيارات البناء: الأولى هي تحديد قائمة من المترجمين، وأنواع أنظمة التشغيل، ومتغيرات البيئة، وما إلى ذلك، وبعد ذلك يتم إنشاء مصفوفة بجميع المجموعات الممكنة؛ والثاني هو إشارة صريحة للمصفوفة. بالطبع، يمكنك الجمع بين هذين النهجين وإضافة حالة فريدة، أو على العكس من ذلك، استبعادها باستخدام القسم منع. يمكنك قراءة المزيد عن هذا في
كل ما تبقى هو تقديم تعليمات التجميع الخاصة بالمشروع:
before_install:
- travis_retry bash .travis.sh travis_before_install
install:
- travis_retry bash .travis.sh travis_install
script:
- bash .travis.sh travis_script
after_success:
- bash .travis.sh travis_after_success
يتيح لك Travis CI إضافة أوامرك الخاصة لمراحل مختلفة من عمر الجهاز الظاهري. قسم before_install يتم تنفيذها قبل تثبيت الحزم. ثم تثبيت، والذي يتبع تثبيت الحزم من القائمة addons.aptالذي أشرنا إليه أعلاه. يتم التجمع نفسه في سيناريو. إذا سار كل شيء على ما يرام، فسنجد أنفسنا فيه after_success (في هذا القسم سنقوم بإجراء تحليل ثابت). هذه ليست كل الخطوات التي يمكن تعديلها، إذا كنت بحاجة إلى المزيد، فعليك البحث فيها
لسهولة القراءة، تم وضع الأوامر في برنامج نصي منفصل .ترافيس.ش، والذي يتم وضعه في جذر المشروع.
لذلك لدينا الملف التالي .travis.yml:
language: cpp
dist: xenial
addons:
apt:
update: true
packages:
- ant
- aria2
- build-essential
- cmake
- libgl1-mesa-dev
- libglu1-mesa-dev
- libsdl2-dev
- pv
- sendemail
- software-properties-common
sources:
- sourceline: 'ppa:ubuntu-toolchain-r/test'
- sourceline: 'ppa:ubuntu-sdk-team/ppa'
matrix:
include:
- os: linux
compiler: "gcc"
env: PVS_ANALYZE=Yes
- os: linux
compiler: "clang"
before_install:
- travis_retry bash .travis.sh travis_before_install
install:
- travis_retry bash .travis.sh travis_install
script:
- bash .travis.sh travis_script
after_success:
- bash .travis.sh travis_after_success
قبل تثبيت الحزم، سنقوم بتحديث الوحدات الفرعية. هذا ضروري لبناء PPSSPP. دعونا نضيف الوظيفة الأولى إلى .ترافيس.ش (لاحظ الامتداد):
travis_before_install() {
git submodule update --init --recursive
}
نأتي الآن مباشرةً إلى إعداد التشغيل التلقائي لـ PVS-Studio في Travis CI. نحتاج أولاً إلى تثبيت حزمة PVS-Studio على النظام:
travis_install() {
if [ "$CXX" = "g++" ]; then
sudo apt-get install -qq g++-4.8
fi
if [ "$PVS_ANALYZE" = "Yes" ]; then
wget -q -O - https://files.viva64.com/etc/pubkey.txt
| sudo apt-key add -
sudo wget -O /etc/apt/sources.list.d/viva64.list
https://files.viva64.com/etc/viva64.list
sudo apt-get update -qq
sudo apt-get install -qq pvs-studio
libio-socket-ssl-perl
libnet-ssleay-perl
fi
download_extract
"https://cmake.org/files/v3.6/cmake-3.6.2-Linux-x86_64.tar.gz"
cmake-3.6.2-Linux-x86_64.tar.gz
}
في بداية الوظيفة travis_install نقوم بتثبيت المترجمين الذين نحتاجهم باستخدام متغيرات البيئة. ثم إذا كان المتغير $PVS_ANALYZE قيمة المتاجر نعم (وقد أشرنا إليه في الباب الحياة الفطرية أثناء تكوين مصفوفة البناء)، نقوم بتثبيت الحزمة pvs-studio. وبالإضافة إلى ذلك، يشار أيضا إلى الحزم libio-socket-ssl-perl и libnet-ssleay-بيرلومع ذلك، فهي مطلوبة لإرسال النتائج بالبريد، لذا فهي ليست ضرورية إذا اخترت طريقة أخرى لتسليم تقريرك.
وظيفة download_extract تنزيل وتفكيك الأرشيف المحدد:
download_extract() {
aria2c -x 16 $1 -o $2
tar -xf $2
}
حان الوقت لوضع المشروع معًا. يحدث هذا في القسم سيناريو:
travis_script() {
if [ -d cmake-3.6.2-Linux-x86_64 ]; then
export PATH=$(pwd)/cmake-3.6.2-Linux-x86_64/bin:$PATH
fi
CMAKE_ARGS="-DHEADLESS=ON ${CMAKE_ARGS}"
if [ "$PVS_ANALYZE" = "Yes" ]; then
CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
fi
cmake $CMAKE_ARGS CMakeLists.txt
make
}
في الواقع، هذا تكوين أصلي مبسط، باستثناء هذه السطور:
if [ "$PVS_ANALYZE" = "Yes" ]; then
CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
fi
في هذا القسم من التعليمات البرمجية وضعنا ل cmake علامة لتصدير أوامر التجميع. يعد هذا ضروريًا لمحلل الكود الثابت. يمكنك قراءة المزيد عن هذا في المقال "
إذا نجح التجمع، فسوف نصل إليه after_success، حيث نقوم بإجراء تحليل ثابت:
travis_after_success() {
if [ "$PVS_ANALYZE" = "Yes" ]; then
pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic
pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic
-o PVS-Studio-${CC}.log
--disableLicenseExpirationCheck
plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html
sendemail -t [email protected]
-u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT"
-m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT"
-s smtp.gmail.com:587
-xu $MAIL_USER
-xp $MAIL_PASSWORD
-o tls=yes
-f $MAIL_USER
-a PVS-Studio-${CC}.log PVS-Studio-${CC}.html
fi
}
دعونا نلقي نظرة فاحصة على السطور التالية:
pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic
pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic
-o PVS-Studio-${CC}.log
--disableLicenseExpirationCheck
plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html
يقوم السطر الأول بإنشاء ملف ترخيص من اسم المستخدم والمفتاح الذي حددناه في البداية عند إعداد متغيرات بيئة Travis CI.
السطر الثاني يبدأ التحليل مباشرة. علَم -ي يحدد عدد المواضيع للتحليل، العلم -ل يشير إلى الترخيص والعلم -o يحدد الملف لسجلات الإخراج، والعلم -تعطيلLicenseExpirationCheck مطلوب للإصدارات التجريبية، لأنه بشكل افتراضي محلل pvs-studio سيحذر المستخدم من أن الترخيص على وشك الانتهاء. لمنع حدوث ذلك، يمكنك تحديد هذه العلامة.
يحتوي ملف السجل على مخرجات أولية لا يمكن قراءتها بدون تحويل، لذا يجب عليك أولاً جعل الملف قابلاً للقراءة. دعونا نمرر السجلات من خلال بلوج المحولوالإخراج هو ملف html.
في هذا المثال، قررت إرسال التقارير عبر البريد باستخدام الأمر ارسل بريد الكتروني.
ونتيجة لذلك، حصلنا على الملف التالي .ترافيس.ش:
#/bin/bash
travis_before_install() {
git submodule update --init --recursive
}
download_extract() {
aria2c -x 16 $1 -o $2
tar -xf $2
}
travis_install() {
if [ "$CXX" = "g++" ]; then
sudo apt-get install -qq g++-4.8
fi
if [ "$PVS_ANALYZE" = "Yes" ]; then
wget -q -O - https://files.viva64.com/etc/pubkey.txt
| sudo apt-key add -
sudo wget -O /etc/apt/sources.list.d/viva64.list
https://files.viva64.com/etc/viva64.list
sudo apt-get update -qq
sudo apt-get install -qq pvs-studio
libio-socket-ssl-perl
libnet-ssleay-perl
fi
download_extract
"https://cmake.org/files/v3.6/cmake-3.6.2-Linux-x86_64.tar.gz"
cmake-3.6.2-Linux-x86_64.tar.gz
}
travis_script() {
if [ -d cmake-3.6.2-Linux-x86_64 ]; then
export PATH=$(pwd)/cmake-3.6.2-Linux-x86_64/bin:$PATH
fi
CMAKE_ARGS="-DHEADLESS=ON ${CMAKE_ARGS}"
if [ "$PVS_ANALYZE" = "Yes" ]; then
CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
fi
cmake $CMAKE_ARGS CMakeLists.txt
make
}
travis_after_success() {
if [ "$PVS_ANALYZE" = "Yes" ]; then
pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic
pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic
-o PVS-Studio-${CC}.log
--disableLicenseExpirationCheck
plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html
sendemail -t [email protected]
-u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT"
-m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT"
-s smtp.gmail.com:587
-xu $MAIL_USER
-xp $MAIL_PASSWORD
-o tls=yes
-f $MAIL_USER
-a PVS-Studio-${CC}.log PVS-Studio-${CC}.html
fi
}
set -e
set -x
$1;
حان الوقت الآن لدفع التغييرات إلى مستودع git، وبعد ذلك سيقوم Travis CI بتشغيل الإنشاء تلقائيًا. انقر على "ppsspp" للانتقال إلى تقارير البناء:
سنرى نظرة عامة على البناء الحالي:
إذا اكتمل البناء بنجاح، فسنتلقى بريدًا إلكترونيًا يتضمن نتائج التحليل الثابت. وبطبيعة الحال، فإن البريد ليس هو الطريقة الوحيدة لتلقي التقرير. يمكنك اختيار أي طريقة تنفيذ. ولكن من المهم أن تتذكر أنه بعد اكتمال الإنشاء، لن يكون من الممكن الوصول إلى ملفات الجهاز الظاهري.
ملخص الخطأ
لقد أكملنا الجزء الأصعب بنجاح. الآن دعونا نتأكد من أن كل جهودنا تستحق العناء. دعونا نلقي نظرة على بعض النقاط المثيرة للاهتمام من تقرير التحليل الثابت الذي وصلتني عبر البريد (لم أشرت إليه عبثًا).
التحسين الخطير
void sha1( unsigned char *input, int ilen, unsigned char output[20] )
{
sha1_context ctx;
sha1_starts( &ctx );
sha1_update( &ctx, input, ilen );
sha1_finish( &ctx, output );
memset( &ctx, 0, sizeof( sha1_context ) );
}
تحذير PVS-Studio:
يوجد هذا الجزء من التعليمات البرمجية في وحدة التجزئة الآمنة، ومع ذلك، فهو يحتوي على ثغرة أمنية خطيرة (
; Line 355
mov r8d, 20
xor edx, edx
lea rcx, QWORD PTR sum$[rsp]
call memset
; Line 356
كل شيء في محله والوظيفة memeset يتم تنفيذه، وبالتالي الكتابة فوق البيانات الهامة في ذاكرة الوصول العشوائي، ومع ذلك، لا نفرح حتى الآن. دعونا نلقي نظرة على قائمة التجميع لإصدار الإصدار مع التحسين:
; 354 :
; 355 : memset( sum, 0, sizeof( sum ) );
; 356 :}
كما يتبين من القائمة، تجاهل المترجم المكالمة memeset. هذا يرجع إلى حقيقة أنه في الوظيفة sha1 بعد المكالمة memeset لا مزيد من الإشارة إلى الهيكل CTX. لذلك، لا يرى المترجم أي فائدة في إضاعة وقت المعالج في الكتابة فوق الذاكرة التي لا يتم استخدامها في المستقبل. يمكنك إصلاح ذلك باستخدام الوظيفة RtlSecureZeroMemory أو
تصحيح:
void sha1( unsigned char *input, int ilen, unsigned char output[20] )
{
sha1_context ctx;
sha1_starts( &ctx );
sha1_update( &ctx, input, ilen );
sha1_finish( &ctx, output );
RtlSecureZeroMemory(&ctx, sizeof( sha1_context ) );
}
مقارنة لا لزوم لها
static u32 sceAudioOutputPannedBlocking
(u32 chan, int leftvol, int rightvol, u32 samplePtr) {
int result = 0;
// For some reason, this is the only one that checks for negative.
if (leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0) {
....
} else {
if (leftvol >= 0) {
chans[chan].leftVolume = leftvol;
}
if (rightvol >= 0) {
chans[chan].rightVolume = rightvol;
}
chans[chan].sampleAddress = samplePtr;
result = __AudioEnqueue(chans[chan], chan, true);
}
}
تحذير PVS-Studio:
انتبه إلى الفرع الآخر لأول مرة if. سيتم تنفيذ التعليمات البرمجية فقط في حالة استيفاء كافة الشروط اليسار> 0xFFFF || رايتفول > 0xFFFF || اليسار < 0 || اليمين <0 سوف يتبين أنها كاذبة. وبالتالي نحصل على العبارات التالية، والتي ستكون صحيحة بالنسبة للفرع else: leftvol <= 0xFFFF, Rightvol <= 0xFFFF, الحجم الأيسر >=0 и اليمين> = 0. لاحظ العبارتين الأخيرتين. هل يعقل التحقق من الشرط الضروري لتنفيذ هذا الجزء من التعليمات البرمجية؟
حتى نتمكن من إزالة هذه العبارات الشرطية بأمان:
static u32 sceAudioOutputPannedBlocking
(u32 chan, int leftvol, int rightvol, u32 samplePtr) {
int result = 0;
// For some reason, this is the only one that checks for negative.
if (leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0) {
....
} else {
chans[chan].leftVolume = leftvol;
chans[chan].rightVolume = rightvol;
chans[chan].sampleAddress = samplePtr;
result = __AudioEnqueue(chans[chan], chan, true);
}
}
سيناريو آخر. هناك نوع من الخطأ المخفي وراء هذه الشروط الزائدة عن الحاجة. ربما لم يتحققوا مما هو مطلوب.
Ctrl+C Ctrl+V يعيد الضربات
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
if (!Memory::IsValidAddress(psmfData) ||
!Memory::IsValidAddress(psmfData)) {
return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
}
....
}
انتبه إلى الشيك بالداخل if. ألا تعتقد أنه من الغريب أن نتحقق مما إذا كان العنوان صالحًا؟ psmfData، ضعفي؟ لذلك يبدو هذا غريبًا بالنسبة لي... في الواقع، هذا بالطبع خطأ مطبعي، وكانت الفكرة هي التحقق من كلا معلمتي الإدخال.
الخيار الصحيح:
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
if (!Memory::IsValidAddress(psmfStruct) ||
!Memory::IsValidAddress(psmfData)) {
return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
}
....
}
المتغير المنسي
extern void ud_translate_att(
int size = 0;
....
if (size == 8) {
ud_asmprintf(u, "b");
} else if (size == 16) {
ud_asmprintf(u, "w");
} else if (size == 64) {
ud_asmprintf(u, "q");
}
....
}
تحذير PVS-Studio:
يقع هذا الخطأ في المجلد تحويلة، لذلك لا علاقة له بالمشروع حقًا، ولكن تم العثور على الخطأ قبل أن ألاحظه، لذلك قررت تركه. بعد كل شيء، هذه المقالة لا تتعلق بمراجعة الأخطاء، ولكنها تتعلق بالتكامل مع Travis CI، ولم يتم تنفيذ أي تكوين للمحلل.
متغير الأحجام تتم تهيئته بواسطة ثابت، ومع ذلك، لا يتم استخدامه على الإطلاق في الكود، وصولاً إلى المشغل ifوالذي يعطي بالطبع زائف أثناء التحقق من الشروط، لأنه، كما نتذكر، الأحجام يساوي الصفر. الشيكات اللاحقة أيضا لا معنى لها.
على ما يبدو، نسي مؤلف جزء التعليمات البرمجية الكتابة فوق المتغير الأحجام قبل ذلك.
قلة النوم
هذا هو المكان الذي سننتهي فيه على الأرجح بالأخطاء. الغرض من هذه المقالة هو عرض عمل PVS-Studio مع Travis CI، وليس تحليل المشروع بأكبر قدر ممكن من الدقة. إذا كنت تريد أخطاء أكبر وأجمل، فيمكنك دائمًا الإعجاب بها
اختتام
يتيح لك استخدام خدمات الويب لبناء المشاريع جنبًا إلى جنب مع ممارسة التحليل التزايدي العثور على العديد من المشكلات فورًا بعد دمج التعليمات البرمجية. ومع ذلك، قد لا يكون إنشاء واحد كافيًا، لذا فإن إعداد الاختبار مع التحليل الثابت سيؤدي إلى تحسين جودة التعليمات البرمجية بشكل كبير.
وصلات مفيدة
إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فيرجى استخدام رابط الترجمة: Maxim Zvyagintsev.
المصدر: www.habr.com