ओपन-सोर्स गेम मल्टी थेफ्ट ऑटो की जाँच करें

एमटीए और पीवीएस-स्टूडियो
हमने लंबे समय तक पीवीएस-स्टूडियो के साथ गेम का परीक्षण नहीं किया है। हमने इसे ठीक करने का फैसला किया और एमटीए परियोजना को चुना। मल्टी थेफ्ट ऑटो (MTA) गेम ग्रैंड थेफ्ट ऑटो के पीसी संस्करण के लिए एक संशोधन है: सैन एंड्रियास रॉकस्टार नॉर्थ से। एमटीए दुनिया भर के खिलाड़ियों को ऑनलाइन एक-दूसरे के खिलाफ खेलने की अनुमति देता है। जैसा कि विकिपीडिया में लिखा गया है, खेल की विशेषता है "कम से कम संख्या में क्रैश के साथ अनुकूलित कोड।" खैर, देखते हैं कि कोड विश्लेषक क्या कहता है।


परिचय


इस बार, मैंने उस लेख में नैदानिक ​​संदेशों को शामिल नहीं करने का फैसला किया जो पीवीएस-स्टूडियो विश्लेषक संदिग्ध लाइनों पर उत्पन्न करता है। वैसे भी, मैं कोड स्निपेट्स को स्पष्टीकरण देता हूं। यदि यह पता लगाना दिलचस्प है कि किस लाइन में त्रुटि पाई गई और विश्लेषक ने कौन सा नैदानिक ​​संदेश जारी किया, तो यह mtasa-review.txt फ़ाइल में पाया जा सकता है।

जब मैंने प्रोजेक्ट को देखा, तो मैंने कोड के टुकड़े लिखे जो mtasa-review.txt फ़ाइल के लिए संदिग्ध लग रहे थे। बाद में, इसके आधार पर, मैंने यह लेख लिखा।

यह महत्वपूर्ण है। केवल वे कोड अंश जो मैंने व्यक्तिगत रूप से फ़ाइल में नहीं थे। मैं एमटीए के विकास में भाग नहीं लेता हूं और इसमें कैसे और क्या काम करता है, इसकी कल्पना भी नहीं कर सकता। इसलिए, मुझे स्थानों पर गलत किया गया होगा: मैंने सूची में सही कोड शामिल किया और वास्तविक त्रुटियों को छोड़ दिया। लेकिन कहीं न कहीं मैं बहुत आलसी था और प्रिंटफ () फ़ंक्शन के लिए बहुत सही कॉल का वर्णन नहीं किया था। मैं एमटीए टीम से डेवलपर्स को इस पाठ पर भरोसा नहीं करने और परियोजना को स्वतंत्र रूप से सत्यापित करने के लिए कहता हूं। परियोजना काफी बड़ी है और डेमो संस्करण पूर्ण जांच के लिए पर्याप्त नहीं है। हालाँकि, हम नि: शुल्क ओपन-सोर्स परियोजनाओं का समर्थन करते हैं। हमें लिखें और हम पीवीएस-स्टूडियो के मुफ्त संस्करण के विषय पर सहमत होंगे।

तो, मल्टी थेफ्ट ऑटो गेम एक ओपन-सोर्स प्रोजेक्ट है जो C / C ++ में लिखा गया है:

सत्यापन के लिए, मैंने पीवीएस-स्टूडियो 5.05 विश्लेषक का उपयोग किया:


अब देखते हैं कि पीवीएस-स्टूडियो विश्लेषक इस खेल में क्या खोजने में सक्षम था। इतनी गलतियाँ नहीं हैं। इसके अलावा, उनमें से ज्यादातर कार्यक्रम के शायद ही कभी इस्तेमाल किए जाने वाले हिस्सों (त्रुटि संचालकों) में निहित हैं। यह आश्चर्य की बात नहीं है। अधिकांश त्रुटियों को अन्य, धीमी और अधिक महंगी विधियों द्वारा ठीक किया जाता है। स्थैतिक विश्लेषण का उचित उपयोग नियमित रूप से चलाना है। उदाहरण के लिए, पीवीएस-स्टूडियो विश्लेषक नई संशोधित और संकलित फ़ाइलों के लिए शुरू कर सकता है ( वृद्धिशील विश्लेषण मोड देखें)। इसलिए डेवलपर तुरंत कई त्रुटियों और टाइपो को ढूंढता है और उन्हें ठीक करता है। परीक्षण के दौरान इन त्रुटियों का पता लगाने की तुलना में यह कई गुना तेज और सस्ता है। इस विषय पर " लियो टॉल्स्टॉय और स्टेटिक कोड विश्लेषण " लेख में अधिक विस्तार से चर्चा की गई थी। अच्छा लेख। मैं पीवीएस-स्टूडियो जैसे उपकरणों का उपयोग करने की विचारधारा को समझने के लिए परिचयात्मक भाग को पढ़ने की सलाह देता हूं।

अजीब रंग


// c3dmarkersa.cpp SColor C3DMarkerSA::GetColor() { DEBUG_TRACE("RGBA C3DMarkerSA::GetColor()"); // From ABGR unsigned long ulABGR = this->GetInterface()->rwColour; SColor color; color.A = ( ulABGR >> 24 ) && 0xff; color.B = ( ulABGR >> 16 ) && 0xff; color.G = ( ulABGR >> 8 ) && 0xff; color.R = ulABGR && 0xff; return color; } 

संयोग से, 'और' के बजाय, '&&' का उपयोग किया जाता है। नतीजतन, सींग और पैर रंग से शून्य या एक के रूप में रहते हैं।

एक समान दुर्भाग्य "ccheckpointsa.cpp" फ़ाइल में देखा जा सकता है।

रंग प्रतिपादन के साथ एक और परेशानी।
 // cchatechopacket.h class CChatEchoPacket : public CPacket { .... inline void SetColor( unsigned char ucRed, unsigned char ucGreen, unsigned char ucBlue ) { m_ucRed = ucRed; m_ucGreen = ucGreen; m_ucRed = ucRed; }; .... } 

लाल को दो बार कॉपी किया जाता है, लेकिन नीला नहीं। सही कोड इस तरह होना चाहिए:
 { m_ucRed = ucRed; m_ucGreen = ucGreen; m_ucBlue = ucBlue; }; 

Cdebugechopacket.h फ़ाइल में एक समान समस्या मौजूद है।

सामान्य तौर पर, गेम की कई त्रुटियां दो फाइलों में दोहराई जाती हैं। यह मुझे लगता है कि फ़ाइलों में से एक क्लाइंट भाग से संबंधित है, और दूसरा सर्वर भाग से संबंधित है। कॉपी-पेस्ट :) तकनीक की पूरी शक्ति महसूस करता है।

Utf8 के साथ कुछ गलत है


 // utf8.h int utf8_wctomb (unsigned char *dest, wchar_t wc, int dest_size) { if (!dest) return 0; int count; if (wc < 0x80) count = 1; else if (wc < 0x800) count = 2; else if (wc < 0x10000) count = 3; else if (wc < 0x200000) count = 4; else if (wc < 0x4000000) count = 5; else if (wc <= 0x7fffffff) count = 6; else return RET_ILSEQ; .... } 

विंडोज पर wchar_t प्रकार का आकार 2 बाइट्स है। इसके मूल्यों की सीमा [0..65535] है। इसका मतलब है कि संख्या 0x10000, 0x200000, 0x4000000, 0x7fffffff से तुलना करने का कोई मतलब नहीं है। शायद, कोड को किसी तरह अलग तरीके से लिखा जाना चाहिए था।

भुला देने वाला विराम


 // cpackethandler.cpp void CPacketHandler::Packet_ServerDisconnected (....) { .... case ePlayerDisconnectType::BANNED_IP: strReason = _("Disconnected: You are banned.\nReason: %s"); strErrorCode = _E("CD33"); bitStream.ReadString ( strDuration ); case ePlayerDisconnectType::BANNED_ACCOUNT: strReason = _("Disconnected: Account is banned.\nReason: %s"); strErrorCode = _E("CD34"); break; .... } 

इस कोड में 'ब्रेक' स्टेटमेंट को भुला दिया गया है। परिणामस्वरूप, स्थिति "BANNED_IP" उसी तरह से "BANNED_ACCOUNT" के रूप में नियंत्रित की जाती है।

अजीब जाँच


 // cvehicleupgrades.cpp bool CVehicleUpgrades::IsUpgradeCompatible ( unsigned short usUpgrade ) { .... case 402: return ( us == 1009 || us == 1009 || us == 1010 ); .... } 

चर की तुलना 1009 नंबर के साथ दो बार की जाती है। बस नीचे, आप एक और समान डबल तुलना पा सकते हैं।

निम्नलिखित संदिग्ध तुलना:
 // cclientplayervoice.h bool IsTempoChanged(void) { return m_fSampleRate != 0.0f || m_fSampleRate != 0.0f || m_fTempo != 0.0f; } 

उसी त्रुटि को cclientsound.h फ़ाइल में कॉपी किया गया था।

एक अशक्त सूचक को संदर्भित करना


 // cgame.cpp void CGame::Packet_PlayerJoinData(CPlayerJoinDataPacket& Packet) { .... // Add the player CPlayer* pPlayer = m_pPlayerManager->Create (....); if ( pPlayer ) { .... } else { // Tell the console CLogger::LogPrintf( "CONNECT: %s failed to connect " "(Player Element Could not be created.)\n", pPlayer->GetSourceIP() ); } .... } 

यदि "प्लेयर" ऑब्जेक्ट बनाना संभव नहीं था, तो प्रोग्राम कंसोल को संबंधित जानकारी प्रिंट करने का प्रयास करता है। लेकिन सफल नहीं हुआ। "PPlayer-> GetSourceIP ()" फ़ंक्शन को कॉल करके एक अशक्त सूचक का उपयोग करना एक बुरा विचार है।

एक और अशक्त सूचक को यहां संदर्भित किया गया है:
 // clientcommands.cpp void COMMAND_MessageTarget ( const char* szCmdLine ) { if ( !(szCmdLine || szCmdLine[0]) ) return; .... } 

यदि szCmdLine पॉइंटर शून्य है, तो इसे डीरेल किया जाएगा।

सबसे अधिक संभावना है, यह इस तरह होना चाहिए:
 if ( !(szCmdLine && szCmdLine[0]) ) 

सबसे अधिक, मुझे यह कोड पसंद आया:
 // cdirect3ddata.cpp void CDirect3DData::GetTransform (....) { switch ( dwRequestedMatrix ) { case D3DTS_VIEW: memcpy (pMatrixOut, &m_mViewMatrix, sizeof(D3DMATRIX)); break; case D3DTS_PROJECTION: memcpy (pMatrixOut, &m_mProjMatrix, sizeof(D3DMATRIX)); break; case D3DTS_WORLD: memcpy (pMatrixOut, &m_mWorldMatrix, sizeof(D3DMATRIX)); break; default: // Zero out the structure for the user. memcpy (pMatrixOut, 0, sizeof ( D3DMATRIX ) ); break; } .... } 

सुंदर कॉपी-पेस्ट। अंतिम मेम्पी () फ़ंक्शन के बजाय, मेमसेट () फ़ंक्शन को बुलाया जाना चाहिए।

कच्ची सरण


अशुद्ध सरणियों से संबंधित कई त्रुटियां हैं। त्रुटियों को दो श्रेणियों में विभाजित किया जा सकता है। सबसे पहले, आइटम हटाए नहीं जाते हैं। दूसरा - सभी सरणी तत्वों में शून्य मान नहीं होते हैं।

बिना पका सामान


 // cperfstat.functiontiming.cpp std::map < SString, SFunctionTimingInfo > m_TimingMap; void CPerfStatFunctionTimingImpl::DoPulse ( void ) { .... // Do nothing if not active if ( !m_bIsActive ) { m_TimingMap.empty (); return; } .... } 

खाली () फ़ंक्शन चेक करता है कि कंटेनर में तत्व हैं या नहीं। 'M_TimingMap' कंटेनर से तत्वों को निकालने के लिए, स्पष्ट () फ़ंक्शन को बुलाया जाना चाहिए।

निम्न उदाहरण:
 // cclientcolsphere.cpp void CreateSphereFaces ( std::vector < SFace >& faceList, int iIterations ) { int numFaces = (int)( pow ( 4.0, iIterations ) * 8 ); faceList.empty (); faceList.reserve ( numFaces ); .... } 

ऐसी ही कुछ और त्रुटियाँ cresource.cpp फ़ाइल में हैं।

नोट। यदि किसी ने लेख की शुरुआत को याद किया और बीच से पढ़ने का फैसला किया: जहां यह और अन्य त्रुटियां हैं, तो आप mtasa-review.txt फ़ाइल में पता कर सकते हैं।

अब शून्य गद्दी त्रुटियों के बारे में


 // crashhandler.cpp LPCTSTR __stdcall GetFaultReason(EXCEPTION_POINTERS * pExPtrs) { .... PIMAGEHLP_SYMBOL pSym = (PIMAGEHLP_SYMBOL)&g_stSymbol ; FillMemory ( pSym , NULL , SYM_BUFF_SIZE ) ; .... } 

पहली नज़र में, सब कुछ ठीक है। यह सिर्फ FillMemory () का कोई प्रभाव नहीं होगा। फिलमोरी () और मेमसेट () एक ही चीज नहीं हैं। यहां देखें:
 #define RtlFillMemory(Destination,Length,Fill) \ memset((Destination),(Fill),(Length)) #define FillMemory RtlFillMemory 


दूसरे और तीसरे तर्क उलट हैं। इसके लिए, यह इस तरह सही होगा:

 FillMemory ( pSym , SYM_BUFF_SIZE, 0 ) ; 


Ccrashhandlerapi.cpp फ़ाइल में एक समान भ्रम मौजूद है।

और इस विषय पर कोड का अंतिम टुकड़ा। यहां केवल एक बाइट साफ की जाती है।
 // hash.hpp unsigned char m_buffer[64]; void CMD5Hasher::Finalize ( void ) { .... // Zeroize sensitive information memset ( m_buffer, 0, sizeof (*m_buffer) ); .... } 

तारांकन '*' सतही है। इसे "sizeof (m_buffer)" कहना चाहिए।

Uninitialized चर


 // ceguiwindow.cpp Vector2 Window::windowToScreen(const UVector2& vec) const { Vector2 base = d_parent ? d_parent->windowToScreen(base) + getAbsolutePosition() : getAbsolutePosition(); .... } 


चर 'आधार' का उपयोग खुद को इनिशियलाइज़ करने के लिए किया जाता है। इस तरह की एक और त्रुटि नीचे कुछ पंक्तियाँ हैं।

एक सरणी में विदेश जा रहे हैं


 // cjoystickmanager.cpp struct { bool bEnabled; long lMax; long lMin; DWORD dwType; } axis[7]; bool CJoystickManager::IsXInputDeviceAttached ( void ) { .... m_DevInfo.axis[6].bEnabled = 0; m_DevInfo.axis[7].bEnabled = 0; .... } 

अंतिम पंक्ति "m_DevInfo.axis [7] .bEnabled = 0;" शानदार है।

एक और गलती
 // cwatermanagersa.cpp class CWaterPolySAInterface { public: WORD m_wVertexIDs[3]; }; CWaterPoly* CWaterManagerSA::CreateQuad ( const CVector& vecBL, const CVector& vecBR, const CVector& vecTL, const CVector& vecTR, bool bShallow ) { .... pInterface->m_wVertexIDs [ 0 ] = pV1->GetID (); pInterface->m_wVertexIDs [ 1 ] = pV2->GetID (); pInterface->m_wVertexIDs [ 2 ] = pV3->GetID (); pInterface->m_wVertexIDs [ 3 ] = pV4->GetID (); .... } 

और एक और:
 // cmainmenu.cpp #define CORE_MTA_NEWS_ITEMS 3 CGUILabel* m_pNewsItemLabels[CORE_MTA_NEWS_ITEMS]; CGUILabel* m_pNewsItemShadowLabels[CORE_MTA_NEWS_ITEMS]; void CMainMenu::SetNewsHeadline (....) { .... for ( char i=0; i <= CORE_MTA_NEWS_ITEMS; i++ ) { m_pNewsItemLabels[ i ]->SetFont ( szFontName ); m_pNewsItemShadowLabels[ i ]->SetFont ( szFontName ); .... } .... } 

एक और, सरणी की सीमाओं को पार करने की कम से कम एक त्रुटि cpoolssa.cpp फ़ाइल में है। लेकिन मैंने लेख में इसका वर्णन नहीं किया। यह एक लंबा उदाहरण है, लेकिन मुझे नहीं पता कि इसे छोटा और स्पष्ट कैसे बनाया जाए। यह और अन्य त्रुटियां, जैसा कि मैंने कहा, पूरी रिपोर्ट में पाया जा सकता है।

छूटा हुआ शब्द 'फेंक'


 // fallistheader.cpp ListHeaderSegment* FalagardListHeader::createNewSegment(const String& name) const { if (d_segmentWidgetType.empty()) { InvalidRequestException( "FalagardListHeader::createNewSegment - " "Segment widget type has not been set!"); } return ....; } 

वहाँ एक "फेंकना होगा InvalidRequestException (....)"।

कोड का एक और टुकड़ा।
 // ceguistring.cpp bool String::grow(size_type new_size) { // check for too big if (max_size() <= new_size) std::length_error( "Resulting CEGUI::String would be too big"); .... } 

यह होना चाहिए: एसटीडी फेंको :: length_error (....)।

उफ़: नि: शुल्क (नया टी [एन])


 // cresourcechecker.cpp int CResourceChecker::ReplaceFilesInZIP(....) { .... // Load file into a buffer buf = new char[ ulLength ]; if ( fread ( buf, 1, ulLength, pFile ) != ulLength ) { free( buf ); buf = NULL; } .... } 

मेमोरी को 'नए' ऑपरेटर के उपयोग से आवंटित किया गया है। और वे इसे मुफ्त () फ़ंक्शन का उपयोग करके रिलीज़ करने का प्रयास करते हैं। परिणाम अप्रत्याशित है।

हमेशा सही / गलत परिस्थितियाँ


 // cproxydirect3ddevice9.cpp #define D3DCLEAR_ZBUFFER 0x00000002l HRESULT CProxyDirect3DDevice9::Clear(....) { if ( Flags | D3DCLEAR_ZBUFFER ) CGraphics::GetSingleton(). GetRenderItemManager()->SaveReadableDepthBuffer(); .... } 

प्रोग्रामर ध्वज चर में एक विशिष्ट बिट की जांच करना चाहता था। एक टाइपो के कारण, 'और' ऑपरेशन के बजाय, उन्होंने '|' ऑपरेशन का उपयोग किया। नतीजतन, हालत हमेशा संतुष्ट है।

एक समान भ्रम cvehiclesa.cpp फ़ाइल में मौजूद है।

निम्न चेक त्रुटि: अहस्ताक्षरित <
 // crenderitem.effectcloner.cpp unsigned long long Get ( void ); void CEffectClonerImpl::MaybeTidyUp ( void ) { .... if ( m_TidyupTimer.Get () < 0 ) return; .... } 

Get () फ़ंक्शन अहस्ताक्षरित प्रकार 'अहस्ताक्षरित लंबे समय' का मान लौटाता है। तो चेक "m_TidyupTimer.Get () <0" का कोई मतलब नहीं है। इस किस्म की अन्य त्रुटियां फाइलों में पाई जाती हैं: csettings.cpp, cmultiplayersa_1.3.cpp, cvehiclerpcs.cpp।

यह काम कर सकता है, लेकिन रिफ्लेक्टर करना बेहतर है


पीवीएस-स्टूडियो द्वारा जारी किए गए कई संदेश उन त्रुटियों का निदान करते हैं जो सबसे अधिक संभावना खुद को प्रकट नहीं करेंगे। मुझे ऐसी त्रुटियों के बारे में लिखना पसंद नहीं है। यह दिलचस्प नहीं है। मैं सिर्फ कुछ उदाहरण दूंगा।

 // cluaacldefs.cpp int CLuaACLDefs::aclListRights ( lua_State* luaVM ) { char szRightName [128]; .... strncat ( szRightName, (*iter)->GetRightName (), 128 ); .... } 

स्ट्रेंकैट () का तीसरा तर्क बफर के आकार का नहीं है, लेकिन इसमें और कितने अक्षर रखे जा सकते हैं। सैद्धांतिक रूप से, एक बफर अतिप्रवाह यहां संभव है। व्यवहार में, सबसे अधिक संभावना है कि यह कभी नहीं होगा। इस प्रकार की त्रुटि के बारे में अधिक जानकारी V645 निदान के विवरण में पाई जा सकती है।

एक और उदाहरण।
 // cscreenshot.cpp void CScreenShot::BeginSave (....) { .... HANDLE hThread = CreateThread ( NULL, 0, (LPTHREAD_START_ROUTINE)CScreenShot::ThreadProc, NULL, CREATE_SUSPENDED, NULL ); .... } 

खेल में कई जगह CreateThread () / ExitThread () फ़ंक्शन का उपयोग करते हैं। एक नियम के रूप में, यह पूरी तरह से सच नहीं है। _Beginthreadex () / _ endthreadex () फ़ंक्शन का उपयोग करें। आप इसके बारे में V513 निदान के विवरण से अधिक जान सकते हैं।

मुझे कहीं रुकना होगा


मैंने उन सभी कमियों से दूर का वर्णन किया है जिन पर मैंने ध्यान दिया। हालांकि, यह रुकने का समय है। लेख पहले से ही काफी बड़ा है। अन्य त्रुटियाँ mtasa-review.txt फ़ाइल में मिल सकती हैं

उदाहरण के लिए, निम्न प्रकार की त्रुटियाँ हैं जो लेख में नहीं हैं:


निष्कर्ष


पीवीएस-स्टूडियो विश्लेषक का उपयोग गेम प्रोजेक्ट्स और किसी भी अन्य दोनों में कई त्रुटियों के शीघ्र उन्मूलन के लिए प्रभावी रूप से किया जा सकता है। उसे एल्गोरिदम संबंधी त्रुटियां नहीं मिलेंगी (इसके लिए कृत्रिम बुद्धिमत्ता की आवश्यकता होती है)। लेकिन अनिवार्य रूप से वह समय जो बेकार में मूर्खतापूर्ण गलतियों और टाइपोस की खोज में बिताया जाता है। प्रोग्रामर साधारण दोषों पर ज्यादा सोचते हैं। यहां तक ​​कि पहले से ही डिबग किए गए और परीक्षण किए गए कोड में, ऐसी कई त्रुटियां हैं । और नया कोड लिखते समय, ऐसी त्रुटियां 10 गुना अधिक सही हो जाती हैं।

Source: https://habr.com/ru/post/In190292/


All Articles