Travis CI là một dịch vụ web phân tán để xây dựng và thử nghiệm phần mềm sử dụng GitHub làm nơi lưu trữ mã nguồn. Ngoài các kịch bản vận hành ở trên, bạn có thể thêm các kịch bản của riêng mình nhờ các tùy chọn cấu hình mở rộng. Trong bài viết này, chúng tôi sẽ định cấu hình Travis CI để hoạt động với PVS-Studio bằng ví dụ về mã PPSSPP.
Giới thiệu
Thiết lập Travis CI
Chúng tôi sẽ cần một kho lưu trữ trên GitHub, nơi đặt dự án chúng tôi cần, cũng như khóa cho PVS-Studio (bạn có thể lấy
Chúng ta hãy đi đến trang web
Để kiểm tra, tôi đã rẽ nhánh PPSSPP.
Chúng tôi kích hoạt kho lưu trữ mà chúng tôi muốn thu thập:
Hiện tại, Travis CI không thể xây dựng dự án của chúng tôi vì không có hướng dẫn xây dựng. Vì vậy, đã đến lúc cấu hình.
Trong quá trình phân tích, một số biến sẽ hữu ích cho chúng tôi, chẳng hạn như khóa cho PVS-Studio, không nên chỉ định trong tệp cấu hình. Vì vậy, hãy thêm các biến môi trường bằng cách sử dụng cài đặt bản dựng trong Travis CI:
Chúng tôi sẽ cần:
- PVS_USERNAME - tên người dùng
- PVS_KEY - khóa
- MAIL_USER - email sẽ được sử dụng để gửi báo cáo
- MAIL_PASSWORD - mật khẩu email
Hai cái cuối cùng là tùy chọn. Chúng sẽ được sử dụng để gửi kết quả qua thư. Nếu bạn muốn phân phối báo cáo theo cách khác, bạn không cần phải chỉ ra chúng.
Vì vậy, chúng tôi đã thêm các biến môi trường mà chúng tôi cần:
Bây giờ hãy tạo một tập tin .travis.yml và đặt nó vào thư mục gốc của dự án. PPSSPP đã có tệp cấu hình cho Travis CI, tuy nhiên, nó quá lớn và hoàn toàn không phù hợp cho ví dụ, vì vậy chúng tôi phải đơn giản hóa nó rất nhiều và chỉ để lại các thành phần cơ bản.
Trước tiên, hãy chỉ ra ngôn ngữ, phiên bản Ubuntu Linux mà chúng tôi muốn sử dụng trong máy ảo và các gói cần thiết cho bản dựng:
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'
Tất cả các gói được liệt kê đều cần thiết dành riêng cho PPSSPP.
Bây giờ chúng tôi chỉ ra ma trận lắp ráp:
matrix:
include:
- os: linux
compiler: "gcc"
env: PPSSPP_BUILD_TYPE=Linux PVS_ANALYZE=Yes
- os: linux
compiler: "clang"
env: PPSSPP_BUILD_TYPE=Linux
Nói thêm một chút về phần ma trận. Trong Travis CI, có hai cách để tạo tùy chọn xây dựng: cách thứ nhất là chỉ định danh sách trình biên dịch, loại hệ điều hành, biến môi trường, v.v., sau đó tạo ra ma trận gồm tất cả các kết hợp có thể có; thứ hai là một dấu hiệu rõ ràng của ma trận. Tất nhiên, bạn có thể kết hợp hai cách tiếp cận này và thêm một trường hợp duy nhất hoặc ngược lại, loại trừ nó bằng cách sử dụng phần loại trừ. Bạn có thể đọc thêm về điều này trong
Tất cả những gì còn lại là cung cấp hướng dẫn lắp ráp dành riêng cho dự án:
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 cho phép bạn thêm các lệnh của riêng mình cho các giai đoạn khác nhau trong vòng đời của máy ảo. Phần before_install được thực hiện trước khi cài đặt gói. Sau đó cài đặt, dựng lên, theo sau việc cài đặt các gói từ danh sách addons.aptmà chúng tôi đã chỉ ra ở trên. Bản thân việc lắp ráp diễn ra ở kịch bản. Nếu mọi việc suôn sẻ thì chúng ta sẽ thấy mình ở trong after_success (trong phần này chúng tôi sẽ chạy phân tích tĩnh). Đây không phải là tất cả các bước có thể được sửa đổi, nếu bạn cần thêm, bạn nên xem xét
Để dễ đọc, các lệnh được đặt trong một tập lệnh riêng .travis.sh, được đặt ở thư mục gốc của dự án.
Vì vậy, chúng tôi có tập tin sau .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
Trước khi cài đặt các gói, chúng tôi sẽ cập nhật các mô-đun con. Điều này là cần thiết để xây dựng PPSSPP. Hãy thêm chức năng đầu tiên vào .travis.sh (lưu ý phần mở rộng):
travis_before_install() {
git submodule update --init --recursive
}
Bây giờ chúng ta trực tiếp đến phần thiết lập khởi chạy tự động PVS-Studio trong Travis CI. Đầu tiên chúng ta cần cài đặt gói PVS-Studio trên hệ thống:
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
}
Ở đầu hàm travis_install chúng tôi cài đặt các trình biên dịch chúng tôi cần bằng cách sử dụng các biến môi trường. Sau đó nếu biến $PVS_ANALYZE lưu trữ giá trị Có (chúng tôi đã chỉ ra nó trong phần env trong quá trình cấu hình ma trận xây dựng), chúng tôi cài đặt gói pvs-studio. Ngoài ra, các gói cũng được chỉ định libio-socket-ssl-Perl и libnet-ssleay-Perltuy nhiên, chúng được yêu cầu để gửi kết quả qua thư, vì vậy chúng không cần thiết nếu bạn đã chọn một phương pháp khác để gửi báo cáo của mình.
Chức năng tải về_extract tải xuống và giải nén kho lưu trữ được chỉ định:
download_extract() {
aria2c -x 16 $1 -o $2
tar -xf $2
}
Đã đến lúc kết hợp dự án lại với nhau. Điều này xảy ra trong phần kịch bản:
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
}
Trên thực tế, đây là cấu hình ban đầu được đơn giản hóa, ngoại trừ những dòng sau:
if [ "$PVS_ANALYZE" = "Yes" ]; then
CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
fi
Trong phần mã này, chúng tôi đặt cho cmake cờ để xuất các lệnh biên dịch. Điều này là cần thiết cho một bộ phân tích mã tĩnh. Bạn có thể đọc thêm về điều này trong bài viết “
Nếu quá trình lắp ráp thành công thì chúng ta sẽ có được after_success, nơi chúng tôi thực hiện phân tích tĩnh:
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
}
Chúng ta hãy xem xét kỹ hơn các dòng sau:
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
Dòng đầu tiên tạo tệp giấy phép từ tên người dùng và khóa mà chúng tôi đã chỉ định ngay từ đầu khi thiết lập các biến môi trường Travis CI.
Dòng thứ hai bắt đầu phân tích trực tiếp. Lá cờ -j đặt số lượng chủ đề để phân tích, gắn cờ -l cho biết giấy phép, cờ -o xác định tệp để xuất nhật ký và cờ -vô hiệu hóaLicensExpirationCheck bắt buộc đối với các phiên bản dùng thử, vì theo mặc định máy phân tích pvs-studio sẽ cảnh báo người dùng rằng giấy phép sắp hết hạn. Để ngăn điều này xảy ra, bạn có thể chỉ định cờ này.
Tệp nhật ký chứa đầu ra thô không thể đọc được nếu không chuyển đổi, vì vậy trước tiên bạn phải làm cho tệp có thể đọc được. Hãy chuyển nhật ký qua chuyển đổi plogvà đầu ra là một tệp html.
Trong ví dụ này, tôi quyết định gửi báo cáo qua thư bằng lệnh gửi email.
Kết quả là chúng tôi đã nhận được tệp sau .travis.sh:
#/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;
Bây giờ là lúc đẩy các thay đổi vào kho git, sau đó Travis CI sẽ tự động chạy bản dựng. Nhấp vào “ppsspp” để đi tới báo cáo bản dựng:
Chúng ta sẽ thấy tổng quan về bản dựng hiện tại:
Nếu quá trình xây dựng hoàn tất thành công, chúng tôi sẽ nhận được email có kết quả phân tích tĩnh. Tất nhiên, gửi thư không phải là cách duy nhất để nhận được báo cáo. Bạn có thể chọn bất kỳ phương pháp thực hiện nào. Nhưng điều quan trọng cần nhớ là sau khi quá trình xây dựng hoàn tất, bạn sẽ không thể truy cập vào các tệp máy ảo.
Tóm tắt lỗi
Chúng ta đã hoàn thành xuất sắc phần khó nhất. Bây giờ hãy đảm bảo rằng mọi nỗ lực của chúng ta đều xứng đáng. Chúng ta hãy xem xét một số điểm thú vị từ báo cáo phân tích tĩnh được gửi đến cho tôi qua thư (tôi đã chỉ ra điều đó không phải là vô ích).
Tối ưu hóa nguy hiểm
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 ) );
}
Cảnh báo của PVS-Studio:
Đoạn mã này nằm trong mô-đun băm an toàn, tuy nhiên, nó chứa một lỗ hổng bảo mật nghiêm trọng (
; Line 355
mov r8d, 20
xor edx, edx
lea rcx, QWORD PTR sum$[rsp]
call memset
; Line 356
Mọi thứ đều theo thứ tự và chức năng bộ ghi nhớ được thực thi, do đó ghi đè dữ liệu quan trọng trong RAM, tuy nhiên, đừng vội vui mừng. Hãy xem danh sách lắp ráp của phiên bản Phát hành đã được tối ưu hóa:
; 354 :
; 355 : memset( sum, 0, sizeof( sum ) );
; 356 :}
Như có thể thấy từ danh sách, trình biên dịch đã bỏ qua lệnh gọi bộ ghi nhớ. Điều này là do thực tế là trong hàm sha1 sau cuộc gọi bộ ghi nhớ không còn tham chiếu đến cấu trúc CTX.. Do đó, trình biên dịch thấy không có ích gì khi lãng phí thời gian của bộ xử lý để ghi đè bộ nhớ không được sử dụng trong tương lai. Bạn có thể khắc phục điều này bằng cách sử dụng chức năng RtlSecureZeroBộ nhớ hoặc
Chính xác:
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 ) );
}
So sánh không cần thiết
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);
}
}
Cảnh báo của PVS-Studio:
Hãy chú ý đến nhánh khác đầu tiên if. Mã sẽ chỉ được thực thi nếu tất cả các điều kiện leftvol > 0xFFFF || phảivol > 0xFFFF || trái vol < 0 || bên phải < 0 sẽ trở thành sai. Do đó, chúng tôi nhận được các tuyên bố sau, điều này sẽ đúng với nhánh else: trái vol <= 0xFFFF, phảivol <= 0xFFFF, trái vol >= 0 и bên phải >= 0. Hãy chú ý hai câu cuối cùng. Việc kiểm tra điều kiện cần thiết để thực thi đoạn mã này có hợp lý không?
Vì vậy, chúng ta có thể loại bỏ các câu lệnh có điều kiện này một cách an toàn:
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);
}
}
Một kịch bản khác. Có một số loại lỗi ẩn đằng sau những điều kiện dư thừa này. Có lẽ họ đã không kiểm tra những gì được yêu cầu.
Ctrl+C Ctrl+V Đánh trả
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
if (!Memory::IsValidAddress(psmfData) ||
!Memory::IsValidAddress(psmfData)) {
return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
}
....
}
Hãy chú ý kiểm tra bên trong if. Bạn có thấy lạ không khi chúng tôi kiểm tra xem địa chỉ đó có hợp lệ không? psmfData, gấp đôi? Vì vậy, điều này có vẻ lạ đối với tôi... Trên thực tế, đây tất nhiên là một lỗi đánh máy và ý tưởng là để kiểm tra cả hai tham số đầu vào.
Tùy chọn đúng:
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
if (!Memory::IsValidAddress(psmfStruct) ||
!Memory::IsValidAddress(psmfData)) {
return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
}
....
}
Biến bị quên
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");
}
....
}
Cảnh báo của PVS-Studio:
Lỗi này nằm trong thư mục ext, vì vậy không thực sự liên quan đến dự án, nhưng lỗi đã được phát hiện trước khi tôi kịp nhận ra nên tôi quyết định bỏ nó đi. Suy cho cùng, bài viết này không phải về việc xem xét các lỗi mà là về việc tích hợp với Travis CI và không thực hiện cấu hình máy phân tích nào.
Biến kích thước được khởi tạo bởi một hằng số, tuy nhiên, nó hoàn toàn không được sử dụng trong mã, ngay cả đối với toán tử if, tất nhiên, mang lại sai trong khi kiểm tra các điều kiện, bởi vì, như chúng ta nhớ, kích thước bằng không. Kiểm tra tiếp theo cũng không có ý nghĩa.
Có vẻ như tác giả đoạn mã đã quên ghi đè lên biến kích thước trước đó.
Dừng
Đây có thể là nơi chúng ta sẽ kết thúc với những sai lầm. Mục đích của bài viết này là để chứng minh công việc của PVS-Studio cùng với Travis CI chứ không phải để phân tích dự án một cách kỹ lưỡng nhất có thể. Nếu bạn muốn những sai lầm lớn hơn và đẹp hơn, bạn luôn có thể ngưỡng mộ chúng
Kết luận
Việc sử dụng dịch vụ web để xây dựng dự án cùng với việc thực hành phân tích gia tăng cho phép bạn tìm ra nhiều vấn đề ngay sau khi hợp nhất mã. Tuy nhiên, một bản dựng có thể là không đủ, vì vậy việc thiết lập thử nghiệm cùng với phân tích tĩnh sẽ cải thiện đáng kể chất lượng mã.
Liên kết hữu ích
Nếu bạn muốn chia sẻ bài viết này với độc giả nói tiếng Anh, vui lòng sử dụng liên kết bản dịch: Maxim Zvyagintsev.
Nguồn: www.habr.com