PVS-Studio di Awan: CircleCI

Gambar 2

Ini adalah bagian baru dari seri artikel kami tentang menggunakan analisa statis PVS-Studio dengan sistem cloud CI. Hari ini kita akan melihat layanan lain, CircleCI. Kami akan menggunakan aplikasi pemutar media Kodi sebagai proyek uji dan melihat apakah kami dapat menemukan bug yang menarik dalam kode sumbernya.
Catatan Artikel sebelumnya tentang mengintegrasikan PVS-Studio dengan sistem cloud CI:


Sebelum mengatur lingkungan kerja dan memeriksa laporan analisis, saya ingin mengatakan beberapa kata tentang perangkat lunak yang akan kita gunakan dan periksa.

CircleCI adalah layanan cloud CI untuk pembuatan, pengujian, dan penyebaran perangkat lunak otomatis. Ini mendukung pembangunan proyek baik dalam wadah dan pada mesin virtual pada Windows, Linux, dan macOS.

Kodi adalah aplikasi media player lintas platform gratis dan open-source. Ini memungkinkan pengguna untuk memutar dan melihat sebagian besar media streaming, seperti video, musik, podcast, dan video dari Internet, serta semua file media digital umum dari media penyimpanan lokal dan jaringan. Ini mendukung penggunaan tema dan skin dan ekstensi fungsionalitas melalui plugin. Kodi tersedia untuk Windows, Linux, macOS, dan Android.

PVS-Studio adalah penganalisa statis untuk mendeteksi bug dan kerentanan potensial dalam kode sumber aplikasi yang ditulis dalam C, C ++, C #, dan Java. Penganalisa berjalan pada Windows, Linux, dan macOS.

Menyiapkan


Pertama kita harus pergi ke halaman utama CircleCI dan klik "Daftar"

Gambar 1

Pada halaman berikutnya, kami ditawari untuk mengotorisasi dengan akun GitHub atau Bitbucket. Kami memilih GitHub dan membuka halaman otorisasi CircleCI.

Gambar 3

Setelah mengotorisasi aplikasi (dengan mengklik tombol hijau "Otorisasi circleci"), kami diarahkan ke "Selamat Datang di CircleCI!" halaman:

Gambar 4

Di sini kita dapat menentukan segera proyek mana yang ingin kita bangun CircleCI. Kami mencentang repositori kami dan klik "Ikuti".

Setelah menambahkan repositori, CircleCI akan secara otomatis memulai proses build, tetapi karena kami belum memiliki file konfigurasi di repositori kami, pekerjaan build akan dibatalkan dengan pesan kesalahan.

Gambar 5

Sebelum menambahkan file konfigurasi, kita perlu menambahkan beberapa variabel yang berisi data lisensi analyzer. Untuk melakukan itu, kita klik "Pengaturan" di sidebar kiri, pilih "Proyek" di bagian "ORGANISASI", dan klik tombol roda gigi di sebelah kanan nama proyek kami. Jendela pengaturan akan muncul.

Gambar 6

Kita pergi ke halaman "Variabel Lingkungan". Di sini kita membuat dua variabel, PVS_USERNAME dan PVS_KEY , yang berisi kunci lisensi nama pengguna dan penganalisa.

Gambar 7

Saat memulai build, CircleCI membaca konfigurasi pekerjaan dari file yang disimpan dalam repositori di .circleci / config.yml. Mari kita tambahkan.

Pertama, kita perlu menentukan gambar mesin virtual yang akan dijalankan oleh alat analisa. Daftar lengkap gambar tersedia di sini .

version: 2 jobs: build: machine: image: ubuntu-1604:201903-01 

Selanjutnya, kami menambahkan repositori yang diperlukan untuk apt dan menginstal dependensi proyek:

 steps: - checkout - run: sudo -- sh -c " add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends && add-apt-repository -y ppa:wsnipex/vaapi && add-apt-repository -y ppa:pulse-eight/libcec && apt-get update" - run: sudo apt-get install -y automake autopoint build-essential cmake curl default-jre gawk gdb gdc gettext git-core gperf libasound2-dev libass-dev libbluray-dev libbz2-dev libcap-dev libcdio-dev libcec4-dev libcrossguid-dev libcurl3 libcurl4-openssl-dev libdbus-1-dev libegl1-mesa-dev libfmt3-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgif-dev libgl1-mesa-dev libglu1-mesa-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev libxrandr-dev libxrender-dev libxslt1-dev libxt-dev mesa-utils nasm pmount python-dev python-imaging python-sqlite rapidjson-dev swig unzip uuid-dev yasm zip zlib1g-dev wget 

Menambahkan repositori PVS-Studio dan menginstal analyzer:

 - run: 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 - run: sudo -- sh -c "apt-get update && apt-get install pvs-studio -y" 

Kemudian kami sedang membangun dependensi:

 - run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local 

Setelah itu, kami membuat Makefiles di direktori build:

 - run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. 

Langkah selanjutnya adalah menyiapkan dan memulai analisis proyek.

Pertama kita membuat file lisensi penganalisa. Perintah lain akan mulai melacak pembangunan proyek oleh kompiler.

Perintah selanjutnya mengikuti penelusuran menjalankan analisis seperti itu. Jika Anda menggunakan versi demo PVS-Studio, luncurkan dengan parameter:

--disableLicenseExpirationCheck .

Perintah terakhir mengubah file laporan analyzer menjadi laporan html:

 - run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY} - run: pvs-studio-analyzer trace -- make -j2 -C build/ - run: pvs-studio-analyzer analyze -j2 -l PVS.lic -o PVS-Studio.log --disableLicenseExpirationCheck - run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log 

Setelah tes selesai, kami menyimpan laporan:

 - run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/ - store_artifacts: path: ./PVS_Result 

Berikut teks lengkap file .circleci / config.yml:

 version: 2.1 jobs: build: machine: image: ubuntu-1604:201903-01 steps: - checkout - run: sudo -- sh -c " add-apt-repository -y ppa:team-xbmc/xbmc-ppa-build-depends && add-apt-repository -y ppa:wsnipex/vaapi && add-apt-repository -y ppa:pulse-eight/libcec && apt-get update" - run: sudo apt-get install -y automake autopoint build-essential cmake curl default-jre gawk gdb gdc gettext git-core gperf libasound2-dev libass-dev libbluray-dev libbz2-dev libcap-dev libcdio-dev libcec4-dev libcrossguid-dev libcurl3 libcurl4-openssl-dev libdbus-1-dev libegl1-mesa-dev libfmt3-dev libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgif-dev libgl1-mesa-dev libglu1-mesa-dev libiso9660-dev libjpeg-dev liblcms2-dev libltdl-dev liblzo2-dev libmicrohttpd-dev libmysqlclient-dev libnfs-dev libpcre3-dev libplist-dev libpng-dev libpulse-dev libsmbclient-dev libsqlite3-dev libssl-dev libtag1-dev libtinyxml-dev libtool libudev-dev libusb-dev libva-dev libvdpau-dev libxml2-dev libxmu-dev libxrandr-dev libxrender-dev libxslt1-dev libxt-dev mesa-utils nasm pmount python-dev python-imaging python-sqlite rapidjson-dev swig unzip uuid-dev yasm zip zlib1g-dev wget - run: 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 - run: sudo -- sh -c "apt-get update && apt-get install pvs-studio -y" - run: sudo make -C tools/depends/target/flatbuffers PREFIX=/usr/local - run: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. - run: pvs-studio-analyzer credentials -o PVS.lic ${PVS_USER} ${PVS_KEY} - run: pvs-studio-analyzer trace -- make -j2 -C build/ - run: pvs-studio-analyzer analyze -j2 -l PVS.lic -o PVS-Studio.log --disableLicenseExpirationCheck - run: plog-converter -t html -o PVS-Studio.html PVS-Studio.log - run: mkdir PVS_Result && cp PVS-Studio.* ./PVS_Result/ - store_artifacts: path: ./PVS_Result 

Setelah file ini diunggah ke repositori, CircleCI akan secara otomatis memulai pembuatan.

Gambar 12

Setelah pekerjaan selesai, file dengan hasil analisis dapat diunduh pada tab "Artefak".

Gambar 11

Hasil analisis


OK, sekarang mari kita lihat beberapa output peringatan oleh penganalisa.

Peringatan PVS-Studio: V504 Sangat mungkin bahwa titik koma ';' hilang setelah kata kunci 'kembali'. AdvancedSettings.cpp: 1476

 void CAdvancedSettings::SetExtraArtwork(const TiXmlElement* arttypes, std::vector<std::string>& artworkMap) { if (!arttypes) return artworkMap.clear(); const TiXmlNode* arttype = arttypes->FirstChild("arttype"); .... } 

Pemformatan kode menyarankan logika eksekusi berikut:

  • jika arttypes adalah null pointer, metode ini mengembalikan;
  • jika arttypes adalah pointer non-null, vektor artworkMap akan dihapus dan beberapa tindakan kemudian dilakukan.

Tapi yang hilang ';' karakter istirahat semuanya, dan logika eksekusi yang sebenarnya adalah sebagai berikut:

  • jika arttypes adalah pointer nol, vektor artworkMap akan dihapus dan metode kembali;
  • jika arttypes adalah pointer non-null, program mengeksekusi tindakan apa pun yang terjadi selanjutnya tetapi vektor artworkMap tidak bisa dihapus.

Singkatnya, situasi ini memang terlihat seperti bug. Lagi pula, Anda hampir tidak mengharapkan orang untuk menulis ekspresi seperti return artworkMap.clear (); :)

Peringatan PVS-Studio:

  • Ekspresi 'lastsector' V547 selalu salah. udf25.cpp: 636
  • Ekspresi 'lastsector' V547 selalu salah. udf25.cpp: 644
  • V571 Cek berulang. Kondisi 'if (lastsector)' sudah diverifikasi di baris 636. udf25.cpp: 644

 int udf25::UDFGetAVDP( struct avdp_t *avdp) { .... uint32_t lastsector; .... lastsector = 0; // <= .... for(;;) { .... if( lastsector ) { // <= V547 lbnum = lastsector; terminate = 1; } else { //! @todo Find last sector of the disc (this is optional). if( lastsector ) // <= V547 lbnum = lastsector - 256; else return 0; } } .... } 

Perhatikan tempat yang ditandai dengan // <= . Variabel lastsector diberi nilai 0 dan kemudian digunakan sebagai ekspresi kondisional dalam dua pernyataan if . Karena nilai tidak berubah baik dalam loop atau antara penugasan, kontrol tidak akan pernah masuk ke cabang lain dari kedua pernyataan if .

Namun, itu juga bisa berarti bahwa para pengembang belum mengimplementasikan fungsi yang dimaksudkan (perhatikan komentar todo ).

Ngomong-ngomong, seperti yang mungkin Anda perhatikan, cuplikan ini memicu tiga peringatan sekaligus. Tetapi bahkan banyak peringatan untuk sepotong kode tidak akan terlihat cukup meyakinkan bagi beberapa pengguna, dan mereka akan tetap percaya bahwa penganalisa itu salah ... Aspek ini dibahas secara rinci dalam sebuah pos oleh salah satu rekan tim saya: " Satu Hari dari Dukungan Pengguna PVS-Studio ":).

Peringatan PVS-Studio: Ekspresi V547 'values.size ()! = 2' selalu salah. GUIControlSettings.cpp: 1174

 bool CGUIControlRangeSetting::OnClick() { .... std::vector<CVariant> values; SettingConstPtr listDefintion = settingList->GetDefinition(); switch (listDefintion->GetType()) { case SettingType::Integer: values.push_back(m_pSlider-> GetIntValue(CGUISliderControl::RangeSelectorLower)); values.push_back(m_pSlider-> GetIntValue(CGUISliderControl::RangeSelectorUpper)); break; case SettingType::Number: values.push_back(m_pSlider-> GetFloatValue(CGUISliderControl::RangeSelectorLower)); values.push_back(m_pSlider-> GetFloatValue(CGUISliderControl::RangeSelectorUpper)); break; default: return false; } if (values.size() != 2) return false; SetValid(CSettingUtils::SetList(settingList, values)); return IsValid(); } 

Pemeriksaan values.size ()! = 2 berlebihan di sini karena ekspresi kondisional ini akan selalu bernilai false . Memang, jika eksekusi memasuki salah satu cabang case dari pernyataan switch , dua elemen akan ditambahkan ke vektor, dan karena awalnya kosong, ukurannya secara alami akan menjadi sama dengan 2; jika tidak (yaitu jika cabang default dijalankan), metode akan kembali.

Peringatan PVS-Studio: Ekspresi V547 'prio == 0x7fffffff' selalu benar. DBusReserve.cpp: 57

 bool CDBusReserve::AcquireDevice(const std::string& device) { .... int prio = INT_MAX; .... res = dbus_bus_request_name( m_conn, service.c_str(), DBUS_NAME_FLAG_DO_NOT_QUEUE | (prio == INT_MAX ? 0 : DBUS_NAME_FLAG_ALLOW_REPLACEMENT), // <= error); .... } 

Variabel prio diinisialisasi ke nilai INT_MAX dan kemudian digunakan sebagai operan dari operator ternary dalam perbandingan prio == INT_MAX , meskipun nilainya tidak berubah setelah inisialisasi. Ini berarti ekspresi prio == INT_MAX benar dan operator ternary akan selalu mengembalikan 0.

Peringatan PVS-Studio:

  • V575 Potensi penunjuk nol dilewatkan ke fungsi 'memcpy'. Periksa argumen pertama. Periksa baris: 39, 38. DVDOverlayImage.h: 39
  • V575 Potensi penunjuk nol dilewatkan ke fungsi 'memcpy'. Periksa argumen pertama. Periksa baris: 44, 43. DVDOverlayImage.h: 44

 CDVDOverlayImage(const CDVDOverlayImage& src) : CDVDOverlay(src) { Data = (uint8_t*)malloc(src.linesize * src.height); memcpy(data, src.data, src.linesize * src.height); // <= if(src.palette) { palette = (uint32_t*)malloc(src.palette_colors * 4); memcpy(palette, src.palette, src.palette_colors * 4); // <= } .... } 

Kedua peringatan memiliki pola yang sama: pointer yang dikembalikan oleh fungsi malloc digunakan lebih lanjut dalam fungsi memcpy tanpa diperiksa untuk NULL terlebih dahulu.

Beberapa orang mungkin berpendapat bahwa malloc tidak akan pernah mengembalikan null pointer, dan jika ya, akan lebih baik bagi aplikasi untuk crash. Ini adalah subjek diskusi terpisah, tetapi apa pun pendapat Anda, saya sarankan membaca posting ini oleh rekan satu tim saya: " Mengapa penting untuk memeriksa apa fungsi malloc dikembalikan ".

Jika mau, Anda dapat menyesuaikan penganalisa sehingga tidak menganggap malloc dapat mengembalikan null pointer - ini akan mencegahnya mengeluarkan jenis peringatan ini. Rincian lebih lanjut dapat ditemukan di sini .

Peringatan PVS-Studio: V522 Mungkin ada referensi 'entri' penunjuk nol potensial. Periksa baris: 985, 981. emu_msvcrt.cpp: 985

 struct dirent *dll_readdir(DIR *dirp) { .... struct dirent *entry = NULL; entry = (dirent*) malloc(sizeof(*entry)); if (dirData->curr_index < dirData->items.Size() + 2) { if (dirData->curr_index == 0) strncpy(entry->d_name, ".\0", 2); .... } 

Contoh ini mirip dengan yang sebelumnya. Pointer yang dikembalikan oleh fungsi malloc disimpan ke variabel entri , dan variabel ini kemudian digunakan tanpa cek nol sebelumnya ( entri-> d_name ).

Peringatan PVS-Studio: V773 Ruang lingkup visibilitas dari pointer 'progressHandler' keluar tanpa melepaskan memori. Kebocoran memori dimungkinkan. PVRGUIChannelIconUpdater.cpp: 94

 void CPVRGUIChannelIconUpdater::SearchAndUpdateMissingChannelIcons() const { .... CPVRGUIProgressHandler* progressHandler = new CPVRGUIProgressHandler(g_localizeStrings.Get(19286)); for (const auto& group : m_groups) { const std::vector<PVRChannelGroupMember> members = group->GetMembers(); int channelIndex = 0; for (const auto& member : members) { progressHandler->UpdateProgress(member.channel->ChannelName(), channelIndex++, members.size()); .... } progressHandler->DestroyProgress(); } 

Nilai pointer progressHandler dikembalikan oleh operator baru . Tetapi tidak ada operator hapus untuk penunjuk ini. Ini berarti kebocoran memori.

Peringatan PVS-Studio: V557 Array overrun dimungkinkan. Indeks 'idx' menunjuk di luar batas array. PlayerCoreFactory.cpp: 240

 std::vector<CPlayerCoreConfig *> m_vecPlayerConfigs; bool CPlayerCoreFactory::PlaysVideo(const std::string& player) const { CSingleLock lock(m_section); size_t idx = GetPlayerIndex(player); if (m_vecPlayerConfigs.empty() || idx > m_vecPlayerConfigs.size()) return false; return m_vecPlayerConfigs[idx]->m_bPlaysVideo; } 

Pernyataan if membatasi ukuran vektor m_vecPlayerConfigs ke rentang tertentu dengan meminta metode kembali jika kondisi pemeriksaan ukuran benar. Akibatnya, ketika eksekusi mencapai pernyataan pengembalian terakhir, ukuran vektor m_vecPlayerConfigs akan berada dalam kisaran yang ditentukan, [1; idx]. Tetapi beberapa baris kemudian, program ini mengindeks vektor di idx : m_vecPlayerConfigs [idx] -> m_bPlaysVideo . Ini berarti bahwa jika idx sama dengan ukuran vektor, kami akan mengindeks di luar rentang yang valid.

Mari kita buat artikel ini dengan beberapa contoh dari kode perpustakaan Platinum .

Peringatan PVS-Studio: V542 Pertimbangkan untuk memeriksa pemeran tipe ganjil: 'bool' ke 'char *'. PltCtrlPoint.cpp: 1617

 NPT_Result PLT_CtrlPoint::ProcessSubscribeResponse(...) { .... bool subscription = (request.GetMethod().ToUppercase() == "SUBSCRIBE"); .... NPT_String prefix = NPT_String::Format(" PLT_CtrlPoint::ProcessSubscribeResponse %ubscribe for service \"%s\" (result = %d, status code = %d)", (const char*)subscription?"S":"Uns", // <= (const char*)service->GetServiceID(), res, response?response->GetStatusCode():0); .... } 

Para pengembang memiliki asumsi yang salah tentang prioritas operasi. Apa yang dilemparkan ke const char * bukan hasil yang dikembalikan oleh operator ternary ( berlangganan? "S": "Uns" ) tetapi variabel berlangganan . Ini terlihat aneh, paling tidak.

Peringatan PVS-Studio: V560 Bagian dari ekspresi kondisional selalu salah: c == '\ t'. NptUtils.cpp: 863

 NPT_Result NPT_ParseMimeParameters(....) { .... case NPT_MIME_PARAMETER_PARSER_STATE_NEED_EQUALS: if (c < ' ') return NPT_ERROR_INVALID_SYNTAX; // END or CTLs are invalid if (c == ' ' || c == '\t') continue; // ignore leading whitespace .... } 

Kode karakter spasi adalah 0x20, dan kode karakter tab adalah 0x09. Oleh karena itu, subekspresi c == '\ t' akan selalu bernilai false karena case ini sudah dicakup oleh pemeriksaan c <'' (yang, jika benar, akan menyebabkan fungsi kembali).

Kesimpulan


Seperti yang ditunjukkan artikel ini, kami berhasil membuat analisis oleh PVS-Studio pada sistem CI lain (CircleCI). Saya mengundang Anda untuk mengunduh dan mencoba penganalisa pada proyek Anda sendiri. Jika Anda memiliki pertanyaan tentang pengaturan atau penggunaan PVS-Studio, jangan ragu untuk menghubungi kami - kami akan dengan senang hati membantu.

Dan, tentu saja, kami berharap Anda mendapatkan kode bugless. :)

Source: https://habr.com/ru/post/id470465/


All Articles