पीवीएस-स्टूडियो: डूम 3 कोड का विश्लेषण

कयामत 3 और पीवीएस-स्टूडियो
आईडी सॉफ्टवेयर पीवीएस-स्टूडियो को लाइसेंस दिया जाता है। फिर भी, हमने डूम 3 के स्रोत कोड की जांच करने का फैसला किया, जो हाल ही में नेटवर्क पर पोस्ट किए गए थे। परिणाम - कुछ त्रुटियां पाई गईं, लेकिन फिर भी मिलीं। मुझे लगता है कि यह इस प्रकार समझाया जा सकता है।

Doom3 कोड का एक हिस्सा अब उपयोग किया जाता है और, शायद, त्रुटियां पहले से ही तय की गई हैं। कोड का हिस्सा हटा दिया जाता है और उपयोग नहीं किया जाता है। सबसे अधिक संभावना है, यह वह जगह है जहां संदिग्ध कोड टुकड़े पाए गए थे।

जो लोग इस विषय में रुचि रखते हैं, उनके लिए मैं आपको पीवीएस-स्टूडियो विश्लेषक द्वारा बताए गए कोड स्निपेट्स प्रदान करता हूं। हमेशा की तरह, मैं आपको याद दिलाता हूं कि मैं केवल कुछ चेतावनियों पर विचार कर रहा हूं। परियोजना के अन्य वर्गों को कार्यक्रम की संरचना के ज्ञान की आवश्यकता होती है, और मैंने उनका अध्ययन नहीं किया।



Doom 3 के लिए स्रोत कोड GitHub पर और कंपनी के आधिकारिक FTP पर GPL v3 लाइसेंस के तहत प्रकाशित किया गया है । विश्लेषण के लिए, मैंने पीवीएस-स्टूडियो 4.39 टूल का उपयोग किया। कार्यक्रम के लिए दरारें, कृपया मत देखो। मैंने पीवीएस-स्टूडियो के लिए पहले से ही सैन्य टुकड़ियों को चाबी और दरार के रूप में प्रच्छन्न देखा है। हमें बेहतर लिखें और हम थोड़ी देर के लिए एक परीक्षण कुंजी देंगे।

विखंडन 1. संदेहास्पद स्थिति।


  #define BIT (संख्या) (1 << (संख्या))
 const int BUTTON_ATTACK = BIT (0);
 void idTarget_WaitForButton :: सोचो (शून्य) {
   ...
   अगर (खिलाड़ी &&
       (खिलाड़ी-> पुरानेबटन और बटन_टैक) &&
       (खिलाड़ी-> usercmd.buttons और BUTTON_ATTACK)) {
   ...
 } 

पीवीएस-स्टूडियो डायग्नोस्टिक संदेश: V564 'और' ऑपरेटर को बूल प्रकार के मूल्य पर लागू किया जाता है। आप शायद कोष्ठकों को शामिल करना भूल गए हैं या '&&' ऑपरेटर का उपयोग करना चाहते हैं। गेम का लक्ष्य

टुकड़े पर ध्यान दें "! खिलाड़ी-> पुरानेबटन और बटन_टैक"। यहां वे जांचना चाहते थे कि कम से कम महत्वपूर्ण बिट 0. है, लेकिन ऑपरेटर की प्राथमिकता 'है!' '&' ऑपरेटर से अधिक है। इसका मतलब है कि स्थिति इस प्रकार काम करती है:
  (खिलाड़ी-> ओल्डबटन) और १ 

यह पता चला है कि स्थिति केवल तभी सही है जब सभी बिट्स शून्य हों। कोड का सही संस्करण:
  अगर (खिलाड़ी &&
     ((खिलाड़ी-> पुरानेबटन और बटन_टैक)) और&
     (खिलाड़ी-> usercmd.buttons और BUTTON_ATTACK)) { 

विखंडन 2. संदिग्ध चक्र।


  शून्य idSurface_Polytope :: FromPlanes (...)
 {
   ...
   के लिए (j = 0; j <w.GetNumPoints (); j ++) {
     के लिए (k = 0; k <verts.Num (); j ++) {
       अगर (verts [k] .xyz.Compare (w [j] .ToVec3))
                                 POLYTOPE_VERTEX_EPSILON)) {
         तोड़;
       }
     }
     ...
   }
   ...
 } 

पीवीएस-स्टूडियो डायग्नोस्टिक संदेश: V533 यह संभावना है कि 'के लिए' ऑपरेटर के अंदर एक गलत चर बढ़ाया जा रहा है। 'जे' की समीक्षा करने पर विचार करें। idLib सतह_पॉलिटोप ..cpp 65

एक नेस्टेड लूप वेरिएबल 'j' को बढ़ाता है, 'k' को नहीं। चर 'k' बिल्कुल नहीं बढ़ता है। इस तरह के चक्र के परिणाम अप्रत्याशित हैं। कोड का सही संस्करण:
  के लिए (k = 0; k <verts.Num (); k ++) { 

विखंडन 3. एक और संदिग्ध चक्र।


  बूल idMatX :: IsOrthonormal (कॉन्स्टैट फ्लोट एप्सिलॉन) const {
   ...
   for (int i = 0; मैं <numRows; i ++) {
     ...
     for (i = 1; मैं <numRows; i ++) {
       ...
     }
     अगर (idMath :: Fabs (sum)> एप्सिलॉन) {
       झूठे लौटना;
     }
   }
   सच लौटना;
 } 

पीवीएस-स्टूडियो डायग्नोस्टिक संदेश: V535 'i' का उपयोग इस लूप के लिए और बाहरी लूप के लिए किया जा रहा है। idLib मैट्रिक्स.सीपी 3128

एक नेस्टेड लूप को बाहरी लूप के समान चर का उपयोग करके आयोजित किया जाता है। दोनों छोरों में एक स्टॉप कंडीशन है: i <numRows। यह पता चला है कि बाहरी लूप हमेशा केवल एक पुनरावृत्ति करता है। कोड को ठीक करने के लिए, आप आंतरिक लूप में एक और चर का उपयोग कर सकते हैं।

विखंडन 4. अपरिभाषित व्यवहार।


  int idFileSystemLocal :: ListOSFiles (...)
 {
   ...
   dir_cache_index = (++ dir_cache_index)% MAX_CACHED_DIRS;
   ...
 } 

पीवीएस-स्टूडियो डायग्नोस्टिक संदेश: V567 अपरिभाषित व्यवहार। अनुक्रम बिंदुओं के बीच दो बार उपयोग किए जाने के दौरान 'dir_cache_index' चर को संशोधित किया जाता है। TypeInfo filesystem.cpp 1877

चर "dir_cache_index" अनुक्रम में एक ही बिंदु पर दो बार बदला जाता है । तथ्य यह है कि एक उपसर्ग वृद्धि का उपयोग किया जाता है, कोई फर्क नहीं पड़ता। संकलक को सैद्धांतिक रूप से निम्नलिखित फॉर्म का कोड बनाने का अधिकार है:
  ए = dir_cache_index;
 ए = ए + 1;
 बी = एक% MAX_CACHED_DIRS;
 dir_cache_index = B;
 dir_cache_index = A; 

बेशक, सबसे अधिक संभावना है, अभिव्यक्ति की सही गणना की जाती है। लेकिन इस बात पर यकीन नहीं किया जा सकता है। परिणाम कंपाइलर, अनुकूलन सेटिंग्स के प्रकार और संस्करण से प्रभावित हो सकता है। कोड का सही संस्करण:
  dir_cache_index = (dir_cache_index + 1)% MAX_CACHED_DIRS; 

विखंडन 5. संदिग्ध सरणी सफाई।


  शून्य idMegaTexture :: GenerateMegaMipMaps () {
   ...
   बाइट * newBlock = (बाइट *) _ alloca (टाइलसाइज़);
   ...
   मेमसेट (newBlock, 0, sizeof (newBlock));
   ...
 } 

पीवीएस-स्टूडियो डायग्नोस्टिक संदेश: V579 मेमसेट फ़ंक्शन सूचक और उसके आकार को तर्क के रूप में प्राप्त करता है। यह संभवतः एक गलती है। तीसरे तर्क का निरीक्षण करें। DoomDLL megatexture.cpp 542

'NewBlock' ऐरे का केवल हिस्सा शून्य से भरा है। जाहिर है, यह गलत है। ऐसा लगता है कि यह पहले इस तरह से लिखा गया था:
  बाइट न्यूब्लॉक [CONST_ARRAY_SIZE];
 ...
 मेमसेट (newBlock, 0, sizeof (newBlock)); 

फिर आवश्यकताओं में बदलाव आया और 'न्यूब्लॉक' सरणी का आकार बदलने लगा। लेकिन वे इसे साफ करने के कार्य के बारे में भूल गए। कोड का सही संस्करण:
  मेमसेट (न्यूब्लॉक, 0, टाइलसाइज़); 

टुकड़ा 6. एक और संदिग्ध सरणी सफाई।


  शून्य Sys_GetCurrentMemoryStatus (sysMemoryStats_t और आँकड़े) {
   ...
   मेमसेट (& Statex, sizeof (Statex), 0);
   ...
 } 

पीवीएस-स्टूडियो डायग्नोस्टिक संदेश: V575 'मेमसेट' फ़ंक्शन '0' तत्वों को प्रोसेस करता है। तीसरे तर्क का निरीक्षण करें। DoomDLL win_sared.cpp 177

यहां, 'मेमसेट' फ़ंक्शन को कॉल करते समय, तर्क भ्रमित होते हैं। फ़ंक्शन 0 बाइट्स को साफ़ करता है। यह संयोगवश बहुत ही सामान्य गलती है। मैं उनसे कई बार अलग-अलग प्रोजेक्ट में मिला।

सही फ़ंक्शन कॉल:
  मेमसेट (& Statex, 0, sizeof (Statex)); 

टुकड़ा 7. नमस्कार, कॉपी-पेस्ट।


  शून्य idAASFileLocal :: DeleteClusters (शून्य) {
   ...
   memset (& portal, 0, sizeof (portal));
   portals.Append (पोर्टल);

   मेमसेट (और क्लस्टर, 0, आकार (पोर्टल));
   clusters.Append (क्लस्टर);
 } 

पीवीएस-स्टूडियो डायग्नोस्टिक संदेश: V512 'मेमसेट' फ़ंक्शन के कॉल से बफर 'और क्लस्टर' का बहिर्वाह होगा। DoomDLL aasfile.cpp 1312

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

मैंने अन्य "अविकसित" सरणियों को कोड में देखा, लेकिन उनके बारे में लिखना दिलचस्प नहीं है।

विखंडन 8. एक सूचक के साथ संदिग्ध कार्य।


  void idBrushBSP :: FloodThroughPortals_r (idBrushBSPNode * नोड, ...)
 {
   ...
   अगर (नोड-> कब्जा) {
     सामान्य-> त्रुटि ("FloodThroughPortals_r: नोड पहले से ही कब्जे में \ n");
   }
   अगर (नोड) {
     सामान्य-> त्रुटि ("FloodThroughPortals_r: NULL नोड \ n");
   }
   ...
 } 

पीवीएस-स्टूडियो डायग्नोस्टिक संदेश: V595 'नोड' पॉइंटर को nullptr के खिलाफ सत्यापित होने से पहले उपयोग किया गया था। चेक लाइनें: 1421, 1424। डूमलएल ब्रशलस्पॉट। सीपीसी 1421

सबसे पहले, 'नोड' पॉइंटर का नाम बदल दिया जाता है: नोड-> अधिकृत। और फिर, अचानक यह जाँच की जाती है कि क्या यह NULL है। यह एक बहुत ही संदिग्ध कोड है। मुझे नहीं पता कि इसे सही कैसे बनाया जाए, क्योंकि मैं फ़ंक्शन के तर्क को नहीं जानता। शायद यह लिखें:
  अगर (नोड और नोड -> व्याप्त) { 

विखंडन 9. संदिग्ध स्ट्रिंग प्रारूप।


  स्ट्रक्चर गेमविज़न_
   gameVersion_s (शून्य)
   {
     स्प्रिंटफ (स्ट्रिंग, "% s।% d% s% s% s", "
             Engine_VERSION, BUILD_NUMBER, BUILD_DEBUG,
             BUILD_STRING, __DATE__, __TIME__);
   }
   चार स्ट्रिंग [256];
 } gameVersion; 

पीवीएस-स्टूडियो डायग्नोस्टिक संदेश: V576 गलत प्रारूप। 'स्प्रिंटफ' फ़ंक्शन को कॉल करने के दौरान वास्तविक तर्कों की एक अलग संख्या की उम्मीद की जाती है। अपेक्षित: 7. वर्तमान: 8. खेल syscvar.cpp 54

यह संदिग्ध है कि तर्क '__TIME__' का उपयोग बिल्कुल नहीं किया जाता है।

विखंडन 10. वह कोड जो भ्रामक हो।


बार-बार पाया गया कोड, जो, जैसा कि मैं समझता हूं, सही ढंग से काम करता है, लेकिन यह अजीब लगता है। मैं ऐसे कोड का केवल एक उदाहरण दूंगा।
  स्टेटिक बूल R_ClipLineToLight (..., const idPlane frum [4], ...)
 {
   ...
   for (j = 0; j <6; j ++) {
     डी 1 = फ्रुम [जे] ।डिस्टेंस (पी 1);
     डी 2 = फ्रुम [जे] ।डिस्टेंस (पी 2);
     ...
   }
   ...
 } 

संकेत के लिए, प्रोग्रामर ने लिखा है कि सरणी 'फ़्रुम' में 4 तत्व होते हैं। लेकिन 6 तत्वों पर कार्रवाई की जाती है। यदि आप 'R_ClipLineToLight' पर कॉल को देखते हैं, तो 6 तत्वों की एक सरणी है। यही है, सब कुछ सही ढंग से काम करना चाहिए, लेकिन कोड आपको लगता है।

अन्य त्रुटियों और कमियों को पीवीएस-स्टूडियो विश्लेषक शुरू करके देखा जा सकता है। वैसे, इस अवसर को लेते हुए, मैं जॉन कार्मैक को अपने संबंध से अवगत कराना चाहता हूं और उन्हें बताना चाहता हूं कि हम जल्द ही एक दोष को ठीक कर देंगे जो आईडी सॉफ्टवेयर में पीवीएस-स्टूडियो के पूर्ण उपयोग की अनुमति नहीं देता है।

यह कमी विश्लेषक की कम गति है। कंपनी के साथ काम करने वाले स्रोत कोड की बड़ी मात्रा को देखते हुए, यह एक महत्वपूर्ण सीमा है। पीवीएस-स्टूडियो संस्करण 4.50 में, जो इस वर्ष जारी किया जाएगा, विज़ुअल सी ++ प्रीप्रोसेसर के बजाय क्लैंग को प्रीप्रोसेसर के रूप में उपयोग करना संभव होगा। इससे परियोजनाओं के सत्यापन में काफी तेजी आएगी। उदाहरण के लिए, Visual C ++ से पूर्वप्रक्रमक का उपयोग करते समय, कयामत 3 स्रोत कोड 26 मिनट में जांचे जाते हैं। और क्लैंग प्रीप्रोसेसर का उपयोग करते समय - 16 मिनट में। उदाहरण, हालांकि, बहुत सफल नहीं था। अधिकांश परियोजनाओं पर, विश्लेषण की गति में लाभ अधिक मजबूत होगा।

सही है, डिफ़ॉल्ट रूप से, जबकि आपको विजुअल C ++ से प्रीप्रोसेसर का उपयोग करना होगा। Clang में अभी भी विंडोज प्लेटफॉर्म के संबंध में कुछ असंगतताएं और खामियां हैं। इसलिए केवल 80% परियोजनाएं सफलतापूर्वक सत्यापित की जाती हैं।

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


All Articles