Kisah tentang bagaimana penganalisa statis PVS-Studio dan kode OS Haiku bertemu kembali ke tahun 2015. Itu adalah eksperimen yang menarik dan pengalaman yang berguna untuk tim dari kedua proyek. Kenapa harus bereksperimen? Pada saat itu, kami tidak memiliki analisa untuk Linux dan kami tidak akan memilikinya untuk satu setengah tahun lagi. Bagaimanapun, upaya para penggemar dari tim kami telah dihargai: kami berkumpul dengan pengembang Haiku dan meningkatkan kualitas kode, memperluas basis kesalahan kami dengan bug langka yang dibuat oleh pengembang dan menyempurnakan penganalisis. Sekarang Anda dapat memeriksa kode Haiku untuk kesalahan dengan mudah dan cepat.
Pendahuluan
Temui karakter utama dari cerita kami -
Haiku dengan kode sumber terbuka dan penganalisa statis
PVS-Studio untuk C, C ++, C # dan Java. Ketika kami menggali analisis proyek 4,5 tahun yang lalu, kami hanya harus berurusan dengan file penganalisa yang dapat dieksekusi yang dikompilasi. Semua infrastruktur untuk parsing parameter kompiler, menjalankan preprosesor, sejajar dengan analisis dan seterusnya diambil dari utilitas
Compiler Monitoring UI , ditulis dalam C #. Utilitas itu porting sebagian ke platform Mono untuk dijalankan di Linux. Proyek Haiku dibangun menggunakan kompiler silang di bawah berbagai OS, kecuali Windows. Sekali lagi, saya ingin menyebutkan kenyamanan dan kelengkapan dokumentasi terkait dengan gedung Haiku. Saya juga ingin mengucapkan terima kasih kepada pengembang Haiku atas bantuan mereka dalam membangun proyek.
Jauh lebih mudah untuk melakukan analisis sekarang. Berikut adalah daftar semua perintah untuk membangun dan menganalisis proyek:
cd /opt git clone https:
Omong-omong, analisis proyek dilaksanakan dalam wadah Docker. Baru-baru ini kami telah menyiapkan dokumentasi baru tentang topik ini:
Menjalankan PVS-Studio di Docker . Ini dapat membuatnya sangat mudah bagi beberapa perusahaan untuk menerapkan teknik analisis statis untuk proyek mereka.
Variabel tidak diinisialisasi
V614 'rval' variabel tidak diinisialisasi digunakan. fetch.c 1727
int auto_fetch(int argc, char *argv[]) { volatile int argpos; int rval;
Variabel
rval belum diinisialisasi pada deklarasi, jadi perbandingannya dengan nilai nol akan menghasilkan hasil yang tidak ditentukan. Jika keadaan gagal, nilai tidak pasti dari variabel
rval dapat menjadi nilai kembali fungsi
auto_fetch .
V614 'Res' pointer tidak diinisialisasi digunakan. commands.c 2873
struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; char *ai_canonname; struct sockaddr *ai_addr; struct addrinfo *ai_next; }; static int sourceroute(struct addrinfo *ai, char *arg, char **cpp, int *lenp, int *protop, int *optp) { static char buf[1024 + ALIGNBYTES]; char *cp, *cp2, *lsrp, *ep; struct sockaddr_in *_sin; #ifdef INET6 struct sockaddr_in6 *sin6; struct ip6_rthdr *rth; #endif struct addrinfo hints, *res;
Berikut adalah kasus serupa menggunakan variabel tidak diinisialisasi, kecuali bahwa
res adalah pointer tidak diinisialisasi yang terjadi di sini.
Pointer
V506 ke variabel lokal 'dinormalisasi' disimpan di luar ruang lingkup variabel ini. Pointer seperti itu akan menjadi tidak valid. TextView.cpp 5596
void BTextView::_ApplyStyleRange(...., const BFont* font, ....) { if (font != NULL) { BFont normalized = *font; _NormalizeFont(&normalized); font = &normalized; } .... fStyles->SetStyleRange(fromOffset, toOffset, fText->Length(), mode, font, color); }
Pemrogram mungkin perlu menormalkan objek menggunakan variabel perantara. Tapi sekarang pointer
font berisi pointer ke objek yang
dinormalisasi , yang akan dihapus setelah keluar dari ruang lingkup, tempat objek sementara dibuat.
V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 27
int8 BUnicodeChar::Type(uint32 c) { BUnicodeChar(); return u_charType(c); }
Kesalahan yang sangat umum di antara programmer C ++ adalah menggunakan panggilan konstruktor yang seharusnya untuk menginisialisasi / membatalkan bidang kelas. Dalam hal ini, modifikasi bidang kelas tidak terjadi, tetapi objek baru yang tidak disebutkan namanya dari kelas ini dibuat dan kemudian segera dihancurkan. Sayangnya, ada banyak tempat seperti itu di proyek:
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 37
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 49
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 58
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 67
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 77
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 89
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 103
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 115
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 126
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 142
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 152
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 163
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 186
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 196
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 206
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 214
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 222
- V603 Objek telah dibuat tetapi tidak sedang digunakan. Jika Anda ingin memanggil konstruktor, 'this-> BUnicodeChar :: BUnicodeChar (....)' harus digunakan. UnicodeChar.cpp 230
V670 Anggota kelas 'fPatternHandler' yang tidak diinisialisasi digunakan untuk menginisialisasi anggota 'fInternal'. Ingat bahwa anggota diinisialisasi dalam urutan deklarasi mereka di dalam kelas. Painter.cpp 184
Painter::Painter() : fInternal(fPatternHandler), .... fPatternHandler(), .... { .... }; class Painter { .... private: mutable PainterAggInterface fInternal;
Contoh lain inisialisasi yang salah. Bidang kelas diinisialisasi dalam urutan deklarasi mereka di kelas itu sendiri. Dalam contoh ini, bidang
fInternal akan menjadi yang pertama menginisialisasi menggunakan nilai
fPatternHandler yang tidak diinisialisasi.
#Define mencurigakan
V523 Pernyataan 'lalu' sama dengan pernyataan 'lain'. subr_gtaskqueue.c 191
#define TQ_LOCK(tq) \ do { \ if ((tq)->tq_spin) \ mtx_lock_spin(&(tq)->tq_mutex); \ else \ mtx_lock(&(tq)->tq_mutex); \ } while (0) #define TQ_ASSERT_LOCKED(tq) mtx_assert(&(tq)->tq_mutex, MA_OWNED) #define TQ_UNLOCK(tq) \ do { \ if ((tq)->tq_spin) \ mtx_unlock_spin(&(tq)->tq_mutex); \ else \ mtx_unlock(&(tq)->tq_mutex); \ } while (0) void grouptask_block(struct grouptask *grouptask) { .... TQ_LOCK(queue); gtask->ta_flags |= TASK_NOENQUEUE; gtaskqueue_drain_locked(queue, gtask); TQ_UNLOCK(queue); }
Cuplikan kode ini tidak terlihat mencurigakan hingga Anda melihat hasil preprosesor:
void grouptask_block(struct grouptask *grouptask) { .... do { if ((queue)->tq_spin) mtx_lock(&(queue)->tq_mutex); else mtx_lock(&(queue)->tq_mutex); } while (0); gtask->ta_flags |= 0x4; gtaskqueue_drain_locked(queue, gtask); do { if ((queue)->tq_spin) mtx_unlock(&(queue)->tq_mutex); else mtx_unlock(&(queue)->tq_mutex); } while (0); }
Alat analisa benar-benar benar -
jika dan cabang lainnya identik. Tetapi di
manakah fungsi
mtx_lock_spin dan
mtx_unlock_spin ? Macros
TQ_LOCK ,
TQ_UNLOCK dan fungsi
grouptask_block dideklarasikan dalam satu file hampir di sebelah satu sama lain, tetapi meskipun demikian terjadi penggantian di suatu tempat di sini.
Pencarian melalui file hanya menghasilkan
mutex.h dengan konten berikut:
#define mtx_lock_spin(x) mtx_lock(x) #define mtx_unlock_spin(x) mtx_unlock(x)
Pengembang proyek harus memeriksanya apakah penggantian semacam itu benar atau tidak. Saya memeriksa proyek ini di Linux dan penggantian seperti itu tampak mencurigakan bagi saya.
Kesalahan dengan fungsi bebas
V575 Penunjuk nol dilewatkan ke fungsi 'bebas'. Periksa argumen pertama. setmime.cpp 727
void MimeType::_PurgeProperties() { fShort.Truncate(0); fLong.Truncate(0); fPrefApp.Truncate(0); fPrefAppSig.Truncate(0); fSniffRule.Truncate(0); delete fSmallIcon; fSmallIcon = NULL; delete fBigIcon; fBigIcon = NULL; fVectorIcon = NULL;
Anda dapat melewatkan pointer nol di fungsi
bebas , tetapi penggunaan seperti itu pasti mencurigakan. Dengan demikian, penganalisa menemukan campuran garis kode. Pertama, pembuat kode harus melepaskan memori dengan pointer
fVectorIcon , hanya setelah itu tetapkan
NULL .
V575 Penunjuk nol dilewatkan ke fungsi 'bebas'. Periksa argumen pertama. driver_settings.cpp 461
static settings_handle * load_driver_settings_from_file(int file, const char *driverName) { .... handle = new_settings(text, driverName); if (handle != NULL) {
Ini adalah contoh lain dari pengoperan null pointer secara eksplisit ke fungsi
bebas . Baris ini dapat dihapus, saat fungsi keluar setelah mendapatkan pointer dengan sukses.
V575 Penunjuk nol dilewatkan ke fungsi 'bebas'. Periksa argumen pertama. PackageFileHeapWriter.cpp 166
void* _GetBuffer() { .... void* buffer = malloc(fBufferSize); if (buffer == NULL && !fBuffers.AddItem(buffer)) { free(buffer); throw std::bad_alloc(); } return buffer; }
Seseorang membuat kesalahan di sini. Operator || harus digunakan alih-alih &&. Hanya dalam kasus ini pengecualian
std :: bad_alloc () akan dilemparkan seandainya alokasi memori (menggunakan fungsi
malloc ) gagal.
Kesalahan dengan operator hapus
V611 Memori dialokasikan menggunakan operator 'T baru] tetapi dirilis menggunakan operator' hapus '. Pertimbangkan untuk memeriksa kode ini. Mungkin lebih baik menggunakan 'delete [] fMsg;'. Err.cpp 65
class Err { public: .... private: char *fMsg; ssize_t fPos; }; void Err::Unset() { delete fMsg;
Pointer
fMsg digunakan untuk mengalokasikan memori untuk berbagai karakter. Operator
hapus digunakan untuk melepaskan memori alih-alih
menghapus [] .
V611 Memori dialokasikan menggunakan operator 'baru' tetapi dirilis menggunakan fungsi 'bebas'. Pertimbangkan untuk memeriksa logika operasi di belakang variabel 'wrapperPool'. vm_page.cpp 3080
status_t vm_page_write_modified_page_range(....) { .... PageWriteWrapper* wrapperPool = new(malloc_flags(allocationFlags)) PageWriteWrapper[maxPages + 1]; PageWriteWrapper** wrappers = new(malloc_flags(allocationFlags)) PageWriteWrapper*[maxPages]; if (wrapperPool == NULL || wrappers == NULL) { free(wrapperPool);
Di sini
malloc_flags adalah fungsi yang memanggil
malloc . Kemudian
penempatan-baru membangun objek di sini. Karena kelas
PageWriteWrapper diimplementasikan dengan cara berikut:
class PageWriteWrapper { public: PageWriteWrapper(); ~PageWriteWrapper(); void SetTo(vm_page* page); bool Done(status_t result); private: vm_page* fPage; struct VMCache* fCache; bool fIsActive; }; PageWriteWrapper::PageWriteWrapper() : fIsActive(false) { } PageWriteWrapper::~PageWriteWrapper() { if (fIsActive) panic("page write wrapper going out of scope but isn't completed"); }
penghancur objek dari kelas ini tidak akan dipanggil karena penggunaan fungsi
bebas untuk melepaskan memori.
V611 Memori dialokasikan menggunakan operator 'T baru] tetapi dirilis menggunakan operator' hapus '. Pertimbangkan untuk memeriksa kode ini. Mungkin lebih baik menggunakan 'delete [] fOutBuffer;'. Periksa baris: 26, 45. PCL6Rasterizer.h 26
class PCL6Rasterizer : public Rasterizer { public: .... ~PCL6Rasterizer() { delete fOutBuffer; fOutBuffer = NULL; } .... virtual void InitializeBuffer() { fOutBuffer = new uchar[fOutBufferSize]; } private: uchar* fOutBuffer; int fOutBufferSize; };
Ini adalah kesalahan umum untuk menggunakan operator
hapus alih-alih
menghapus []. Cara termudah untuk membuat kesalahan saat menulis kelas, karena kode destruktor sering jauh dari lokasi memori. Di sini, programmer secara tidak benar membebaskan memori yang disimpan oleh pointer
fOutBuffer di destructor.
V772 Memanggil operator 'hapus' untuk pointer kosong akan menyebabkan perilaku tidak terdefinisi. Hashtable.cpp 207
void Hashtable::MakeEmpty(int8 keyMode,int8 valueMode) { .... for (entry = fTable[index]; entry; entry = next) { switch (keyMode) { case HASH_EMPTY_DELETE:
Selain pilihan yang salah antara
hapus /
hapus [] dan
gratis , Anda juga dapat mengalami perilaku yang tidak terdefinisi saat mencoba menghapus memori dengan pointer ke tipe
void (void *) .
Fungsi tanpa nilai kembali
Fungsi V591 Non-void harus mengembalikan nilai. Referenceable.h 228
BReference& operator=(const BReference<const Type>& other) { fReference = other.fReference; }
Operator penugasan yang kelebihan muatan tidak memiliki nilai balik. Dalam hal ini, operator akan mengembalikan nilai acak, yang dapat menyebabkan kesalahan aneh.
Berikut adalah masalah serupa di fragmen kode lain dari kelas ini:
- Fungsi V591 Non-void harus mengembalikan nilai. Referenceable.h 233
- Fungsi V591 Non-void harus mengembalikan nilai. Referenceable.h 239
Fungsi V591 Non-void harus mengembalikan nilai. main.c 1010
void errx(int, const char *, ...) ; char * getoptionvalue(const char *name) { struct option *c; if (name == NULL) errx(1, "getoptionvalue() invoked with NULL name"); c = getoption(name); if (c != NULL) return (c->value); errx(1, "getoptionvalue() invoked with unknown option '%s'", name); }
Komentar pengguna NOTREACHED tidak berarti apa-apa di sini. Anda perlu membuat anotasi fungsi sebagai noreturn agar dapat menulis kode dengan benar untuk skenario semacam itu. Untuk melakukan ini, ada atribut noreturn: standar dan khusus-kompiler. Pertama-tama, atribut-atribut ini diperhitungkan oleh kompiler untuk pembuatan kode yang tepat atau pemberitahuan tentang jenis bug tertentu yang menggunakan peringatan. Berbagai alat analisis statis juga memperhitungkan atribut akun untuk meningkatkan kualitas analisis.
Menangani pengecualian
V596 Objek telah dibuat tetapi tidak sedang digunakan. Kata kunci 'lempar' bisa hilang: lempar ParseException (FOO); Response.cpp 659
size_t Response::ExtractNumber(BDataIO& stream) { BString string = ExtractString(stream); const char* end; size_t number = strtoul(string.String(), (char**)&end, 10); if (end == NULL || end[0] != '\0') ParseException("Invalid number!"); return number; }
Pelemparan kata kunci tidak sengaja terlupakan di sini. Dengan demikian, pengecualian
ParseException tidak akan dihasilkan sementara objek kelas ini akan dihancurkan saat keluar dari ruang lingkup. Setelah itu, fungsi akan terus bekerja seolah-olah tidak ada yang terjadi, seolah-olah nomor yang benar telah dimasukkan.
V1022 Pengecualian dilemparkan oleh pointer. Pertimbangkan untuk melemparkannya dengan nilai saja. gensyscallinfos.cpp 316
int main(int argc, char** argv) { try { return Main().Run(argc, argv); } catch (Exception& exception) {
Penganalisa mendeteksi pengecualian
IOException yang dilemparkan oleh pointer. Melempar pointer mengarah pada fakta bahwa pengecualian tidak akan ditangkap. Jadi pengecualian akhirnya tertangkap oleh referensi. Selain itu, penggunaan pointer memaksa sisi penangkap untuk memanggil operator
hapus untuk menghancurkan objek yang dibuat, yang belum dilakukan.
Beberapa fragmen kode lain yang bermasalah:
- V1022 Pengecualian dilemparkan oleh pointer. Pertimbangkan untuk melemparkannya dengan nilai saja. gensyscallinfos.cpp 347
- V1022 Pengecualian dilemparkan oleh pointer. Pertimbangkan untuk melemparkannya dengan nilai saja. gensyscallinfos.cpp 413
Keamanan formal
V597 Kompiler dapat menghapus panggilan fungsi 'memset', yang digunakan untuk flush objek 'f_key'. Fungsi memset_s () harus digunakan untuk menghapus data pribadi. dst_api.c 1018
#ifndef SAFE_FREE #define SAFE_FREE(a) \ do{if(a != NULL){memset(a,0, sizeof(*a)); free(a); a=NULL;}} while (0) .... #endif DST_KEY * dst_free_key(DST_KEY *f_key) { if (f_key == NULL) return (f_key); if (f_key->dk_func && f_key->dk_func->destroy) f_key->dk_KEY_struct = f_key->dk_func->destroy(f_key->dk_KEY_struct); else { EREPORT(("dst_free_key(): Unknown key alg %d\n", f_key->dk_alg)); } if (f_key->dk_KEY_struct) { free(f_key->dk_KEY_struct); f_key->dk_KEY_struct = NULL; } if (f_key->dk_key_name) SAFE_FREE(f_key->dk_key_name); SAFE_FREE(f_key); return (NULL); }
Penganalisa telah mendeteksi kode yang mencurigakan, dimaksudkan untuk mengamankan data pribadi. Sayangnya, makro
SAFE_FREE yang meluas ke
memset , panggilan
gratis , dan penugasan
NULL tidak membuat kode lebih aman, karena semuanya dihapus oleh kompiler saat dioptimalkan dengan
O2 .
By the way, itu tidak lain adalah
CWE-14 : Penghapusan Kompiler Kode ke Hapus Buffer.
Berikut adalah daftar tempat, di mana pembersihan buffer tidak dilakukan pada kenyataannya:
- V597 Kompiler dapat menghapus pemanggilan fungsi 'memset', yang digunakan untuk membersihkan buffer 'encoded_block'. Fungsi memset_s () harus digunakan untuk menghapus data pribadi. dst_api.c 446
- V597 Kompiler dapat menghapus panggilan fungsi 'memset', yang digunakan untuk menyiram objek 'key_st'. Fungsi memset_s () harus digunakan untuk menghapus data pribadi. dst_api.c 685
- V597 Kompiler dapat menghapus pemanggilan fungsi 'memset', yang digunakan untuk membersihkan buffer 'in_buff'. Fungsi memset_s () harus digunakan untuk menghapus data pribadi. dst_api.c 916
- V597 Kompiler dapat menghapus panggilan fungsi 'memset', yang digunakan untuk menyiram objek 'ce'. Fungsi memset_s () harus digunakan untuk menghapus data pribadi. fs_cache.c 1078
Perbandingan dengan variabel yang tidak ditandatangani
Ekspresi
V547 'tersisa <0' selalu salah. Nilai tipe yang tidak ditandatangani tidak pernah <0. DwarfFile.cpp 1947
status_t DwarfFile::_UnwindCallFrame(....) { .... uint64 remaining = lengthOffset + length - dataReader.Offset(); if (remaining < 0) return B_BAD_DATA; .... }
Penganalisa menemukan perbandingan eksplisit dari variabel unsigned dengan nilai negatif. Mungkin, seseorang harus membandingkan variabel yang
tersisa hanya dengan nol atau mengimplementasikan pemeriksaan untuk overflow.
V547 Ekspresi 'sleep ((unsigned) secs) <0' selalu salah. Nilai tipe yang tidak ditandatangani tidak pernah <0. Misc.cpp 56
status_t snooze(bigtime_t amount) { if (amount <= 0) return B_OK; int64 secs = amount / 1000000LL; int64 usecs = amount % 1000000LL; if (secs > 0) { if (sleep((unsigned)secs) < 0)
Untuk mendapatkan poin utama dari kesalahan, mari kita membahas tanda tangan fungsi
sleep dan
usleep :
extern unsigned int sleep (unsigned int __seconds); extern int usleep (__useconds_t __useconds);
Seperti yang dapat kita lihat, fungsi
tidur mengembalikan nilai yang tidak ditandatangani dan penggunaannya dalam kode salah.
Pointer berbahaya
V774 Pointer 'perangkat' digunakan setelah memori dilepaskan. xhci.cpp 1572
void XHCI::FreeDevice(Device *device) { uint8 slot = fPortSlots[device->HubPort()]; TRACE("FreeDevice() port %d slot %d\n", device->HubPort(), slot);
Objek
perangkat dibuat oleh operator
hapus . Ini cukup logis untuk fungsi
FreeDevice . Tetapi, untuk beberapa alasan, untuk melepaskan sumber daya lain, objek yang sudah dihapus ditangani.
Kode tersebut sangat berbahaya dan dapat dipenuhi di beberapa tempat lain:
- V774 Pointer 'mandiri' digunakan setelah memori dilepaskan. TranslatorRoster.cpp 884
- V774 Pointer 'string' digunakan setelah memori dilepaskan. RemoteView.cpp 1269
- V774 Pointer 'bs' digunakan setelah memori dilepaskan. mkntfs.c 4291
- V774 Pointer 'bs' digunakan setelah memori dilepaskan. mkntfs.c 4308
- V774 Pointer 'al' digunakan setelah memori dialokasikan kembali. inode.c 1155
V522 Dereferencing dari 'data' pointer nol mungkin terjadi. Pointer nol dilewatkan ke fungsi 'malo_hal_send_helper'. Periksa argumen ketiga. Periksa baris: 350, 394. if_malohal.c 350
static int malo_hal_fwload_helper(struct malo_hal *mh, char *helper) { .... error = malo_hal_send_helper(mh, 0, NULL, 0, MALO_NOWAIT);
Analisis antar prosedural mengungkapkan kasus ketika
NULL diteruskan ke fungsi dan penunjuk
data dengan nilai seperti itu akhirnya direferensikan dalam fungsi
memcpy .
V773 Fungsi itu keluar tanpa melepaskan pointer 'inputFileFile'. Kebocoran memori dimungkinkan. command_recompress.cpp 119
int command_recompress(int argc, const char* const* argv) { .... BFile* inputFileFile = new BFile; error = inputFileFile->SetTo(inputPackageFileName, O_RDONLY); if (error != B_OK) { fprintf(stderr, "Error: Failed to open input file \"%s\": %s\n", inputPackageFileName, strerror(error)); return 1; } inputFile = inputFileFile; .... }
PVS-Studio dapat mendeteksi kebocoran memori . Dalam contoh ini, jika terjadi kesalahan, memori tidak akan dirilis. Seseorang mungkin berpikir bahwa jika terjadi kesalahan Anda tidak perlu repot dengan rilis memori, karena program masih akan berakhir. Tetapi tidak selalu demikian. Ini adalah persyaratan bagi banyak program untuk menangani kesalahan dengan benar dan untuk terus bekerja.
V595 Pointer 'fReply' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 49, 52. BalasBuilder.cpp 49
RPC::CallbackReply* ReplyBuilder::Reply() { fReply->Stream().InsertUInt(fStatusPosition, _HaikuErrorToNFS4(fStatus)); fReply->Stream().InsertUInt(fOpCountPosition, fOpCount); if (fReply == NULL || fReply->Stream().Error() == B_OK) return fReply; else return NULL; }
Ini adalah kesalahan yang sangat umum untuk pointer dereferensi sebelum memeriksanya. Diagnostik
V595 hampir selalu berlaku dalam jumlah peringatan dalam suatu proyek. Fragmen kode ini termasuk penggunaan pointer
fReply yang
berbahaya .
V595 Pointer 'mq' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 782, 786. oce_queue.c 782
static void oce_mq_free(struct oce_mq *mq) { POCE_SOFTC sc = (POCE_SOFTC) mq->parent; struct oce_mbx mbx; struct mbx_destroy_common_mq *fwcmd; if (!mq) return; .... }
Contoh serupa. Pointer
mg akan direferensikan beberapa baris lebih awal dari yang diperiksa untuk null. Ada banyak tempat serupa dalam proyek ini. Dalam beberapa cuplikan, penggunaan dan pemeriksaan pointer cukup jauh dari satu sama lain, jadi dalam artikel ini Anda hanya akan menemukan beberapa contoh seperti itu. Pengembang dipersilakan untuk memeriksa contoh lain dalam laporan analisa lengkap.
Lain-lain
V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 101
static void dump_acpi_namespace(acpi_ns_device_info *device, char *root, int indenting) { char output[320]; char tabs[255] = ""; .... strlcat(tabs, "|--- ", sizeof(tabs)); .... while (....) { uint32 type = device->acpi->get_object_type(result); snprintf(output, sizeof(output), "%s%s", tabs, result + depth); switch(type) { case ACPI_TYPE_INTEGER: strncat(output, " INTEGER", sizeof(output)); break; case ACPI_TYPE_STRING: strncat(output, " STRING", sizeof(output)); break; .... } .... } .... }
Perbedaan antara fungsi
strlcat dan
strncat tidak terlalu jelas bagi seseorang yang tidak terbiasa dengan deskripsi fungsi-fungsi ini. Fungsi
strlcat mengharapkan ukuran seluruh buffer sebagai argumen ketiga sementara fungsi
strncat - ukuran ruang kosong dalam buffer, yang mengharuskan mengevaluasi nilai yang diperlukan sebelum memanggil fungsi. Tetapi pengembang sering lupa atau tidak tahu tentang itu. Melewati seluruh ukuran buffer ke fungsi
strncat dapat menyebabkan buffer overflow, karena fungsi akan mempertimbangkan nilai ini sebagai jumlah karakter yang dapat disalin. Fungsi
strlcat tidak memiliki masalah seperti itu. Tetapi Anda harus melewatkan string, diakhiri dengan terminal null sehingga itu berfungsi dengan baik.
Berikut ini seluruh daftar tempat berbahaya dengan string:
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 104
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 107
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 110
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 113
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 118
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 119
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 120
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 123
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 126
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 129
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 132
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 135
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 138
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 141
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'output'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. NamespaceDump.cpp 144
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'fitur_string'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. VirtioDevice.cpp 283
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'fitur_string'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. VirtioDevice.cpp 284
- V645 Panggilan fungsi 'strncat' dapat menyebabkan buffer overflow 'fitur_string'. Batas tidak boleh berisi ukuran buffer, tetapi sejumlah karakter dapat menampungnya. VirtioDevice.cpp 285
V792 Fungsi 'SetDecoratorSettings' terletak di sebelah kanan operator '|' akan dipanggil terlepas dari nilai operan kiri. Mungkin, lebih baik menggunakan '||'. DesktopListener.cpp 324
class DesktopListener : public DoublyLinkedListLinkImpl<DesktopListener> { public: .... virtual bool SetDecoratorSettings(Window* window, const BMessage& settings) = 0; .... }; bool DesktopObservable::SetDecoratorSettings(Window* window, const BMessage& settings) { if (fWeAreInvoking) return false; InvokeGuard invokeGuard(fWeAreInvoking); bool changed = false; for (DesktopListener* listener = fDesktopListenerList.First(); listener != NULL; listener = fDesktopListenerList.GetNext(listener)) changed = changed | listener->SetDecoratorSettings(window, settings); return changed; }
Kemungkinan besar, '|' dan '||' operator kacau. Kesalahan ini menyebabkan panggilan yang tidak perlu dari fungsi
SetDecoratorSettings .
V627 Pertimbangkan untuk memeriksa ekspresi. Argumen sizeof () adalah makro yang diperluas ke angka. device.c 72
#define PCI_line_size 0x0c static status_t wb840_open(const char* name, uint32 flags, void** cookie) { .... data->wb_cachesize = gPci->read_pci_config(data->pciInfo->bus, data->pciInfo->device, data->pciInfo->function, PCI_line_size, sizeof(PCI_line_size)) & 0xff; .... }
Melewati nilai
0x0c ke
ukuran operator terlihat mencurigakan. Mungkin, penulis seharusnya mengevaluasi ukuran suatu objek, misalnya
data .
V562 Aneh membandingkan nilai tipe bool dengan nilai 18: 0x12 == IsProfessionalSpdif (). CEchoGals_mixer.cpp 533
typedef bool BOOL; virtual BOOL IsProfessionalSpdif() { ... } #define ECHOSTATUS_DSP_DEAD 0x12 ECHOSTATUS CEchoGals::ProcessMixerFunction(....) { .... if ( ECHOSTATUS_DSP_DEAD == IsProfessionalSpdif() )
Fungsi
IsProfessionalSpdif mengembalikan nilai tipe
bool . Dengan demikian, hasil fungsi dibandingkan dengan angka
0x12 dalam kondisi.
Kesimpulan
Kami melewatkan rilis beta Haiku pertama musim gugur yang lalu, karena kami sibuk merilis PVS-Studio untuk Jawa. Namun sifat kesalahan pemrograman adalah sedemikian rupa sehingga tidak hilang jika Anda tidak mencarinya dan tidak memperhatikan kualitas kode. Pengembang proyek menggunakan
Coverity Scan , tetapi yang terakhir hampir dua tahun yang lalu. Ini pasti mengecewakan pengguna Haiku. Meskipun analisis telah dikonfigurasi pada 2014 menggunakan Cakupan, itu tidak menghentikan kami dari menulis dua artikel panjang tentang tinjauan kesalahan pada tahun 2015 (
bagian 1 ,
bagian 2 )
Ulasan kesalahan Haiku lainnya akan segera keluar bagi mereka yang membaca posting ini sampai akhir. Laporan analisa lengkap akan dikirim ke pengembang sebelum memposting ulasan kesalahan ini, sehingga beberapa kesalahan mungkin diperbaiki pada saat Anda membaca ini. Untuk menghabiskan waktu di antara artikel, saya sarankan mengunduh dan mencoba
PVS-Studio untuk proyek Anda.
Apakah Anda ingin mencoba Haiku dan Anda memiliki pertanyaan? Pengembang Haiku mengundang Anda ke
saluran telegram .