Saat ini tidak perlu menerapkan fisika objek dari awal untuk pengembangan game karena ada banyak perpustakaan untuk tujuan ini. Bullet aktif digunakan di banyak game AAA, proyek realitas virtual, berbagai simulasi dan pembelajaran mesin. Dan masih digunakan, misalnya, 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 simulasi fisika skala besar.
Perpustakaan ini
didistribusikan secara bebas , sehingga semua orang dapat menggunakannya dalam proyek mereka sendiri jika mereka mau. Selain Red Dead Redemption, mesin fisika ini juga digunakan dalam industri film untuk menciptakan efek khusus. Misalnya, digunakan dalam penembakan "Sherlock Holmes" karya Guy Ritchie untuk menghitung tabrakan.
Jika ini adalah pertama kalinya Anda bertemu artikel di mana PVS-Studio memeriksa proyek, saya akan membuat sedikit penyimpangan.
PVS-Studio adalah penganalisa kode statis yang membantu Anda menemukan kesalahan, cacat, dan kerentanan potensial dalam kode sumber program C, C ++, C #, Java. Analisis statis adalah semacam proses peninjauan kode otomatis.
Lakukan 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 pada nilai Pi (3.141592653 ...). Digit ke 7 di bagian pecahan hilang - itu harus sama dengan 6.
Mungkin, kesalahan dalam fraksi kesepuluh juta setelah titik desimal tidak akan menghasilkan konsekuensi yang signifikan, tetapi Anda tetap harus menggunakan konstanta perpustakaan yang sudah ada yang tidak memiliki kesalahan ketik. Ada konstanta
M_PI untuk nomor Pi dari header
math.h.Salin tempel
Contoh 2:Kadang-kadang analisa memungkinkan Anda untuk menemukan kesalahan secara tidak langsung. Sebagai contoh, tiga argumen terkait halfExtentsX, halfExtentsY, halfExtentsZ dilewatkan ke fungsi di sini tetapi yang terakhir tidak digunakan di mana pun dalam fungsi. Anda mungkin memperhatikan bahwa variabel halfExtentsY digunakan dua kali saat memanggil metode
addVertex . Jadi mungkin itu adalah kesalahan copypaste dan argumen yang dilupakan harus digunakan di sini.
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 telah mendeteksi fragmen menarik berikut dan saya akan menunjukkannya terlebih dahulu dalam bentuk awal.
Lihat baris looooooooooong ini?
Sangat aneh bahwa programmer memutuskan untuk menulis kondisi yang panjang menjadi satu baris. Tetapi tidak mengherankan bahwa kesalahan kemungkinan besar menyelinap ke dalamnya.
Penganalisa menghasilkan 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 menuliskan semuanya dalam bentuk "tabular" yang jelas, kita dapat melihat bahwa semua 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 dengan jenis yang sama:
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, satu dan elemen array yang sama diperiksa dua kali. Kemungkinan besar, kondisinya pasti terlihat seperti ini:
cs.m_fJacCoeffInv [0] == 0 && cs.m_fJacCoeffInv [1] == 0 . Ini adalah contoh klasik dari kesalahan salin-tempel.
Contoh 5:Ditemukan juga bahwa ada cacat seperti itu:
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 1 jika berhasil diselesaikan dan -1 jika gagal. Kemungkinan besar, yang
lain jika cabang seharusnya bereaksi terhadap nilai negatif
serviceResult , tetapi kondisi pemeriksaan digandakan. Mungkin itu juga kesalahan salin-tempel.
Ada peringatan yang sama dari penganalisa, tetapi tidak ada gunanya melihatnya lebih dekat dalam artikel ini.
V517 Penggunaan
pola 'jika (A) {...} else jika (A) {...}' terdeteksi. Ada kemungkinan kehadiran kesalahan logis. Periksa baris: 151, 190. PhysicsClientUDP.cpp 151
Di atas: melebihi batas array
Contoh 6:Salah satu kesalahan yang tidak menyenangkan untuk dicari adalah overrun array. Kesalahan ini sering terjadi karena pengindeksan kompleks dalam satu lingkaran.
Di sini, dalam kondisi loop, batas atas variabel
dofIndex adalah 128 dan
dof ' adalah 4 inklusif. Tetapi
m_desiredState juga hanya berisi 128 item. Akibatnya, indeks
[dofIndex + dof] dapat menyebabkan overrun 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 tetapi sekarang ini disebabkan oleh jumlah bukan saat pengindeksan array tetapi dalam kondisi. Jika file memiliki nama dengan panjang maksimal, terminal nol akan ditulis di luar array (
Kesalahan Satu Per Satu ). Tentu saja, variabel
len akan sama dengan
MAX_FILENAME_LENGTH hanya dalam kasus luar biasa, tetapi tidak menghilangkan kesalahan tetapi membuatnya jarang.
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 ketika Anda perlu menggunakan hasil beberapa fungsi beberapa kali atau menggunakan variabel yang harus melewati seluruh rantai panggilan untuk mendapatkan akses, Anda harus menggunakan variabel sementara untuk optimasi dan pembacaan kode yang lebih baik. Penganalisa telah menemukan lebih dari 100 tempat dalam kode di mana Anda dapat melakukan koreksi tersebut.
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(....); } }
Rantai panggilan yang sama digunakan berulang kali di sini dan 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 ke dalamnya alih-alih memanggil fungsi empat kali untuk setiap argumen.
Bocor
Contoh 10:Banyak kesalahan dari jenis berikut terdeteksi di 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); }
Memori belum dirilis dari pointer
importir di sini. Ini dapat menyebabkan kebocoran memori. Dan untuk mesin fisik mungkin tren yang buruk. Untuk menghindari kebocoran, cukup menambahkan add
importir setelah variabel menjadi tidak perlu. Tapi, tentu saja, lebih baik menggunakan pointer pintar.
C ++ hidup dengan kode sendiri
Contoh 11:Kesalahan berikutnya muncul dalam kode karena aturan C ++ tidak selalu bertepatan dengan aturan matematika atau "akal sehat". Apakah Anda memperhatikan di mana fragmen kode kecil ini berisi kesalahan?
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
Akan terlihat bahwa kondisi memeriksa bahwa
f0 sama dengan
f1 dan sama dengan jumlah item di
m_fractureBodies . Tampaknya perbandingan ini harus memeriksa apakah
f0 dan
f1 terletak di akhir array
m_fractureBodies , karena mengandung posisi objek yang ditemukan oleh metode
findLinearSearch () . Tetapi pada kenyataannya, ungkapan ini berubah menjadi cek untuk melihat apakah
f0 dan
f1 sama dengan
m_fractureBodies.size () dan kemudian cek untuk melihat apakah
m_fractureBodies.size () sama dengan hasil
f0 == f1 . Akibatnya, operan ketiga di sini dibandingkan dengan 0 atau 1.
Kesalahan indah! Dan, untungnya, sangat jarang. Sejauh ini, kami hanya
bertemu di dua proyek sumber terbuka, dan menarik bahwa semuanya adalah mesin game.
Contoh 12:Saat bekerja dengan string, seringkali lebih baik menggunakan fitur yang disediakan oleh kelas
string . Jadi, untuk dua kasus berikutnya 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 kita bisa berhenti di sini. Seperti yang Anda lihat, analisis kode statis dapat mendeteksi berbagai kesalahan.
Sangat menarik untuk membaca tentang pemeriksaan proyek satu kali, tetapi itu bukan cara yang tepat untuk menggunakan penganalisa kode statis. Dan kita akan membicarakannya di bawah ini.
Kesalahan ditemukan sebelum kita
Sangat menarik untuk mencoba menemukan bug atau cacat yang sudah diperbaiki tetapi yang dapat
dianalisis oleh analis statis berdasarkan artikel terbaru "
Kesalahan yang tidak ditemukan oleh analisis kode statis karena tidak digunakan ".
Tidak ada 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) { .... } }
Komentar untuk permintaan mengatakan bahwa Anda perlu memeriksa array untuk fakta bahwa itu tidak kosong, tetapi sebaliknya dilakukan pemeriksaan pointer yang tidak berarti, yang selalu mengembalikan true. Inilah peringatan PVS-Studio tentang cek asli memberi tahu Anda:
V600 Pertimbangkan untuk memeriksa kondisinya. Pointer 'info.m_deviceExtensions' selalu tidak sama dengan NULL. b3OpenCLUtils.cpp 551
Contoh 14:Bisakah Anda mencari tahu apa masalahnya dengan fungsi selanjutnya?
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
Penugasan berulang dalam bentuk rekaman ini sulit dilacak dengan mata telanjang dan, sebagai hasilnya, beberapa elemen matriks tidak mendapatkan nilai awal. Kesalahan ini diperbaiki oleh bentuk tabel dari rekaman tugas:
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 yang jelas. 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 bisa menemukan kesalahan ini dan menghasilkan peringatan berikut:
V768 Konstanta enumerasi 'V_TwoSided' digunakan sebagai variabel tipe-Boolean. btSoftBody.cpp 542
Cek tetap terlihat seperti ini:
if (m_cfg.aeromodel == btSoftBody::eAeroModel::V_TwoSided) { .... }
Alih-alih kesetaraan properti objek ke salah satu enumerator, enumerator
V_TwoSided sendiri diperiksa.
Sudah jelas bahwa saya tidak melihat semua permintaan tarik, karena bukan itu intinya. Saya hanya ingin menunjukkan kepada Anda bahwa penggunaan penganalisa kode statis secara teratur dapat mendeteksi kesalahan pada tahap paling awal. Ini adalah cara yang tepat untuk menggunakan analisis kode statis. Analisis statis harus dibangun ke dalam proses DevOps dan menjadi filter bug utama. Semua ini dijelaskan dengan baik di artikel "
Perkenalkan Analisis Statis dalam Proses, Jangan Hanya Mencari Bug dengan Itu ."
Kesimpulan
Dilihat oleh beberapa permintaan tarik, suatu proyek kadang-kadang diperiksa melalui berbagai alat analisis kode tetapi koreksi dilakukan tidak secara bertahap tetapi dalam kelompok dan dengan interval besar. Dalam beberapa permintaan, komentar menunjukkan bahwa perubahan itu dilakukan hanya untuk menekan peringatan. Pendekatan untuk menggunakan analisis ini secara signifikan mengurangi kegunaannya karena ini adalah pemeriksaan rutin proyek yang memungkinkan Anda untuk memperbaiki kesalahan segera daripada menunggu bug eksplisit muncul.
Ikuti kami dan berlangganan akun dan saluran media sosial kami:
Instagram ,
Twitter ,
Facebook ,
Telegram . Kami akan senang berada bersama Anda di mana pun Anda berada dan membuat Anda tetap di jalur.