
Aplikasi modern dibangun dari perpustakaan pihak ketiga seperti blok penyusun. Ini normal dan satu-satunya pilihan untuk menyelesaikan proyek dalam jumlah waktu yang wajar dan dengan anggaran yang masuk akal. Namun, mengambil semua batu bata tanpa pandang bulu mungkin bukan ide yang bagus. Jika ada beberapa opsi, sebaiknya luangkan waktu untuk menganalisis pustaka yang terbuka untuk memilih pustaka yang berkualitas tertinggi.
Koleksi "Library C ++ khusus header yang mengagumkan"
Kisah penulisan ini dimulai dengan podcast Cppcast " Cross Platform Mobile Telephony ". Dari situ, saya belajar tentang keberadaan daftar " awesome-hpp ", yang mencantumkan sejumlah besar pustaka C ++ terbuka, yang hanya terdiri dari file header.
Daftar ini menarik minat saya karena dua alasan. Pertama, ini adalah kesempatan untuk mengisi kembali basis proyek untuk menguji penganalisis PVS-Studio kami pada kode modern. Banyak proyek ditulis dalam C ++ 11, C ++ 14 dan C ++ 17. Kedua, ini adalah kesempatan untuk menulis artikel tentang memeriksa proyek-proyek ini.
Proyeknya kecil, jadi ada sedikit bug di masing-masing proyek. Ditambah, ada sedikit peringatan, karena beberapa bug hanya dapat dideteksi jika kelas atau fungsi template dibuat dalam kode kustom. Sampai kelas dan fungsi ini digunakan, seringkali tidak mungkin untuk mengetahui apakah ada kesalahan atau tidak. Meski demikian, secara total, ada banyak kesalahan, dan akan saya tulis di artikel berikutnya. Artikel ini bukan tentang kesalahan, tapi tentang peringatan.
Mengapa menganalisis
Dengan menggunakan pustaka pihak ketiga, Anda tanpa syarat mempercayai mereka untuk melakukan beberapa pekerjaan dan penghitungan. Bahayanya adalah terkadang programmer memilih sebuah perpustakaan tanpa berpikir sama sekali bahwa kesalahan tidak hanya dapat berisi kode mereka, tetapi juga kode dari perpustakaan itu sendiri. Akibatnya, ada kesalahan yang tidak terlihat dan tidak dapat dipahami yang dapat muncul dengan cara yang paling tidak terduga.
Kode pustaka open source terkenal telah di-debug dengan baik, dan kemungkinan menghadapi kesalahan jauh lebih sedikit daripada kode serupa yang Anda tulis sendiri. Masalahnya adalah tidak semua pustaka digunakan secara luas dan di-debug. Dan di sinilah muncul pertanyaan tentang menilai kualitas mereka.
Untuk membuatnya lebih jelas, mari kita lihat contohnya. Mari kita lihat perpustakaan JSONCONS .
JSONCONS adalah C ++, pustaka hanya-header untuk membangun format data seperti JSON dan JSON seperti CBOR.Perpustakaan khusus untuk tugas tertentu. Ini mungkin bekerja dengan baik secara keseluruhan, dan Anda tidak akan pernah melihat bug di dalamnya. Tapi Tuhan melarang Anda perlu menggunakan operator yang kelebihan beban ini << = .
static constexpr uint64_t basic_type_bits = sizeof(uint64_t) * 8;
....
uint64_t* data()
{
return is_dynamic() ? dynamic_stor_.data_ : short_stor_.values_;
}
....
basic_bigint& operator<<=( uint64_t k )
{
size_type q = (size_type)(k / basic_type_bits);
if ( q ) // Increase common_stor_.length_ by q:
{
resize(length() + q);
for (size_type i = length(); i-- > 0; )
data()[i] = ( i < q ? 0 : data()[i - q]);
k %= basic_type_bits;
}
if ( k ) // 0 < k < basic_type_bits:
{
uint64_t k1 = basic_type_bits - k;
uint64_t mask = (1 << k) - 1; // <=
resize( length() + 1 );
for (size_type i = length(); i-- > 0; )
{
data()[i] <<= k;
if ( i > 0 )
data()[i] |= (data()[i-1] >> k1) & mask;
}
}
reduce();
return *this;
}
Peringatan penganalisis PVS-Studio: V629 Pertimbangkan untuk memeriksa ekspresi '1 << k'. Pergeseran bit dari nilai 32-bit dengan perluasan berikutnya ke tipe 64-bit. bigint.hpp 744
Seperti yang saya pahami, fungsi ini bekerja dengan angka besar yang disimpan sebagai array elemen 64-bit. Untuk bekerja dengan bit tertentu, Anda perlu membentuk mask 64-bit:
uint64_t mask = (1 << k) - 1;
Tapi topeng ini tidak terbentuk dengan benar. Karena literal numerik 1 berjenis int , menggesernya lebih dari 31 bit akan menghasilkan perilaku tidak terdefinisi.
Dari standar:Nilai variabel mask bisa apa saja yang Anda suka. Ya saya tahu, secara teori apapun bisa terjadi karena UB. Tetapi dalam praktiknya, kemungkinan besar, kita berbicara tentang hasil ekspresi yang salah.
shift-ekspresi << aditif-ekspresi
...
2. Nilai E1 << E2 adalah E1 posisi bit E2 bergeser kiri; bit yang dikosongkan tidak diisi. Jika E1 memiliki tipe unsigned, nilai hasilnya adalah E1 * 2 ^ E2, mengurangi satu modulo lebih dari nilai maksimum yang dapat diwakili dalam tipe hasil. Sebaliknya, jika E1 memiliki tipe bertanda dan nilai non-negatif, dan E1 * 2 ^ E2 dapat direpresentasikan dalam tipe hasil, maka itulah nilai yang dihasilkan; jika tidak, perilaku tidak terdefinisi.
Jadi, kami memiliki fungsi yang tidak bisa digunakan. Sebaliknya, ini hanya akan berfungsi untuk beberapa kasus khusus dari nilai argumen input. Ini adalah jebakan potensial yang bisa membuat programmer jatuh. Program dapat menjalankan dan melewati berbagai tes, dan kemudian secara tidak terduga menolak pengguna pada file masukan lainnya.
Dan satu error lagi bisa dilihat di operator >> = .
Sebuah pertanyaan retoris. Haruskah saya mempercayai perpustakaan ini?
Mungkin sepadan. Bagaimanapun, ada kesalahan dalam proyek apa pun. Namun, perlu dipertimbangkan: jika ada kesalahan ini, apakah ada kesalahan lain yang dapat menyebabkan kerusakan data yang parah? Bukankah lebih baik memberikan preferensi ke perpustakaan yang lebih populer / teruji jika ada beberapa?
Contoh yang tidak meyakinkan? Oke, mari kita beli satu sama lain. Mari kita ambil perpustakaan matematika Universal . Diharapkan perpustakaan menyediakan kemampuan untuk beroperasi dengan vektor. Misalnya, mengalikan dan membagi vektor dengan nilai skalar. Oke, mari kita lihat bagaimana operasi ini diterapkan. Perkalian:
template<typename Scalar>
vector<Scalar> operator*(double scalar, const vector<Scalar>& v) {
vector<Scalar> scaledVector(v);
scaledVector *= scalar;
return v;
}
Peringatan penganalisis PVS-Studio: V1001 Variabel 'scaledVector' ditetapkan tetapi tidak digunakan di akhir fungsi. vector.hpp 124
Karena kesalahan ketik, bukan container baru scaledVector yang dikembalikan , tetapi vektor aslinya. Kesalahan yang sama terjadi pada operator divisi. Telapak tangan.
Sekali lagi, kesalahan ini tidak berarti apa-apa secara terpisah. Meskipun tidak, ini adalah petunjuk bahwa library ini kurang dimanfaatkan dan ada kemungkinan besar ada bug serius lainnya yang tidak terlihat di dalamnya.
Keluaran. Jika beberapa perpustakaan menyediakan fungsionalitas yang sama, maka sebaiknya lakukan analisis awal kualitasnya dan pilih yang paling teruji dan andal.
Bagaimana menganalisis
Oke, kami ingin memahami kualitas kode perpustakaan, tetapi bagaimana melakukannya? Ya, ini tidak mudah dilakukan. Anda tidak bisa pergi begitu saja dan melihat kodenya. Sebaliknya, Anda dapat melihat sesuatu, tetapi itu akan memberikan sedikit informasi. Selain itu, tinjauan semacam itu tidak mungkin membantu menilai kepadatan kesalahan dalam proyek.
Mari kembali ke pustaka matematika Universal yang disebutkan sebelumnya. Cobalah untuk menemukan kesalahan dalam kode fungsi ini. Sebenarnya, melihat komentar yang menyertainya, saya tidak bisa melewati tempat ini :).
// subtract module using SUBTRACTOR: CURRENTLY BROKEN FOR UNKNOWN REASON

template<size_t fbits, size_t abits>
void module_subtract_BROKEN(const value<fbits>& lhs, const value<fbits>& rhs,
value<abits + 1>& result) {
if (lhs.isinf() || rhs.isinf()) {
result.setinf();
return;
}
int lhs_scale = lhs.scale(),
rhs_scale = rhs.scale(),
scale_of_result = std::max(lhs_scale, rhs_scale);
// align the fractions
bitblock<abits> r1 = lhs.template nshift<abits>(lhs_scale-scale_of_result+3);
bitblock<abits> r2 = rhs.template nshift<abits>(rhs_scale-scale_of_result+3);
bool r1_sign = lhs.sign(), r2_sign = rhs.sign();
if (r1_sign) r1 = twos_complement(r1);
if (r1_sign) r2 = twos_complement(r2);
if (_trace_value_sub) {
std::cout << (r1_sign ? "sign -1" : "sign 1") << " scale "
<< std::setw(3) << scale_of_result << " r1 " << r1 << std::endl;
std::cout << (r2_sign ? "sign -1" : "sign 1") << " scale "
<< std::setw(3) << scale_of_result << " r2 " << r2 << std::endl;
}
bitblock<abits + 1> difference;
const bool borrow = subtract_unsigned(r1, r2, difference);
if (_trace_value_sub) std::cout << (r1_sign ? "sign -1" : "sign 1")
<< " borrow" << std::setw(3) << (borrow ? 1 : 0) << " diff "
<< difference << std::endl;
long shift = 0;
if (borrow) { // we have a negative value result
difference = twos_complement(difference);
}
// find hidden bit
for (int i = abits - 1; i >= 0 && difference[i]; i--) {
shift++;
}
assert(shift >= -1);
if (shift >= long(abits)) { // we have actual 0
difference.reset();
result.set(false, 0, difference, true, false, false);
return;
}
scale_of_result -= shift;
const int hpos = abits - 1 - shift; // position of the hidden bit
difference <<= abits - hpos + 1;
if (_trace_value_sub) std::cout << (borrow ? "sign -1" : "sign 1")
<< " scale " << std::setw(3) << scale_of_result << " result "
<< difference << std::endl;
result.set(borrow, scale_of_result, difference, false, false, false);
}
Saya yakin, meskipun saya menyarankan bahwa ada kesalahan dalam kode ini, tidak mudah untuk menemukannya.
Jika tidak ditemukan, maka ini dia. Peringatan PVS-Studio: V581 Ekspresi bersyarat dari pernyataan 'jika' yang terletak berdampingan adalah identik. Periksa baris: 789, 790. value.hpp 790
if (r1_sign) r1 = twos_complement(r1);
if (r1_sign) r2 = twos_complement(r2);
Salah ketik klasik. Pada kondisi kedua, variabel r2_sign harus dicentang .
Secara umum, Anda bisa melupakan tinjauan kode "manual". Ya, jalur seperti itu mungkin saja, tetapi memakan waktu yang tidak masuk akal.
Apa yang saya sarankan? Sangat sederhana. Gunakan analisis kode statis .
Periksa perpustakaan yang ingin Anda gunakan. Mulailah melihat laporan dan semuanya akan menjadi cukup jelas dengan cepat.
Anda bahkan tidak perlu analisis mendalam dan menyeluruh, dan Anda tidak perlu memfilter positif palsu. Anda hanya perlu membaca laporan dan memeriksa peringatannya. Positif palsu karena kurangnya pengaturan dapat dengan mudah bersabar dan fokus pada kesalahan.
Namun, positif palsu juga dapat diperhitungkan secara tidak langsung. Semakin banyak, semakin berantakan kodenya. Dengan kata lain, ada banyak trik dalam kode yang membingungkan penganalisis. Mereka juga membingungkan orang-orang yang mendukung proyek dan, akibatnya, berdampak negatif pada kualitasnya.
Catatan. Jangan lupa tentang ukuran proyek. Sebuah proyek besar akan selalu memiliki lebih banyak bug. Tetapi jumlah kesalahan tidak sama dengan kepadatan kesalahan. Pertimbangkan ini saat mengambil proyek dengan ukuran berbeda dan membuat penyesuaian.
Apa yang harus digunakan
Ada banyak alat analisis kode statis. Saya secara alami menyarankan untuk menggunakan penganalisis PVS-Studio . Ini bagus untuk evaluasi kualitas kode satu kali, dan untuk pencarian rutin serta memperbaiki bug.
Anda dapat memeriksa kode proyek di C, C ++, C # dan Java. Produk adalah hak milik. Namun, lisensi uji coba gratis akan lebih dari cukup untuk mengevaluasi kualitas beberapa pustaka sumber terbuka.
Saya juga mengingatkan Anda bahwa ada beberapa opsi lisensi gratis penganalisis untuk:
- siswa ;
- proyek sumber terbuka ;
- proyek tertutup (Anda perlu menambahkan komentar khusus ke kode);
- Microsoft MVP .
Kesimpulan
Metodologi analisis kode statis masih diremehkan oleh banyak programmer. Alasan yang mungkin untuk hal ini adalah pengalaman dengan alat kelas "linter" berisik sederhana yang bekerja sangat sederhana dan, sayangnya, seringkali pemeriksaan tidak terlalu berguna.
Bagi mereka yang ragu apakah perlu mencoba menerapkan penganalisis statis dalam proses pengembangan, dua publikasi berikut adalah:
- Cara menerapkan penganalisis kode statis dalam proyek lama dan tidak menurunkan motivasi tim .
- Alasan untuk memperkenalkan penganalisis kode statis PVS-Studio ke dalam proses pengembangan .
Terima kasih atas perhatian Anda, dan saya harap Anda mengurangi bug baik di kode Anda maupun di kode pustaka yang digunakan :).

Jika Anda ingin berbagi artikel ini dengan audiens berbahasa Inggris, silakan gunakan tautan terjemahan: Andrey Karpov. Mengapa penting untuk menerapkan analisis statis untuk pustaka terbuka yang Anda tambahkan ke proyek Anda .