Celestia adalah simulator ruang tiga dimensi. Simulasi ruang memungkinkan kita menjelajahi alam semesta kita dalam tiga dimensi. Celestia tersedia di Windows, Linux, dan macOS. Proyek ini sangat kecil dan di dalamnya, menggunakan PVS-Studio, sejumlah kecil cacat terdeteksi. Namun, kami benar-benar ingin memperhatikannya, karena ini adalah proyek pendidikan populer yang berguna untuk ditingkatkan. Omong-omong, program ini digunakan dalam film populer, seri, dan program untuk mewakili ruang. Yang juga meningkatkan persyaratan kualitas kode.
Pendahuluan
Di situs web resmi proyek
Celestia, Anda dapat menemukan deskripsi terperinci tentangnya. Kode sumber untuk proyek di-host di
GitHub . Tidak termasuk perpustakaan dan tes, alat analisa memeriksa 166 file .cpp. Proyek ini kecil, tetapi cacat yang ditemukan cukup menarik.
Untuk menganalisis kode, kami menggunakan penganalisa kode statis
PVS-Studio . Baik Celestia dan PVS-Studio adalah cross-platform. Untuk analisis, platform Windows dipilih. Proyek ini mudah dibangun dengan menarik dependensi menggunakan
Vcpkg , manajer perpustakaan Microsoft. Menurut ulasan, itu lebih rendah dari kemampuan
Conan , tetapi program ini juga nyaman digunakan.
Hasil analisis
Peringatan 1V501 Ada sub-ekspresi yang identik ke kiri dan ke kanan operator '<': b.nAttributes <b.nAttributes cmodfix.cpp 378
bool operator<(const Mesh::VertexDescription& a, const Mesh::VertexDescription& b) { if (a.stride < b.stride) return true; if (b.stride < a.stride) return false; if (a.nAttributes < b.nAttributes)
Betapa mudahnya membuat kesalahan saat menyalin kode. Kami menulis tentang ini di setiap ulasan. Rupanya, hanya analisis kode statis yang dapat membantu dalam situasi ini.
Pemrogram menyalin ekspresi bersyarat dan tidak sepenuhnya mengeditnya. Pilihan yang benar kemungkinan besar adalah ini:
if (a.nAttributes < b.nAttributes) return true; if (b.nAttributes < a.nAttributes) return false;
Sebuah studi menarik tentang topik ini: "
Evil hidup dalam fungsi perbandingan ."
Peringatan 2V575 Fungsi 'memset' memproses elemen '0'. Periksa argumen ketiga. winmain.cpp 2235
static void BuildScriptsMenu(HMENU menuBar, const fs::path& scriptsDir) { .... MENUITEMINFO info; memset(&info, sizeof(info), 0); info.cbSize = sizeof(info); info.fMask = MIIM_SUBMENU; .... }
Penulis kode menggabungkan argumen kedua dan ketiga dengan fungsi
memset . Alih-alih mengisi struktur dengan nol, ini ditunjukkan untuk mengisi 0 byte memori.
Peringatan 3V595 Pointer 'tujuan' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 48, 50. wintourguide.cpp 48
BOOL APIENTRY TourGuideProc(....) { .... const DestinationList* destinations = guide->appCore->getDestinations(); Destination* dest = (*destinations)[0]; guide->selectedDest = dest; if (hwnd != NULL && destinations != NULL) { .... } .... }
Pointer
tujuan dereferenced dua baris lebih tinggi daripada dibandingkan dengan
NULL . Kode semacam itu berpotensi menyebabkan kesalahan.
Peringatan 4Kelas
V702 harus selalu diturunkan dari std :: exception (dan yang sama) sebagai 'publik' (tidak ada kata kunci yang ditentukan, jadi kompiler secara default ke 'private'). fs.h 21
class filesystem_error : std::system_error { public: filesystem_error(std::error_code ec, const char* msg) : std::system_error(ec, msg) { } };
Penganalisa mendeteksi kelas yang diwarisi dari kelas
std :: exception melalui pengubah
pribadi (ditentukan secara default). Warisan ini berbahaya karena dalam kasus warisan non-publik, ketika mencoba menangkap
std :: exception exception , ia akan dilewati. Akibatnya, penangan pengecualian tidak berperilaku sebagaimana dimaksud.
Peringatan 5V713 Pointer 's' digunakan dalam ekspresi logis sebelum diverifikasi terhadap nullptr dalam ekspresi logis yang sama. winmain.cpp 3031
static char* skipUntilQuote(char* s) { while (*s != '"' && s != '\0') s++; return s; }
Di satu tempat ekspresi kondisional, mereka lupa untuk merujuk pointer. Hasilnya adalah perbandingan pointer, bukan nilai di atasnya. Dan ini tidak masuk akal dalam situasi ini.
Peringatan 6V773 Fungsi itu keluar tanpa melepaskan pointer 'vertexShader'. Kebocoran memori dimungkinkan. modelviewwidget.cpp 1517
GLShaderProgram* ModelViewWidget::createShader(const ShaderKey& shaderKey) { .... auto* glShader = new GLShaderProgram(); auto* vertexShader = new GLVertexShader(); if (!vertexShader->compile(vertexShaderSource.toStdString())) { qWarning("Vertex shader error: %s", vertexShader->log().c_str()); std::cerr << vertexShaderSource.toStdString() << std::endl; delete glShader; return nullptr; } .... }
Saat Anda keluar dari fungsi, memori dibebaskan oleh pointer
glShader , tetapi tidak dihapus oleh pointer
vertexShader .
Tempat yang sama lebih rendah dalam kode:
- V773 Fungsi itu keluar tanpa melepaskan pointer 'fragmentShader'. Kebocoran memori dimungkinkan. modelviewwidget.cpp 1526
Peringatan 7Ekspresi
V547 '! InputFilename.empty ()' selalu benar. makexindex.cpp 128
int main(int argc, char* argv[]) { if (!parseCommandLine(argc, argv) || inputFilename.empty()) { Usage(); return 1; } istream* inputFile = &cin; if (!inputFilename.empty()) { inputFile = new ifstream(inputFilename, ios::in); if (!inputFile->good()) { cerr << "Error opening input file " << inputFilename << '\n'; return 1; } } .... }
Periksa kembali nama file. Ini bukan kesalahan, tetapi karena fakta bahwa variabel
inputFilename sudah
diperiksa di awal fungsi, verifikasi dapat dihapus di bawah ini, yang akan membuat kode lebih kompak.
Peringatan 8V556 Nilai dari berbagai tipe enum dibandingkan: switch (ENUM_TYPE_A) {case ENUM_TYPE_B: ...}. render.cpp 7457
enum LabelAlignment { AlignCenter, AlignLeft, AlignRight }; enum LabelVerticalAlignment { VerticalAlignCenter, VerticalAlignBottom, VerticalAlignTop, }; struct Annotation { .... LabelVerticalAlignment valign : 3; .... }; void Renderer::renderAnnotations(....) { .... switch (annotations[i].valign) { case AlignCenter: vOffset = -font[fs]->getHeight() / 2; break; case VerticalAlignTop: vOffset = -font[fs]->getHeight(); break; case VerticalAlignBottom: vOffset = 0; break; } .... }
Dalam
pernyataan beralih , nilai enumerasi bingung. Karena itu, enumerasi tipe yang berbeda dibandingkan di satu tempat:
LabelVerticalAlignment dan
AlignCenter .
Peringatan 9V581 Ekspresi kondisional dari pernyataan 'jika' yang terletak berdampingan adalah identik. Periksa baris: 2844, 2850. shadermanager.cpp 2850
GLVertexShader* ShaderManager::buildParticleVertexShader(const ShaderProperties& props) { .... if (props.texUsage & ShaderProperties::PointSprite) { source << "uniform float pointScale;\n"; source << "attribute float pointSize;\n"; } if (props.texUsage & ShaderProperties::PointSprite) { source << DeclareVarying("pointFade", Shader_Float); } .... }
Penganalisa mendeteksi dua ekspresi kondisional yang identik dalam satu baris. Ada kesalahan atau dua kondisi dapat digabungkan menjadi satu, dan dengan demikian menyederhanakan kode.
Peringatan 10V668 Tidak ada gunanya menguji pointer 'dp' terhadap null, karena memori dialokasikan menggunakan operator 'baru'. Pengecualian akan dihasilkan jika terjadi kesalahan alokasi memori. windatepicker.cpp 625
static LRESULT DatePickerCreate(HWND hwnd, CREATESTRUCT& cs) { DatePicker* dp = new DatePicker(hwnd, cs); if (dp == NULL) return -1; .... }
Nilai pointer yang dikembalikan oleh operator
baru dibandingkan dengan nol. Jika operator tidak dapat mengalokasikan memori, maka sesuai dengan standar bahasa C ++, pengecualian
std :: bad_alloc () dihasilkan. Jadi, memeriksa pointer ke kesetaraan ke nol tidak masuk akal.
Tiga cek serupa lainnya:
- V668 Tidak ada gunanya menguji pointer 'mode' terhadap null, karena memori dialokasikan menggunakan operator 'baru'. Pengecualian akan dihasilkan jika terjadi kesalahan alokasi memori. winmain.cpp 2967
- V668 Tidak ada gunanya menguji pointer 'dropTarget' terhadap null, karena memori dialokasikan menggunakan operator 'baru'. Pengecualian akan dihasilkan jika terjadi kesalahan alokasi memori. winmain.cpp 3272
- V668 Tidak ada gunanya menguji pointer 'appCore' terhadap nol, karena memori dialokasikan menggunakan operator 'baru'. Pengecualian akan dihasilkan jika terjadi kesalahan alokasi memori. winmain.cpp 3352
Peringatan 11V624 Konstanta 3.14159265 sedang digunakan. Nilai yang dihasilkan mungkin tidak akurat. Pertimbangkan untuk menggunakan konstanta M_PI dari <math.h>. 3dstocmod.cpp 62
int main(int argc, char* argv[]) { .... Model* newModel = GenerateModelNormals(*model, float(smoothAngle * 3.14159265 / 180.0), weldVertices, weldTolerance); .... }
Diagnostik direkomendasikan, tetapi lebih baik menggunakan konstanta siap pakai untuk nomor Pi dari perpustakaan standar.
Kesimpulan
Baru-baru ini, proyek ini telah dikembangkan oleh para penggemar, tetapi masih populer dan diminati dalam program pelatihan. Di Internet ada ribuan add-on dengan objek ruang angkasa yang berbeda. Celestia digunakan dalam
The Day After Tomorrow dan seri dokumenter
Through the Wormhole dengan Morgan Freeman .
Kami senang bahwa dengan memeriksa
banyak proyek sumber terbuka, kami tidak hanya mempopulerkan metodologi analisis kode statis, tetapi juga berkontribusi pada pengembangan dunia proyek terbuka. Omong-omong, Anda juga dapat menggunakan penganalisa PVS-Studio tidak hanya untuk menguji proyek Anda sendiri, tetapi juga proyek pihak ketiga sebagai penggila. Untuk melakukan ini, Anda dapat menggunakan salah satu
opsi untuk lisensi gratis.
Gunakan penganalisa kode statis, buat proyek Anda lebih andal dan lebih baik!

Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Svyatoslav Razmyslov.
Celestia: Petualangan Bugs di Luar Angkasa .