Saat ini, misalnya, untuk mengembangkan game, tidak ada lagi kebutuhan untuk secara mandiri menerapkan fisika objek dari awal, karena ada banyak perpustakaan untuk ini. Bullet pernah digunakan secara aktif di banyak game AAA, proyek realitas virtual, berbagai simulasi dan pembelajaran mesin. Ya, dan masih digunakan sampai sekarang, misalnya, sebagai salah satu mesin Red Dead Redemption dan Red Dead Redemption 2. Jadi mengapa tidak memeriksa Bullet dengan PVS-Studio untuk melihat kesalahan apa yang dapat dideteksi oleh analisis statis dalam proyek berskala besar seperti itu, terkait dengan simulasi fisika.
Perpustakaan ini
didistribusikan secara bebas , sehingga setiap orang dapat menggunakannya secara opsional dalam proyek mereka. Selain Red Dead Redemption, mesin fisika ini juga digunakan dalam industri film untuk menciptakan efek khusus. Misalnya, ia terlibat dalam pembuatan film Sherlock Holmes oleh Guy Ritchie untuk menghitung tabrakan.
Jika ini adalah pertemuan pertama Anda dengan artikel di mana PVS-Studio memeriksa proyek, maka saya akan melakukan penyimpangan kecil.
PVS-Studio adalah penganalisa kode statis yang membantu menemukan kesalahan, kekurangan dan kerentanan potensial dalam kode sumber program C, C ++, C #, Java. Analisis statis adalah semacam proses peninjauan kode otomatis.
Untuk pemanasan
Contoh 1:Mari kita mulai dengan kesalahan lucu:
V624 Mungkin ada kesalahan cetak dalam konstanta '3.141592538'. Pertimbangkan untuk menggunakan konstanta M_PI dari <math.h>. PhysicsClientC_API.cpp 4109
B3_SHARED_API void b3ComputeProjectionMatrixFOV(float fov, ....) { float yScale = 1.0 / tan((3.141592538 / 180.0) * fov / 2); .... }
Kesalahan ketik kecil di nomor Pi (3.141592653 ...), hilang nomor "6" di posisi ke-7 di bagian fraksional.
Mungkin kesalahan di tempat desimal kesepuluh juta tidak akan menghasilkan konsekuensi nyata, tetapi Anda harus tetap menggunakan konstanta perpustakaan yang ada tanpa kesalahan ketik. Untuk Pi, ada konstanta
M_PI dari header
math.h.Salin tempel
Contoh 2:Kadang-kadang analisa memungkinkan Anda untuk menemukan kesalahan secara tidak langsung. Jadi, misalnya, di sini tiga argumen terkait
halfExtentsX, halfExtentsY, halfExtentsZ dilewatkan ke fungsi, tetapi yang terakhir tidak digunakan di mana pun dalam fungsi. Anda mungkin memperhatikan bahwa saat memanggil metode
addVertex , variabel
halfExtentsY digunakan dua kali. Jadi mungkin ini adalah kesalahan copy-paste dan di sini argumen yang dilupakan harus digunakan.
V751 Parameter 'halfExtentsZ' tidak digunakan di dalam fungsi body. TinyRenderer.cpp 375
void TinyRenderObjectData::createCube(float halfExtentsX, float halfExtentsY, float halfExtentsZ, ....) { .... m_model->addVertex(halfExtentsX * cube_vertices_textured[i * 9], halfExtentsY * cube_vertices_textured[i * 9 + 1], halfExtentsY * cube_vertices_textured[i * 9 + 2], cube_vertices_textured[i * 9 + 4], ....); .... }
Contoh 3:Penganalisa juga menemukan fragmen menarik berikut, saya akan membawanya pertama dalam bentuk aslinya.
Lihat baris terakhir ini?
Sangat aneh bahwa programmer memutuskan untuk menulis kondisi yang begitu lama dalam satu baris. Tetapi fakta bahwa kesalahan yang paling mungkin menyelinap ke dalamnya sama sekali tidak mengejutkan.
Penganalisa mengeluarkan peringatan berikut pada baris ini.
V501 Ada sub-ekspresi identik 'rotmat.Column1 (). Norm () <1.0001' di sebelah kiri dan di sebelah kanan operator '&&'. LinearR4.cpp 351
V501 Ada sub-ekspresi identik '0,9999 <rotmat.Column1 (). Norm ()' di sebelah kiri dan di sebelah kanan operator '&&'. LinearR4.cpp 351
Jika kita menulis semuanya dalam bentuk "tabular" visual, akan menjadi jelas bahwa pemeriksaan yang sama berlaku untuk
Column1 . Dua perbandingan terakhir menunjukkan bahwa ada
Column1 dan
Column2 . Kemungkinan besar, perbandingan ketiga dan keempat harus memeriksa nilai dari
Kolom2 .
Column1().Norm() < 1.0001 && 0.9999 < Column1().Norm() && Column1().Norm() < 1.0001 && 0.9999 < Column1().Norm() &&(Column1() ^ Column2()) < 0.001 && (Column1() ^ Column2()) > -0.001
Dalam bentuk ini, perbandingan yang sama menjadi jauh lebih terlihat.
Contoh 4:Kesalahan yang serupa:
V501 Ada sub-ekspresi identik 'cs.m_fJacCoeffInv [0] == 0' di sebelah kiri dan di sebelah kanan operator '&&'. b3CpuRigidBodyPipeline.cpp 169
float m_fJacCoeffInv[2]; static inline void b3SolveFriction(b3ContactConstraint4& cs, ....) { if (cs.m_fJacCoeffInv[0] == 0 && cs.m_fJacCoeffInv[0] == 0) { return; } .... }
Dalam hal ini, elemen array yang sama diperiksa dua kali. Kemungkinan besar, kondisinya akan terlihat seperti ini:
cs.m_fJacCoeffInv [0] == 0 && cs.m_fJacCoeffInv [1] == 0 . Ini adalah contoh klasik kesalahan salin-tempel.
Contoh 5:Kekurangan lain ditemukan:
V517 Penggunaan
pola 'jika (A) {...} else jika (A) {...}' terdeteksi. Ada kemungkinan kehadiran kesalahan logis. Periksa baris: 79, 112. main.cpp 79
int main(int argc, char* argv[]) { .... while (serviceResult > 0) { serviceResult = enet_host_service(client, &event, 0); if (serviceResult > 0) { .... } else if (serviceResult > 0) { puts("Error with servicing the client"); exit(EXIT_FAILURE); } .... } .... }
Fungsi
enet_host_service , yang hasilnya ditugaskan ke
serviceResult , mengembalikan satu jika
berhasil dan -1 jika gagal. Kemungkinan besar, yang
lain jika cabang seharusnya menanggapi nilai negatif dari
serviceResult , tetapi kondisi verifikasi digandakan. Kemungkinan besar ini juga merupakan kesalahan salin-tempel.
Peringatan penganalisa serupa, yang tidak masuk akal untuk dijelaskan dalam artikel:
V517 Penggunaan
pola 'jika (A) {...} else jika (A) {...}' terdeteksi. Ada kemungkinan kehadiran kesalahan logis. Periksa baris: 151, 190. PhysicsClientUDP.cpp 151
Melampaui batas: melampaui batas array
Contoh 6:Salah satu hal yang tidak menyenangkan untuk mencari kesalahan adalah keluar dari array. Seringkali kesalahan ini terjadi karena pengindeksan kompleks dalam loop.
Di sini, dalam kondisi loop, variabel
dofIndex terbatas pada 128 dari atas, dan
dof hingga 4, inklusif. Tetapi
m_desiredState juga hanya berisi 128 elemen. Akibatnya, indeks
[dofIndex + dof] dapat menyebabkan
keluarnya array.
V557 Array overrun dimungkinkan. Nilai indeks 'dofIndex + dof' bisa mencapai 130. PhysicsClientC_API.cpp 968
#define MAX_DEGREE_OF_FREEDOM 128 double m_desiredState[MAX_DEGREE_OF_FREEDOM]; B3_SHARED_API int b3JointControl(int dofIndex, double* forces, int dofCount, ....) { .... if ( (dofIndex >= 0) && (dofIndex < MAX_DEGREE_OF_FREEDOM ) && dofCount >= 0 && dofCount <= 4) { for (int dof = 0; dof < dofCount; dof++) { command->m_sendState.m_desiredState[dofIndex+dof] = forces[dof]; .... } } .... }
Contoh 7:Kesalahan serupa, tapi sekarang penjumlahan mengarah ke itu bukan saat mengindeks array, tetapi dalam kondisi. Jika nama file sepanjang mungkin, maka terminal nol akan ditulis di luar array (
Kesalahan Off-by-one ). Secara alami, variabel
len akan sama dengan
MAX_FILENAME_LENGTH hanya dalam kasus luar biasa, tetapi ini tidak menghilangkan kesalahan, tetapi hanya membuatnya jarang terjadi.
V557 Array overrun dimungkinkan. Nilai indeks 'len' bisa mencapai 1024. PhysicsClientC_API.cpp 5223
#define MAX_FILENAME_LENGTH MAX_URDF_FILENAME_LENGTH 1024 struct b3Profile { char m_name[MAX_FILENAME_LENGTH]; int m_durationInMicroSeconds; }; int len = strlen(name); if (len >= 0 && len < (MAX_FILENAME_LENGTH + 1)) { command->m_type = CMD_PROFILE_TIMING; strcpy(command->m_profile.m_name, name); command->m_profile.m_name[len] = 0; }
Ukur sekali - potong tujuh kali
Contoh 8:Dalam kasus di mana Anda perlu menggunakan hasil fungsi tertentu berkali-kali atau berulang kali menggunakan variabel yang perlu Anda akses melalui serangkaian panggilan, Anda harus menggunakan variabel sementara untuk mengoptimalkan dan membaca kode dengan lebih baik. Penganalisa menemukan lebih dari 100 tempat dalam kode di mana koreksi tersebut dapat dilakukan.
V807 Penurunan Kinerja. Pertimbangkan membuat pointer untuk menghindari penggunaan ekspresi 'm_app-> m_renderer-> getActiveCamera ()' berulang kali. InverseKinematicsExample.cpp 315
virtual void resetCamera() { .... if (....) { m_app->m_renderer->getActiveCamera()->setCameraDistance(dist); m_app->m_renderer->getActiveCamera()->setCameraPitch(pitch); m_app->m_renderer->getActiveCamera()->setCameraYaw(yaw); m_app->m_renderer->getActiveCamera()->setCameraPosition(....); } }
Di sini rantai panggilan yang sama digunakan kembali, yang dapat diganti dengan satu penunjuk.
Contoh 9:V810 Menurunkan Performa. Fungsi 'btCos (euler_out.pitch)' dipanggil beberapa kali dengan argumen yang identik. Hasilnya mungkin harus disimpan ke variabel sementara, yang kemudian dapat digunakan saat memanggil fungsi 'btAtan2'. btMatrix3x3.h 576
V810 Menurunkan Performa. Fungsi 'btCos (euler_out2.pitch)' dipanggil beberapa kali dengan argumen yang identik. Hasilnya mungkin harus disimpan ke variabel sementara, yang kemudian dapat digunakan saat memanggil fungsi 'btAtan2'. btMatrix3x3.h 578
void getEulerZYX(....) const { .... if (....) { .... } else { .... euler_out.roll = btAtan2(m_el[2].y() / btCos(euler_out.pitch), m_el[2].z() / btCos(euler_out.pitch)); euler_out2.roll = btAtan2(m_el[2].y() / btCos(euler_out2.pitch), m_el[2].z() / btCos(euler_out2.pitch)); euler_out.yaw = btAtan2(m_el[1].x() / btCos(euler_out.pitch), m_el[0].x() / btCos(euler_out.pitch)); euler_out2.yaw = btAtan2(m_el[1].x() / btCos(euler_out2.pitch), m_el[0].x() / btCos(euler_out2.pitch)); } .... }
Dalam kasus ini, Anda dapat membuat dua variabel dan menyimpan nilai yang dikembalikan oleh fungsi
btCos untuk
euler_out.pitch dan
euler_out2.pitch di dalamnya , alih-alih memanggil fungsi empat kali untuk setiap argumen.
Bocor
Contoh 10:Banyak kesalahan dari jenis berikut ini ditemukan dalam proyek:
V773 Ruang lingkup visibilitas dari pointer 'importir' dikeluarkan tanpa melepaskan memori. Kebocoran memori dimungkinkan. SerializeSetup.cpp 94
void SerializeSetup::initPhysics() { .... btBulletWorldImporter* importer = new btBulletWorldImporter(m_dynamicsWorld); .... fclose(file); m_guiHelper->autogenerateGraphicsObjects(m_dynamicsWorld); }
Tidak ada memori yang dibebaskan dari pointer
importir di sini. Ini dapat menyebabkan kebocoran memori. Dan untuk mesin fisik, ini mungkin tren yang buruk. Untuk menghindari kebocoran, cukup setelah variabel tidak lagi diperlukan untuk menambahkan
importir hapus . Tapi tentu saja, lebih baik menggunakan pointer pintar.
Ini undang-undang Anda
Contoh 11:Kesalahan berikut muncul dalam kode karena fakta bahwa aturan C ++ tidak selalu bertepatan dengan aturan matematika atau "akal sehat". Perhatikan sendiri di mana kesalahan ada dalam potongan kecil kode?
btAlignedObjectArray<btFractureBody*> m_fractureBodies; void btFractureDynamicsWorld::fractureCallback() { for (int i = 0; i < numManifolds; i++) { .... int f0 = m_fractureBodies.findLinearSearch(....); int f1 = m_fractureBodies.findLinearSearch(....); if (f0 == f1 == m_fractureBodies.size()) continue; .... } .... }
Penganalisis menghasilkan peringatan berikut:
V709 Perbandingan mencurigakan ditemukan: 'f0 == f1 == m_fractureBodies.size ()'. Ingat bahwa 'a == b == c' tidak sama dengan 'a == b && b == c'. btFractureDynamicsWorld.cpp 483
Tampaknya kondisi memeriksa bahwa
f0 sama dengan
f1 dan sama dengan jumlah elemen di
m_fractureBodies . Sepertinya perbandingan ini seharusnya memeriksa apakah
f0 dan
f1 berada di akhir array
m_fractureBodies , karena mereka berisi posisi objek yang ditemukan oleh metode
findLinearSearch () . Namun, pada kenyataannya, ungkapan ini berubah menjadi tes untuk melihat apakah
f0 dan
f1 sama, dan kemudian untuk memeriksa apakah
m_fractureBodies.size () sama dengan hasil
f0 == f1 . Akibatnya, operan ketiga dibandingkan dengan 0 atau 1 di sini.
Kesalahan indah! Dan, untungnya, sangat jarang. Sejauh ini kami hanya
bertemu dengannya di dua proyek terbuka, dan yang menarik, semuanya hanyalah mesin game.
Contoh 12:Ketika bekerja dengan string, seringkali lebih baik menggunakan fasilitas yang disediakan oleh kelas
string . Jadi, untuk dua kasus berikut, lebih baik mengganti
strlen (MyStr.c_str ()) dan
val = "" dengan
MyStr.length () dan
val.clear (), masing-masing.
V806 Menurunkan Kinerja. Ekspresi jenis strlen (MyStr.c_str ()) dapat ditulis ulang sebagai MyStr.length (). RobotLoggingUtil.cpp 213
FILE* createMinitaurLogFile(const char* fileName, std::string& structTypes, ....) { FILE* f = fopen(fileName, "wb"); if (f) { .... fwrite(structTypes.c_str(), strlen(structTypes.c_str()), 1, f); .... } .... }
V815 Menurunkan Performa. Pertimbangkan untuk mengganti ekspresi 'val = ""' dengan 'val.clear ()'. b3CommandLineArgs.h 40
void addArgs(int argc, char **argv) { .... std::string val; .... val = ""; .... }
Ada peringatan lain, tapi saya pikir Anda bisa berhenti. Seperti yang Anda lihat, analisis kode statis dapat mengungkapkan berbagai macam kesalahan.
Membaca tentang pemeriksaan proyek satu kali itu menarik, tetapi ini bukan cara yang tepat untuk menggunakan penganalisa kode statis. Dan tentang ini kita akan berbicara di bawah ini.
Kesalahan ditemukan sebelum kita
Sangat menarik dalam semangat artikel baru-baru ini "
Kesalahan yang tidak ditemukan oleh analisis kode statis karena tidak digunakan " untuk mencoba menemukan bug atau kekurangan yang telah diperbaiki, tetapi yang dapat dideteksi oleh penganalisa statis.
Tidak ada begitu banyak permintaan tarik dalam repositori, dan banyak dari mereka terkait dengan logika internal mesin. Tetapi ada juga kesalahan yang bisa dideteksi oleh penganalisa.
Contoh 13: char m_deviceExtensions[B3_MAX_STRING_LENGTH]; void b3OpenCLUtils_printDeviceInfo(cl_device_id device) { b3OpenCLDeviceInfo info; b3OpenCLUtils::getDeviceInfo(device, &info); .... if (info.m_deviceExtensions != 0) { .... } }
Edit komentar mengatakan bahwa itu perlu untuk memeriksa array untuk fakta bahwa itu tidak kosong, tetapi sebaliknya pemeriksaan pointer pointless dilakukan, yang selalu mengembalikan true. Ini ditunjukkan oleh peringatan PVS-Studio pada jenis cek awal:
V600 Pertimbangkan untuk memeriksa kondisinya. Pointer 'info.m_deviceExtensions' selalu tidak sama dengan NULL. b3OpenCLUtils.cpp 551
Contoh 14:Bisakah Anda segera mencari tahu apa masalahnya di fungsi berikutnya?
inline void Matrix4x4::SetIdentity() { m12 = m13 = m14 = m21 = m23 = m24 = m13 = m23 = m41 = m42 = m43 = 0.0; m11 = m22 = m33 = m44 = 1.0; }
Penganalisis menghasilkan peringatan berikut:
V570 Nilai yang sama diberikan dua kali ke variabel 'm23'. LinearR4.h 627
V570 Nilai yang sama diberikan dua kali ke variabel 'm13'. LinearR4.h 627
Tugas berulang dengan bentuk rekaman ini sulit dilacak dengan mata telanjang dan sebagai hasilnya, beberapa elemen dari matriks tidak menerima nilai awal. Kesalahan ini diperbaiki dalam bentuk tabel dari catatan penugasan:
m12 = m13 = m14 = m21 = m23 = m24 = m31 = m32 = m34 = m41 = m42 = m43 = 0.0;
Contoh 15:Kesalahan berikut di salah satu kondisi fungsi
btSoftBody :: addAeroForceToNode () menyebabkan bug eksplisit. Menurut komentar dalam permintaan tarikan, kekuatan diterapkan pada objek dari sisi yang salah.
struct eAeroModel { enum _ { V_Point, V_TwoSided, .... END }; }; void btSoftBody::addAeroForceToNode(....) { .... if (....) { if (btSoftBody::eAeroModel::V_TwoSided) { .... } .... } .... }
PVS-Studio juga dapat menemukan kesalahan ini dan menampilkan peringatan berikut:
V768 Konstanta enumerasi 'V_TwoSided' digunakan sebagai variabel tipe-Boolean. btSoftBody.cpp 542
Cek terkoreksi adalah sebagai berikut:
if (m_cfg.aeromodel == btSoftBody::eAeroModel::V_TwoSided) { .... }
Alih-alih kesetaraan properti objek ke salah satu enumerator, enumerator
V_TwoSided sendiri diperiksa.
Jelas bahwa saya tidak melihat semua permintaan tarik, karena saya tidak mengatur sendiri tugas semacam itu. Saya hanya ingin menunjukkan bahwa penggunaan penganalisis kode statis secara teratur dapat mendeteksi kesalahan pada tahap yang sangat awal. Ini adalah cara yang benar untuk menggunakan analisis kode statis. Analisis statis harus dibangun ke dalam proses DevOps dan menjadi filter utama bug. Semua ini dijelaskan dengan baik dalam artikel "
Masukkan analisis statis ke dalam proses, dan jangan mencari bug dengan itu ."
Kesimpulan
Dilihat oleh beberapa permintaan tarik, suatu proyek kadang-kadang diperiksa melalui berbagai alat analisis kode, namun, pengeditan dilakukan tidak secara bertahap, tetapi dalam kelompok dan pada interval waktu yang besar. Dalam beberapa permintaan, komentar menunjukkan bahwa perubahan dilakukan hanya untuk menekan peringatan. Pendekatan ini untuk penggunaan analisis secara signifikan mengurangi kegunaannya, karena ini adalah pemeriksaan konstan proyek yang memungkinkan Anda untuk memperbaiki kesalahan dengan segera, dan tidak menunggu sampai muncul bug yang jelas.
Jika Anda ingin selalu mengetahui tentang berita dan acara tim kami, berlangganan layanan sosial kami. Jaringan:
Instagram ,
Twitter ,
Vkontakte ,
Telegram .

Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan terjemahan:
PVS-Studio Melihat ke dalam Mesin Peluru Red Dead Redemption's Bullet