أمازون لومبيارد: صرخة الروح


الألعاب هي واحدة من منتجات البرمجيات الأكثر شعبية. هذه صناعة ضخمة فيها محرك لعبة جديد - Amazon Lumberyard. لا يزال المشروع في حالة تجريبية ولديه وقت لإصلاح الأخطاء وتحسين جودة التعليمات البرمجية. لدى مطوري المحرك الكثير من العمل للقيام به في المستقبل القريب لعدم إخفاق الملايين من اللاعبين ومطوري الألعاب.

مقدمة


Amazon Lumberyard هو محرك ألعاب متعدد المنصات AAA مجاني تم تطويره بواسطة Amazon ويستند إلى بنية محرك CryEngine ، التي تم ترخيصها من قبل Crytek في عام 2015. بالمناسبة ، لقد تم تحليل CryEngine مرتين بالفعل في أغسطس 2016 وأبريل 2017 . في الوقت نفسه ، يجب أن أشير إلى أنه بعد عام ، أصبح الرمز أسوأ فقط. وفي اليوم الآخر ، قررت أن أرى ما فعله أمازون بناءً على محرك اللعبة هذا. لقد عملوا بشكل جيد للغاية على البيئة. وثائق المطورين والبرمجيات لنشر بيئة عمل تتم بشكل رائع للغاية وعلى مستوى عالٍ. ولكن المشكلة مرة أخرى مع التعليمات البرمجية! آمل أن تمتلك أمازون المزيد من الموارد للعمل مع المشروع ، ولا يزالون يهتمون بجودة الشفرة. مع هذه المراجعة ، آمل أن أسترعي انتباه المطورين إلى جودة الشفرة والدفع من أجل نهج جديد في تطوير محرك اللعبة هذا. كانت الحالة الحالية للشفرة في حالة مؤسفة لدرجة أنني قمت بتغيير عنوان المقالة عدة مرات وأعدت رسم صورة العنوان أثناء النظر في التقرير مع نتائج التحليل. كانت النسخة الأولى من الصورة أقل عاطفية:



قمنا بتحليل مصادر Amazon Lumberyard من أحدث إصدار متاح 1.14.0.1. كود المصدر مأخوذ من المستودع في جيثب . يجب أن تكون Star Citizen واحدة من الألعاب الأولى على محرك Lumberyard. اللاعبين المحتملين الذين ينتظرونها ، أدعوك أيضًا لإلقاء نظرة على ما هو حاليًا "تحت غطاء" اللعبة.

التكامل مع PVS-Studio


تم استخدام PVS-Studio كمحلل ثابت للكود. وهي متاحة لأنظمة Windows و Linux و macOS. على سبيل المثال لتحليل المشروع عبر الأنظمة الأساسية ، هناك حتى شيء للاختيار من بينها للعمل أكثر راحة. بالإضافة إلى C و C ++ ، يتم دعم تحليل المشاريع بلغة C #. خطط جافا . تتم كتابة الغالبية العظمى من التعليمات البرمجية في العالم باللغات المذكورة (ليس بدون أخطاء ، بالطبع) ، لذا جرب محلل PVS-Studio في مشروعك ، ستتعلم الكثير من الأشياء المثيرة للاهتمام ؛-).

كنظام تجميع Lumberyard ، يتم استخدام WAF ، والذي كان أيضًا في CryEngine. ليس لدى المحلل طريقة خاصة للتكامل مع نظام التجميع هذا. قررت العمل مع مشروع على Windows واخترت هذه الطريقة لبدء التحليل: نظام مراقبة تجميعي . يتم إنشاء ملف مشروع Visual Studio تلقائيًا. يمكن استخدامه لبناء المشروع وعرض تقرير المحلل.

تبدو قائمة أوامر التحليل كما يلي:

cd /path/to/lumberyard/dev lmbr_waf.bat ... CLMonitor.exe monitor MSBuild.exe ... LumberyardSDK_vs15.sln ... CLMonitor.exe analyze --log /path/to/report.plog 

التقرير ، كما قلت ، يمكن عرضه في Visual Studio.

حول إيجور وكوالكوم


يضع Amazon Lumberyard نفسه كمحرك ألعاب متعدد المنصات. إن الترويج لمشروع للجماهير بمثل هذه الميزة أمر سهل ، ولكن الحفاظ عليه صعب للغاية. تم إصدار أحد التحذيرات من PVS-Studio على جزء التعليمات البرمجية حيث حارب المبرمج Igor مع مترجم Qualcomm. ربما حل مشكلته ، لكنه ترك رمزًا مريبًا للغاية. قررت رسمها بصورة.

V523 تعادل العبارة "then" العبارة "else". 700 م


هنا يتم تنفيذ نفس الرمز ، بغض النظر عن الحالة المحسوبة. على خلفية التعليقات المتبقية ، يبدو هذا القرار مريبًا.

بشكل عام ، المشروع ليس المكان الوحيد الذي يجب فيه تبسيط الشروط من أجل الوضوح ، أو لتصحيح خطأ حقيقي. إليك قائمة بهذه الأماكن:

  • V523 تعادل العبارة "then" العبارة "else". 1385
  • V523 تعادل العبارة "then" العبارة "else". ج 4201
  • V523 تعادل العبارة "then" العبارة "else". 905
  • V523 تعادل العبارة "then" العبارة "else". 701
  • V523 تعادل العبارة "then" العبارة "else". الحلقة 562
  • V523 تعادل العبارة "then" العبارة "else". الجسيمات 130
  • V523 تعادل العبارة "then" العبارة "else". 1223
  • V523 تعادل العبارة "then" العبارة "else". 447

Python ++




وجد المحلل مثل هذا الرمز المضحك:

تم العثور على مقارنة مريبة V709 CWE-682: 'a == b == c'. تذكر أن 'a == b == c' لا تساوي 'a == b && b == c'. 564

 void CallBinaryOp(....) { .... uint32_t src1SwizCount = GetNumSwizzleElements(....); uint32_t src0SwizCount = GetNumSwizzleElements(....); uint32_t dstSwizCount = GetNumSwizzleElements(....); .... if (src1SwizCount == src0SwizCount == dstSwizCount) // <= { .... } .... } 

في C ++ ، للأسف ، يتم تجميع مثل هذا الرمز بنجاح ، لكنه لا يتم تنفيذه على الإطلاق كما قد يبدو. يتم تقييم التعبيرات في C ++ بترتيب الأولوية ، وتنفيذ الطبقات الضمنية ، إذا لزم الأمر.

مثل هذا الاختيار سيكون صحيحا ، على سبيل المثال ، في لغة بايثون. ولكن في هذه الحالة ، أطلق المطور "نفسه على قدمه".

3 لقطات تحكم إضافية:

  • تم العثور على مقارنة مريبة V709 CWE-682: 'a == b == c'. تذكر أن 'a == b == c' لا تساوي 'a == b && b == c'. 654
  • تم العثور على مقارنة مريبة V709 CWE-682: 'a == b == c'. تذكر أن 'a == b == c' لا تساوي 'a == b && b == c'. 469
  • تم العثور على مقارنة مريبة V709 CWE-682: 'a == b == c'. تذكر أن 'a == b == c' لا تساوي 'a == b && b == c'. 539

أول واحد من أفضل التشخيصات




سيكون حول تحذير V501 - أول تشخيص للأغراض العامة في PVS-Studio. يمكن أن تكون الأخطاء التي اكتشفها هذا التشخيص وحده كافية لكتابة مقال. ويوضح مشروع أمازون لومبيريارد ذلك جيدًا.

لسوء الحظ ، من الممل أن ننظر إلى أخطاء من نفس النوع لفترة طويلة ، لذلك في هذا القسم سأعلق فقط على جزأين من التعليمات البرمجية ، وسيتم سرد بقية التحذيرات.

V501 توجد تعبيرات فرعية متطابقة إلى اليسار وإلى يمين "||" عامل التشغيل: hotX <0 || hotx <0 editorutils.cpp 166

 QCursor CMFCUtils::LoadCursor(....) { .... if (!pm.isNull() && (hotX < 0 || hotX < 0)) { QFile f(path); f.open(QFile::ReadOnly); QDataStream stream(&f); stream.setByteOrder(QDataStream::LittleEndian); f.read(10); quint16 x; stream >> x; hotX = x; stream >> x; hotY = x; } .... } 

الشرط يفتقر إلى المتغير الساخن Y. خطأ إملائي كلاسيكي.

V501 هناك تعبيرات فرعية متطابقة 'sp.m_pTexture == m_pTexture' إلى اليسار وإلى يمين عامل التشغيل "&&". 487

V501 توجد "sp.m_eCGTextureType == m_eCGTextureType" متطابقة إلى اليسار وإلى يمين عامل التشغيل "&&". 487

 bool operator != (const SCGTexture& sp) const { if (sp.m_RegisterOffset == m_RegisterOffset && sp.m_Name == m_Name && sp.m_pTexture == m_pTexture && // <= 1 sp.m_RegisterCount == m_RegisterCount && sp.m_eCGTextureType == m_eCGTextureType && // <= 2 sp.m_BindingSlot == m_BindingSlot && sp.m_Flags == m_Flags && sp.m_pAnimInfo == m_pAnimInfo && sp.m_pTexture == m_pTexture && // <= 1 sp.m_eCGTextureType == m_eCGTextureType && // <= 2 sp.m_bSRGBLookup == m_bSRGBLookup && sp.m_bGlobal == m_bGlobal) { return false; } return true; } 

تم العثور على اثنين من معاجين النسخ في هذا الجزء دفعة واحدة. من أجل الوضوح ، رسمت الأسهم.

V501 توجد تعبيرات فرعية متطابقة إلى يسار ويمين عامل التشغيل '==': pSrc.GetLen () == pSrc.GetLen () fbxpropertytypes.h 978

 inline bool FbxTypeCopy(FbxBlob& pDst, const FbxString& pSrc) { bool lCastable = pSrc.GetLen() == pSrc.GetLen(); FBX_ASSERT( lCastable ); if( lCastable ) pDst.Assign(pSrc.Buffer(), (int)pSrc.GetLen()); return lCastable; } 

هنا أريد أن أقول مرحبا للمطورين من AUTODESK . هذا الخطأ من مكتبة FBX SDK الخاصة بهم. المتغيرات المربكة pSrc و pDst . أعتقد ، إلى جانب Lumberyard ، هناك أيضًا الكثير من المستخدمين الآخرين الذين تعتمد مشاريعهم على رمز مع هذا الخطأ.

V501 توجد تعبيرات فرعية متطابقة إلى اليسار وإلى يمين عامل التشغيل "&&": pTS-> pRT_ALD_1 && pTS-> pRT_ALD_1 d3d_svo.cpp 857

 void CSvoRenderer::ConeTracePass(SSvoTargetsSet* pTS) { .... if (pTS->pRT_ALD_1 && pTS->pRT_ALD_1) { static int nPrevWidth = 0; if (....) { .... } else { pTS->pRT_ALD_1->Apply(10, m_nTexStateLinear); pTS->pRT_RGB_1->Apply(11, m_nTexStateLinear); } } .... } 

العودة إلى رمز Lumberyard. في هذه الحالة ، يتم تحديد نفس المؤشر pTS-> pRT_ALD_1 . كان يجب أن يكون أحدهم pTS-> pRT_RGB_1 . ربما حتى بعد التفسير ، ليس من الممكن على الفور رؤية الفرق ، ولكن هناك فرق: الفرق في السلاسل الفرعية القصيرة ALD و RGB . إذا تم إخبارك بأن مراجعة الشفرة اليدوية كافية ، فقم بعرض هذا المثال.

وإذا كان هذا المثال غير كافٍ ، فهناك 5 أمثلة أخرى متشابهة.
  • V501 توجد تعبيرات فرعية متطابقة إلى اليسار وإلى يمين "||" عامل التشغيل:! pTS-> pRT_ALD_0 ||! pTS-> pRT_ALD_0 d3d_svo.cpp 1041
  • V501 توجد تعبيرات فرعية متطابقة إلى اليسار وإلى يمين عامل التشغيل "&&": m_pRT_AIR_MIN && m_pRT_AIR_MIN d3d_svo.cpp 1808
  • V501 توجد تعبيرات فرعية متطابقة إلى اليسار وإلى يمين عامل التشغيل "&&": m_pRT_AIR_MAX && m_pRT_AIR_MAX d3d_svo.cpp 1819
  • V501 توجد تعبيرات فرعية متطابقة إلى اليسار وإلى يمين عامل التشغيل "&&": m_pRT_AIR_SHAD && m_pRT_AIR_SHAD d3d_svo.cpp 1830
  • V501 توجد تعبيرات فرعية متطابقة إلى يمين ويسار عامل التشغيل "&&": s_pPropertiesPanel && s_pPropertiesPanel الكيانobject.cpp 1700

وكما وعدت ، إليك قائمة بتحذيرات V501 المتبقية بدون أمثلة التعليمات البرمجية:
توسيع القائمة
  • V501 توجد تعبيرات فرعية متطابقة "MaxX <0" إلى اليسار وإلى يمين "||" عامل. 128 ج
  • V501 هناك m_joints تعبيرات فرعية متطابقة [op [1]]. حدود [1] [i] 'إلى اليسار وإلى يمين عامل التشغيل "-". مفصلية 795
  • V501 توجد "m_joints [i] .imits [1] [j]" متطابقة للتعبيرات الفرعية إلى اليسار وإلى يمين عامل التشغيل "-". مفصلية. cpp 2044
  • V501 توجد عبارات متطابقة "irect [0] .x + 1 - irect [1] .x >> 31" إلى اليسار وإلى يمين "|" عامل. 4029
  • V501 هناك تعبيرات فرعية متطابقة 'b-> mlen <= 0' إلى اليسار وإلى يمين '||' عامل. 1779
  • V501 هناك تعبيرات فرعية متطابقة 'b-> mlen <= 0' إلى اليسار وإلى يمين '||' عامل. بسترليب ج 1827
  • V501 هناك تعبيرات فرعية متطابقة 'b-> mlen <= 0' إلى اليسار وإلى يمين '||' عامل. بسترليب ج 1865
  • V501 هناك تعبيرات فرعية متطابقة 'b-> mlen <= 0' إلى اليسار وإلى يمين '||' عامل. 1779
  • V501 هناك تعبيرات فرعية متطابقة 'b-> mlen <= 0' إلى اليسار وإلى يمين '||' عامل. بسترليب ج 1827
  • V501 هناك تعبيرات فرعية متطابقة 'b-> mlen <= 0' إلى اليسار وإلى يمين '||' عامل. بسترليب ج 1865
  • V501 توجد تعبيرات فرعية متطابقة إلى اليسار وإلى يمين عامل التشغيل "-": dd - dd finalizingspline.h 669
  • V501 هناك pVerts [2] - pVerts [3] متطابقة إلى اليسار وإلى يمين عامل التشغيل "^". علي عبدالله علي الجابري 307
  • V501 توجد تعبيرات فرعية متطابقة "! PGroup-> GetStatObj ()" إلى اليسار وإلى يمين "||" عامل. 594
  • V501 توجد تعبيرات فرعية متطابقة إلى اليسار وإلى يمين "||" عامل التشغيل: val == 0 || val == - 0 xmlcpb_attrwriter.cpp 367
  • V501 توجد تعبيرات فرعية متطابقة "geom_colltype_solid" إلى اليسار وإلى يمين "|" عامل. الحلقة 1058
  • V501 توجد تعبيرات فرعية متطابقة "(TriMiddle - RMWPosition)" إلى اليسار وإلى اليمين من "|" عامل. العارضة 174
  • V501 توجد عبارات فرعية متطابقة "(الهدف - pAbsPose [b3] .t)" إلى اليسار وإلى يمين "|" عامل. 115
  • V501 توجد تعبيرات فرعية متطابقة "(الهدف - pAbsPose [b4] .t)" إلى اليسار وإلى يمين "|" عامل. الحلقة 242
  • V501 هناك تعبيرات فرعية متطابقة '(m_eTFSrc == eTF_BC6UH)' إلى اليسار وإلى يمين '||' عامل. 983
  • V501 توجد تعبيرات فرعية متطابقة إلى اليسار وإلى يمين عامل التشغيل "-": q2.vz - q2.vz azentitynode.cpp 102
  • V501 توجد تعبيرات فرعية متطابقة إلى اليسار وإلى يمين عامل التشغيل "-": q2.vz - q2.vz الكيان node.cpp 107
  • V501 يوجد تعبيرات فرعية متطابقة 'm_listRect.contains (event-> pos ())' إلى يسار ويمين '||' عامل. 463
  • V501 توجد تعبيرات فرعية متطابقة إلى اليسار وإلى يمين عامل التشغيل "&&": pObj-> GetParent () && pObj-> GetParent () designerpanel.cpp 253

حول موضع الكاميرا في الألعاب




هناك ثاني أسرع التشخيصات في PVS-Studio - V502 . هذا التشخيص أقدم من بعض لغات البرمجة الجديدة ، حيث لم يعد من الممكن ارتكاب مثل هذا الخطأ. وبالنسبة لـ C ++ ، سيكون هذا الخطأ ذا صلة ، ربما دائمًا.

لنبدأ بمثال بسيط بسيط.

V502 ربما يكون عامل التشغيل '؟:' يعمل بطريقة مختلفة عما كان متوقعًا. عامل التشغيل '؟:' له أولوية أقل من عامل التشغيل '+'. 217

 bool ZipEncryptor::ParseKey(....) { .... size_t pos = i * 2 + (v1 == 0xff) ? 1 : 2; RCLogError("....", pos); return false; .... } 

عملية الإضافة لها أولوية أعلى من عامل التشغيل الثلاثي. لذلك ، فإن هذا التعبير له منطق حساب مختلف تمامًا عما افترضه المؤلف.

يمكنك إصلاح الخطأ بهذه الطريقة:

 size_t pos = i * 2 + (v1 == 0xff ? 1 : 2); 

V502 ربما يكون عامل التشغيل '؟:' يعمل بطريقة مختلفة عما كان متوقعًا. عامل التشغيل '؟:' له أولوية أقل من عامل التشغيل '-'. 3dengine.cpp 1898

 float C3DEngine::GetDistanceToSectorWithWater() { .... return (bCameraInTerrainBounds && (m_pTerrain && m_pTerrain->GetDistanceToSectorWithWater() > 0.1f)) ? m_pTerrain->GetDistanceToSectorWithWater() : max(camPostion.z - OceanToggle::IsActive() ? OceanRequest::GetOceanLevel() : GetWaterLevel(), 0.1f); } 

وإليك رمز مثال حيث يعملون مع موضع الكاميرا. مثال يصعب إدراكه بالعين وهناك خطأ فيه. للنشر ، تم تغيير تنسيق الكود ، ولكن في الملف المصدر هذا الكود أكثر قابلية للقراءة.

يوجد خطأ في هذه السلسلة الفرعية:

 camPostion.z - OceanToggle::IsActive() ? .... : .... 

إذا بدأت الكاميرا في لعبتك فجأة في التصرف بشكل غير لائق ، فعليك أن تعلم أن المطورين قد حفظوا في تحليل الكود الثابت: D.

أمثلة أخرى مع تحذيرات مماثلة:

  • V502 ربما يكون عامل التشغيل '؟:' يعمل بطريقة مختلفة عما كان متوقعًا. عامل التشغيل '؟:' له أولوية أقل من عامل التشغيل '-'. 5203
  • V502 ربما يكون عامل التشغيل '؟:' يعمل بطريقة مختلفة عما كان متوقعًا. عامل التشغيل '؟:' له أولوية أقل من عامل التشغيل '+'. qcolumnwidget.cpp 136
  • V502 ربما يكون عامل التشغيل '؟:' يعمل بطريقة مختلفة عما كان متوقعًا. عامل التشغيل '؟:' له أولوية أقل من عامل التشغيل "&&". shapetool.h 98

تراث CryEngine


يعتمد Amazon Lumberyard على كود CryEngine . وليس في أفضل إصدار لها. لقد خلصت إلى هذا الاستنتاج بالنظر إلى تقرير المحلل. تم إصلاح بعض الأخطاء التي تم العثور عليها بالفعل في أحدث إصدار من CryEngine لمراجعي الكود ، ولكن لا تزال موجودة في كود Lumberyard. أيضًا ، خلال العام الماضي ، تم تحسين المحلل بشكل كبير وكان من الممكن العثور على أخطاء إضافية موجودة الآن في محركي ألعاب. ولكن الوضع مع Lumberyard أسوأ. ورثت أمازون بشكل أساسي جميع الديون التقنية لشركة CryEngine. لكن واجبه التقني ، بالطبع ، يظهر في كل شركة بمفرده :).

في هذا القسم ، سأقدم لك بعض الأخطاء التي تم إصلاحها في أحدث إصدار من CryEngine ، وستبقى الآن مشكلة فقط في مشروع Lumberyard.

V519 يتم تعيين المتغير "BlendFactor [2]" مرتين على التوالي. ربما هذا خطأ. خطوط التحقق: 1283 ، 1284. ccrydxgldevicecontext.cpp 1284



سيشهد مطورو Lumberyard تقريبًا مثل هذه العواطف عندما يكتشفون أن هذا الخطأ بقي معهم فقط.

بالمناسبة ، هناك نوعان آخران:

  • V519 يتم تعيين قيم المتغير "m_auBlendFactor [2]" مرتين على التوالي. ربما هذا خطأ. خطوط التحقق: 919 ، 920. ccrydxgldevicecontext.cpp 920
  • V519 يتم تعيين قيم المتغير "m_auBlendFactor [2]" مرتين على التوالي. ربما هذا خطأ. خطوط التحقق: 926 ، 927. ccrydxgldevicecontext.cpp 927

يوجد مثل هذا الخطأ:

V546 عضو في فئة تمت تهيئته بنفسه: "eConfigMax (eConfigMax.VeryHigh)". 1837

 ParticleParams() : .... fSphericalApproximation(1.f), fVolumeThickness(1.0f), fSoundFXParam(1.f), eConfigMax(eConfigMax.VeryHigh), // <= fFadeAtViewCosAngle(0.f) {} 

في CryEngine ، تمت إعادة كتابة هذه الفئة بشكل عام ، ولكن بقي خطأ التهيئة هنا.

V521 مثل هذه التعبيرات باستخدام عامل التشغيل "،" خطيرة. تأكد من صحة التعبير "! SWords [iWord] .empty ()، iWord ++". تكتيك نقاط نظام 339

 bool CTacticalPointSystem::Parse(....) const { string sInput(sSpec); const int MAXWORDS = 8; string sWords[MAXWORDS]; int iC = 0, iWord = 0; for (; iWord < MAXWORDS; !sWords[iWord].empty(), iWord++) { sWords[iWord] = sInput.Tokenize("_", iC); } .... } 

الدورة المشبوهة التي أعاد CryEngine كتابتها أيضًا.

الأخطاء تعيش أطول مما تعتقد


المستخدمون الذين بدأوا في استخدام PVS-Studio للمرة الأولى لديهم موقف متطابق تقريبًا: وجدوا خطأ ، واكتشفوا أنه تمت إضافته قبل بضعة أشهر ويسعدهم أن يدركوا أنهم تجنبوا بأعجوبة ظهور المشكلة بين مستخدميهم. جاء العديد من عملائنا إلى الاستخدام المنتظم لـ PVS-Studio على وجه التحديد بعد هذه القصص.

في بعض الأحيان ، من أجل بدء عمليات مراقبة جودة التعليمات البرمجية ، يجب على الشركة زيارة مثل هذه المواقف عدة مرات. هنا مثال عن CryEngine و Lumberyard:

يمكن تجاوز صفيف V557 CWE-119. يشير مؤشر "id" إلى تجاوز الصفيف. غيموبوجيكت سيستم 113

 uint32 CGameObjectSystem::GetExtensionSerializationPriority(....) { if (id > m_extensionInfo.size()) { return 0xffffffff; // minimum possible priority } else { return m_extensionInfo[id].serializationPriority; } } 

كما تعلم ، لا يعتمد Amazon Lumberyard على أحدث إصدار من CryEngine. ومع ذلك ، بمساعدة محلل PVS-Studio ، كان من الممكن العثور على خطأ موجود الآن في محركي ألعاب. كان من الضروري التحقق من الفهرس باستخدام عامل التشغيل '> =' ...

خطأ الفهرسة خطير. علاوة على ذلك ، هناك ستة أماكن من هذا القبيل ! هنا مثال آخر:

يمكن تجاوز صفيف V557 CWE-119. يشير مؤشر "الفهرس" إلى تجاوز الصفيف. 73

 CVehicleSeat* CVehicleSeatGroup::GetSeatByIndex(unsigned index) { if (index >= 0 && index <= m_seats.size()) { return m_seats[index]; } return NULL; } 

قام شخص ما بأخطاء من نفس النوع ، ولم يتم إصلاحها لمجرد أنها لم يتم تضمينها مرة واحدة في مراجعات أخطاء CryEngine.

التحذيرات المتبقية:

  • يمكن تجاوز صفيف V557 CWE-119. يشير مؤشر "id" إلى تجاوز الصفيف. Gameobjectsystem.cpp 195
  • يمكن تجاوز صفيف V557 CWE-119. يشير مؤشر "id" إلى تجاوز الصفيف. 290
  • يمكن تجاوز صفيف V557 CWE-119. يشير مؤشر "stateId" إلى تجاوز حدود الصفيف. 311
  • يمكن تجاوز صفيف V557 CWE-119. يشير مؤشر "stateId" إلى تجاوز حدود الصفيف. 354

لا يمكن تفسير الوجود الطويل للأخطاء في الشفرة إلا من خلال المستوى المقابل لاختبار المشروع. يعتقد أن التحليل الثابت يجد أخطاء فقط في التعليمات البرمجية غير المستخدمة. لذا ، الأمر ليس كذلك. ينسى المطورون أن معظم المستخدمين يعانون بصمت من أخطاء غير واضحة في البرامج ، وغالبًا ما يؤثر ظهور هذه الأخطاء النادرة جدًا سلبًا على عمل الشركة بأكملها وسمعتها ومبيعاتها ، إن وجدت.

درجات مختلفة من برمجة النسخ واللصق




عندما وصلت إلى هذا القسم من المقالة ، ربما لاحظت أن برمجة النسخ واللصق هي سبب العديد من المشاكل. في PVS-Studio ، يتم تنفيذ البحث عن مثل هذه الأخطاء في التشخيصات المختلفة. سيعطي هذا القسم أمثلة على النسخ واللصق الموجودة في V561 .

في ما يلي مثال لرمز مريب عند تعريف المتغيرات التي تحمل الاسم نفسه في النطاقات المتداخلة.

V561 CWE-563 ربما يكون من الأفضل تعيين قيمة لمتغير 'pLibrary' بدلاً من إعلانها من جديد. الإعلان السابق: كيانobject.cpp ، السطر 4703. كيانobject.cpp 4706

 void CEntityObject::OnMenuConvertToPrefab() { .... IDataBaseLibrary* pLibrary = GetIEditor()->Get....; if (pLibrary == NULL) { IDataBaseLibrary* pLibrary = GetIEditor()->Get....; } if (pLibrary == NULL) { QString sError = tr(....); CryMessageBox(....); return; } .... } 

لا يقوم مؤشر pLibrary بالكتابة كما هو متوقع. تم نسخ تهيئة هذا المؤشر بالكامل وفقًا للشرط جنبًا إلى جنب مع تعريف النوع.

سأذكر جميع الأماكن المماثلة:

  • V561 CWE-563 ربما يكون من الأفضل تعيين قيمة لمتغير "eType" بدلاً من إعلانها من جديد. الإعلان السابق: toglsloperand.c ، السطر 838. toglsloperand.c 1224
  • V561 CWE-563 ربما يكون من الأفضل تعيين قيمة لمتغير "eType" بدلاً من إعلانها من جديد. الإعلان السابق: toglsloperand.c ، السطر 838. toglsloperand.c 1305
  • V561 CWE-563 ربما يكون من الأفضل تعيين قيمة لمتغير "rSkelPose" بدلاً من إعلانها من جديد. الإعلان السابق: attachmentmanager.cpp ، السطر 409. attachmentmanager.cpp 458
  • V561 CWE-563 ربما يكون من الأفضل تعيين قيمة لمتغير "nThreadID" بدلاً من تعريفها من جديد. الإعلان السابق: d3dmeshbaker.cpp ، السطر 797. d3dmeshbaker.cpp 867
  • V561 CWE-563 ربما يكون من الأفضل تعيين قيمة للمتغير 'directoryNameList' بدلاً من إعلانها من جديد. الإعلان السابق: assetimportermanager.cpp ، السطر 720. assetimportermanager.cpp 728
  • V561 CWE-563 ربما يكون من الأفضل تعيين قيمة لمتغير "pNode" بدلاً من إعلانها من جديد. الإعلان السابق: breakpointsctrl.cpp ، السطر 340. breakpointsctrl.cpp 349
  • V561 CWE-563 ربما يكون من الأفضل تعيين قيمة لمتغير 'pLibrary' بدلاً من إعلانها من جديد. الإعلان السابق: prefabobject.cpp ، السطر 1443. prefabobject.cpp 1446
  • V561 CWE-563 ربما يكون من الأفضل تعيين قيمة لمتغير 'pLibrary' بدلاً من إعلانها من جديد. الإعلان السابق: prefabobject.cpp ، السطر 1470. prefabobject.cpp 1473
  • V561 CWE-563 ربما يكون من الأفضل تعيين قيمة للمتغير 'cmdLine' بدلاً من إعلانها من جديد. الإعلان السابق: fileutil.cpp ، السطر 110. fileutil.cpp 130
  • V561 CWE-563 من الأفضل تعيين قيمة للمتغير 'sfunctionArgs' بدلاً من إعلانها من جديد. الإعلان السابق: attribitemlogiccallbacks.cpp ، السطر 291. attribitemlogiccallbacks.cpp 303
  • V561 CWE-563 من الأفضل تعيين قيمة لمتغير 'curveName' بدلاً من إعلانها من جديد. الإعلان السابق: qgradientselectorwidget.cpp ، السطر 475. qgradientselectorwidget.cpp 488

قائمة طويلة ... بعض الأماكن المذكورة هي نسخ كاملة من المثال الموصوف.

تهيئة Eigenvalue




في كود محرك اللعبة ، تم العثور على الكثير من الأماكن حيث يتم تعيين المتغير لنفسه. في مكان ما تم ترك هذا الرمز للتصحيح ، في مكان ما تم تصميمه ببساطة بشكل جميل (وهو أيضًا مصدر أخطاء غالبًا) ، لذلك سأقدم لك قطعة واحدة من التعليمات البرمجية الأكثر شبهة بالنسبة لي.

V570 يتم تعيين متغير "السلوك Params.ignoreOnVehicleDestroyed " إلى نفسه. مركبة كومبونينت. cpp 168

 bool CVehicleComponent::Init(....) { .... if (!damageBehaviorTable.getAttr(....) { behaviorParams.ignoreOnVehicleDestroyed = false; } else { behaviorParams.ignoreOnVehicleDestroyed = // <= behaviorParams.ignoreOnVehicleDestroyed; // <= } .... } 

في العرض الحالي ، لا توجد حاجة لفرع آخر على الإطلاق. ولكن ربما تحتوي هذه الشفرة على خطأ: فقد أرادوا تعيين القيمة المعاكسة للمتغير:

 bValue = !bValue 

ولكن من الأفضل للمطورين أن يتعرفوا على نتائج هذا التشخيص.

معالجة المشكلة




سيعطي هذا القسم العديد من الأمثلة عندما حدث خطأ أثناء معالجة الخطأ.

مثال 1

V606 الرمز المميز للمالك 'nullptr'. 599

 RootSignature* RootSignatureCache::AcquireRootSignature(....) { .... RootSignature* result = new RootSignature(m_pDevice); if (!result->Init(params)) { DX12_ERROR("Could not create root signature!"); nullptr; } m_RootSignatureMap[hash] = result; return result; } } 

نسيت أن أكتب عودة nullptr. . الآن ، سيتم استخدام القيمة غير الصالحة لمتغير النتيجة في مكان آخر في الكود.

تم نسخ نفس الرمز بالضبط إلى مكان آخر:

  • V606 الرمز المميز للمالك 'nullptr'. 621

مثال 2

V606 الرمز المميز للمالك 'false'. 191

 bool FillSpaceTool::FillHoleBasedOnSelectedElements() { .... if (validEdgeList.size() == 2) { .... } if (validEdgeList.empty()) { .... for (int i = 0, iVertexSize(....); i < iVertexSize; ++i) { validEdgeList.push_back(....); } } if (validEdgeList.empty()) // <= { false; // <= fail } std::vector<BrushEdge3D> linkedEdgeList; std::set<int> usedEdgeSet; linkedEdgeList.push_back(validEdgeList[0]); // <= fail .... } 

مثال مثير جدا للاهتمام لخطأ ببيان إرجاع مفقود. أصبح من الممكن الآن الوصول إلى حاوية فارغة إلى الفهرس.

مثال 3

V564 CWE-480 يتم تطبيق عامل التشغيل "&" على قيمة نوع المنطقية. ربما نسيت تضمين الأقواس أو قصدت استخدام عامل التشغيل "&&". 2914

 void SetDataTypes(....) { .... // Check assumption that both the values which MOVC might pick // have the same basic data type. if(!psContext->flags & HLSLCC_FLAG_AVOID_TEMP_REGISTER_ALIASING) { ASSERT(GetOperandDataType(psContext, &psInst->asOperands[2]) == GetOperandDataType(psContext, &psInst->asOperands[3])); } .... } 

تم التحقق بشكل غير صحيح من وجود البتات في العلم. ينطبق عامل النفي على قيمة العلم ، وليس التعبير بالكامل. اكتب بشكل صحيح مثل هذا:

 if(!(psContext->flags & ....)) 

تحذيرات مماثلة أكثر:

  • V564 CWE-480 "|" يتم تطبيق عامل التشغيل على قيمة نوع منطقية. ربما نسيت تضمين الأقواس أو قصدت استخدام '||' عامل. d3dhwshader.cpp 1832
  • V564 CWE-480 يتم تطبيق عامل التشغيل "&" على قيمة نوع المنطقية. ربما نسيت تضمين الأقواس أو قصدت استخدام عامل التشغيل "&&". 2112
  • V564 CWE-480 "|" يتم تطبيق عامل التشغيل على قيمة نوع منطقية. ربما نسيت تضمين الأقواس أو قصدت استخدام '||' عامل. 1039

مثال 4

V596 CWE-390 تم إنشاء الكائن ولكن لم يتم استخدامه. قد تكون كلمة "رمي" مفقودة: رمي runtime_error (FOO)؛ سبحان الله محمد صليب 1491

 static std::vector<std::string> PyGetPrefabLibrarys() { CPrefabManager* pPrefabManager = GetIEditor()->GetPrefabMa....; if (!pPrefabManager) { std::runtime_error("Invalid Prefab Manager."); } .... } 

خطأ في رمي الاستثناء. كان من الضروري أن تكتب هكذا:

 throw std::runtime_error("Invalid Prefab Manager."); 

القائمة الكاملة لهذه الأخطاء:

  • V596 CWE-390 تم إنشاء الكائن ولكن لم يتم استخدامه. قد تكون كلمة "رمي" مفقودة: رمي runtime_error (FOO)؛ 1515.عوض
  • V596 CWE-390 تم إنشاء الكائن ولكن لم يتم استخدامه. قد تكون كلمة "رمي" مفقودة: رمي runtime_error (FOO)؛ 1521
  • V596 CWE-390 تم إنشاء الكائن ولكن لم يتم استخدامه. قد تكون كلمة "رمي" مفقودة: رمي runtime_error (FOO)؛ 1543
  • V596 CWE-390 تم إنشاء الكائن ولكن لم يتم استخدامه. قد تكون كلمة "رمي" مفقودة: رمي runtime_error (FOO)؛ 1549
  • V596 CWE-390 تم إنشاء الكائن ولكن لم يتم استخدامه. قد تكون كلمة "رمي" مفقودة: رمي runtime_error (FOO)؛ 1603 محمد علي محمد الزعبي
  • V596 CWE-390 تم إنشاء الكائن ولكن لم يتم استخدامه. قد تكون كلمة "رمي" مفقودة: رمي runtime_error (FOO)؛ 1619
  • V596 CWE-390 تم إنشاء الكائن ولكن لم يتم استخدامه. قد تكون كلمة "رمي" مفقودة: رمي runtime_error (FOO)؛ 1644


زوجان من المشاكل عند العمل مع الذاكرة




V549 CWE-688 الوسيطة الأولى للدالة "memcmp" تساوي الوسيطة الثانية. 894.عوض

 struct VertexLess { .... bool operator()(int a, int b) const { .... if (m.m_links[a].links.size() != m.m_links[b].links.size()) { res = (m.m_links[a].links.size() < m.m_links[b].links.size()) ? -1 : +1; } else { res = memcmp(&m.m_links[a].links[0], &m.m_links[a].links[0], sizeof(m.m_links[a].links[0]) * m.m_links[a].links.size()); } .... } .... }; 

يقارن الشرط أحجام متجهين. إذا كانت متساوية ، في الفرع الآخر تتم مقارنة قيم العناصر الأولى للمتجهات باستخدام دالة memcmp () . لكن الحجج الأولى والثانية لهذه الوظيفة هي نفسها! الوصول إلى عناصر المصفوفة مرهق إلى حد ما. هناك مؤشرات أ و ب . على الأرجح ، يوجد خطأ مطبعي فيها.

V611 CWE-762 تم تخصيص الذاكرة باستخدام عامل التشغيل "T الجديد" ولكن تم تحريرها باستخدام عامل التشغيل "حذف". خذ بعين الاعتبار فحص هذا الرمز. ربما من الأفضل استخدام "حذف [] البيانات ؛". vectorn.h 102

 ~vectorn_tpl() { if (!(flags & mtx_foreign_data)) { delete[] data; } } vectorn_tpl& operator=(const vectorn_tpl<ftype>& src) { if (src.len != len && !(flags & mtx_foreign_data)) { delete data; // <= data = new ftype[src.len]; } .... } 

يتم تحرير ذاكرة مؤشر البيانات باستخدام عبارة غير صالحة. يجب استخدام عامل الحذف [] في كل مكان .

رمز لا يمكن الوصول إليه


تم الكشف عن V779 CWE-561 رمز لا يمكن الوصول إليه. من الممكن أن يكون هناك خطأ. 67. فبكسسكينيمبورتير. cpp 67

 Events::ProcessingResult FbxSkinImporter::ImportSkin(....) { .... if (BuildSceneMeshFromFbxMesh(....) { context.m_createdData.push_back(std::move(createdData)); return Events::ProcessingResult::Success; // <= } else { return Events::ProcessingResult::Failure; // <= } context.m_createdData.push_back(); // <= fail return Events::ProcessingResult::Success; } 

تنتهي كافة فروع البيان الشرطي بالخروج من الوظيفة. ومع ذلك ، لم يتم تنفيذ بعض التعليمات البرمجية.

تم الكشف عن V779 CWE-561 رمز لا يمكن الوصول إليه. من الممكن أن يكون هناك خطأ. 153

 bool DockableLibraryTreeView::Init(IDataBaseLibrary* lib) { .... if (m_treeView && m_titleBar && m_defaultView) { if (m_treeView->topLevelItemCount() > 0) { ShowTreeView(); } else { ShowDefaultView(); } return true; // <= } else { return false; // <= } emit SignalFocused(this); // <= fail } 

من السهل اكتشاف خطأ في هذا الجزء من الرمز. ولكن إذا قمت بكتابة الرمز لفترة طويلة ، فإن الانتباه ينخفض ​​بشكل حاد وتندرج مثل هذه الأخطاء بسهولة في المشروع.

V622 CWE-478 خذ بعين الاعتبار فحص بيان "التبديل". من المحتمل أن يكون عامل التشغيل "الحالة" الأول مفقودًا. مسعد محمد علي الجابري 872

 AZ_INLINE bool IsDataGreaterEqual(....) { switch (type.GetType()) { AZ_Error("ScriptCanvas", false, "...."); return false; case Data::eType::Number: return IsDataGreaterEqual<Data::NumberType>(lhs, rhs); .... case Data::eType::AABB: AZ_Error("ScriptCanvas", false, "....", Data::Traits<Data::AABBType>::GetName()); return false; case Data::eType::OBB: AZ_Error("ScriptCanvas", false, "....", Data::Traits<Data::OBBType>::GetName()); return false; .... } 

إذا كان المفتاح يحتوي على كود خارج بيان الحالة / الافتراضي ، فلن يتم تنفيذه على الإطلاق.

الخلاصة


تتضمن المقالة 95 تحذيرًا للمحلل ، 25 منها مع أمثلة برمجية. ما مقدار المواد من تقرير المحلل بأكمله؟ قمت بتمرير ثلث التنبيهات عالية المستوى بسرعة . هناك أيضًا متوسط ​​ومنخفض ، مجموعة من التشخيصات للبحث عن التحسينات ، وفرص أخرى غير مستغلة للمحلل - هذه هي مئات الأخطاء الواضحة والآلاف من التحذيرات غير المستكشفة.

ثم يحتاج القارئ إلى أن يسأل نفسه السؤال: "هل من الممكن مع هذا النهج للمشروع إطلاق محرك لعبة جيد؟". لا يوجد تحكم في جودة الكود. تم أخذ كود CryEngine مع الأخطاء القديمة كأساس ، تمت إضافة أخطاء جديدة. يتم الانتهاء من CryEngine نفسه فقط بعد المراجعة التالية للكود. لدى Amazon كل فرصة بمواردها للعمل في اتجاه جودة الشفرة وإطلاق أروع محرك لعبة!

لا تنزعج جدا. بين عملاء PVS-Studio هناك أكثر من ثلاثين شركة أخرى تشارك في الألعاب. يمكنك التعرف عليهم ومع منتجاتهم على صفحة موقعنا " عملاء " عن طريق اختيار مرشح "تطوير اللعبة". لذلك نقوم بتحسين العالم تدريجياً. ربما يمكننا تحسين Amazon Lumberyard :).

كتب أحد الزملاء مؤخرًا مقالًا حول موضوع جودة برامج الألعاب ، أقترح أن تكون مهتمًا بقراءة: " التحليل الثابت في صناعة ألعاب الفيديو: أهم 10 أخطاء في البرامج ."

رابط لتحميل محلل PVS-Studio ، كيف يمكنك الاستغناء عنه ؛-)



إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فيرجى استخدام رابط الترجمة: Svyatoslav Razmyslov. أمازون لومبيارد: صرخة معاناة

هل قرأت المقال ولديك سؤال؟

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


All Articles