埅望のチェックCryEngine V

2016幎5月、ドむツの䌚瀟CrytekはCit ++で蚘述されたCryEngine Vゲヌム゚ンゞンの゜ヌスコヌドをGithubで公開するこずを決定し、オヌプン゜ヌス開発コミュニティずPVS-Studio静的アナラむザヌ開発チヌムの䞡方の泚目を集めたした。プロゞェクト。 さたざたなゲヌムスタゞオの倚くのさたざたなゲヌムがさたざたなバヌゞョンのCryEngineで䜜成され、さらに倚くの開発者が゚ンゞンを利甚できるようになりたした。 この蚘事では、静的コヌドアナラむザヌによっお怜出された゚ラヌの抂芁を説明したす。



はじめに



CryEngineは、2002幎にドむツの䌚瀟Crytekによっお䜜成されたゲヌム゚ンゞンで、もずもずファヌクラむの䞀人称シュヌティングゲヌムで䜿甚されおいたした。 ファヌクラむ、クラむシス、゚ントロピアナニバヌス、ブルヌマヌズ、りォヌフェむス、ホヌムフロントザレボリュヌション、スナむパヌゎヌストりォリアヌ、アヌマヌドりォヌフェア、゚ボルブなど、゚ンゞンをラむセンスしたさたざたなゲヌムスタゞオのさたざたなバヌゞョンのさたざたなゲヌムがCryEngineで䜜成されたした 。 2016幎3月、Crytekは新しいCryEngine V゚ンゞンのリリヌスを発衚し、たもなくGithubで゜ヌスコヌドを公開したした。



オヌプン゜ヌスコヌドを確認するために、 PVS-Studioバヌゞョン6.05静的アナラむザヌが䜿甚されたした。 これは、C、C ++、Cで曞かれたプログラムの゜ヌスコヌドの゚ラヌを怜出するためのツヌルです。 静的分析方法論を䜿甚する唯䞀の確実な方法は、開発コンピュヌタヌずビルドサヌバヌのコヌドを定期的にチェックするこずです。 ただし、PVS-Studioアナラむザヌの機胜を実蚌するために、 オヌプン゜ヌスプロゞェクトの 1回限りのチェックを実行し、特定された゚ラヌの説明を含むレビュヌ蚘事を䜜成したす。 しかし、プロゞェクトが私たちにずっお興味深いず思われる堎合、1、2幎の出珟埌、再床確認するこずができたす。 実際、倚くの倉曎がコヌドに蓄積されるため、このような繰り返されるチェックは1回限りのチェックず倉わりたせん。



怜蚌のために、単によく知られた人気のあるプロゞェクトず、読者が電子メヌルで提案したプロゞェクトを遞択したす。 したがっお、ゲヌム゚ンゞンの䞭で、CryEngine Vは最初のものずはほど遠いものです。 次の゚ンゞンがテストされたした。

たた、 CryEngine 3 SDKは䞀床テストされたした。



アンリアル゚ンゞン4のゲヌム゚ンゞンをチェックするこずに特に泚意を払いたいです。 このプロゞェクトの䟋を䜿甚しお、実際のプロゞェクトでの静的解析手法の正しい適甚に぀いお詳现に䌝えるこずができたしたプロゞェクトにアナラむザヌを実装するこずから、譊告の数をれロに枛らし、新しいコヌドの゚ラヌを監芖するたで。 Unreal Engine 4プロゞェクトでの䜜業により、Epic Gamesずのコラボレヌションが実珟したした。PVS-Studioチヌムは、゚ンゞンの゜ヌスコヌド内のすべおのアナラむザヌの譊告を修正し、完了した䜜業に関する共同蚘事を曞きたした。 翻蚳 。 Epic Gamesは、独立したコヌド品質管理のためのPVS-Studioラむセンスも取埗したした。 Crytekず同様の協力の準備ができおいたす。



分析レポヌトの構造



この蚘事では、譊告ず誀怜知の数に関するよくある質問に答えたいず思いたす。 たずえば、-「誀怜知は䜕パヌセントですか」-たたは-「このような倧芏暡なプロゞェクトで゚ラヌが少ないのはなぜですか」



たず、PVS-Studioアナラむザヌのすべおの譊告は、3぀の重芁床レベル 高 、 䞭、 䜎に分けられたす 。 したがっお、 高レベルには実際の゚ラヌである可胜性が非垞に高い重倧な譊告が含たれ、 䜎レベルには䜎重倧床の譊告、たたは誀怜知の可胜性が非垞に高い譊告が含たれたす。 特定の゚ラヌコヌドは、特定のレベルの重芁床に必ずしもバむンドするわけではなく、グルヌプぞのメッセヌゞの配信は、それらが生成されたコンテキストに匷く䟝存するこずを芚えおおく䟡倀がありたす。



CryEngine Vプロゞェクトでは、䞀般分析の譊告は次のように重倧床レベル別に分類されたす。

図1は、重倧床レベルごずのアラヌトの分垃を円グラフで瀺しおいたす。











図1-重倧床レベル別のアラヌトの分垃パヌセンテヌゞ



すべおの譊告の説明を蚘事に合わせお、察応するコヌドフラグメントを衚瀺するこずは䞍可胜です。 通垞、蚘事には10〜40個の゚ラヌの䟋ず説明が含たれおいたす。 いく぀かの疑わしいコヌドの堎所は単にリストされおいたす。 ほずんどの譊告は保留䞭のたたです。 最良の堎合、開発者に通知した埌、開発者は詳现な調査のために完党な分析レポヌトを芁求したす。 悲しい真実は、私たちがテストしおいるほずんどのプロゞェクトで、 高レベルの譊告のみを衚瀺した埌、蚘事の資料は十分すぎるずいうこずです。 たた、CryEngine Vゲヌム゚ンゞンも䟋倖ではありたせん。 図2は、 高レベルアラヌトの構造を瀺しおいたす。











図2-高レベルアラヌトの説明



提瀺された円グラフのセクタヌに぀いおさらに詳しく

怜蚌結果



䞍快なコピヌアンドペヌスト











V501 「-」挔算子の巊右に同じ副次匏がありたす。q2.vz-q2.vz entitynode.cpp 93

bool CompareRotation(const Quat& q1, const Quat& q2, float epsilon) { return (fabs_tpl(q1.vx - q2.vx) <= epsilon) && (fabs_tpl(q1.vy - q2.vy) <= epsilon) && (fabs_tpl(q2.vz - q2.vz) <= epsilon) // <= && (fabs_tpl(q1.w - q2.w) <= epsilon); }
      
      





1桁のタむプミスは、おそらくプログラマが䜜成できる最も厄介なタむプミスの1぀です。 この関数では、アナラむザヌは倉数q1ずq2が混同されおいる可胜性が高い疑わしい匏q2.vz-q2.vzを怜出したした。



V501「||」の巊偎ず右偎に同䞀の副次匏「m_eTFSrc == eTF_BC6UH」がありたす 挔算子。 texturestreaming.cpp 919

 //! Texture formats. enum ETEX_Format : uint8 { .... eTF_BC4U, //!< 3Dc+. eTF_BC4S, eTF_BC5U, //!< 3Dc. eTF_BC5S, eTF_BC6UH, eTF_BC6SH, eTF_BC7, eTF_R9G9B9E5, .... }; bool CTexture::StreamPrepare(CImageFile* pIM) { .... if ((m_eTFSrc == eTF_R9G9B9E5) || (m_eTFSrc == eTF_BC6UH) || // <= (m_eTFSrc == eTF_BC6UH)) // <= { m_cMinColor /= m_cMaxColor.a; m_cMaxColor /= m_cMaxColor.a; } .... }
      
      





別のタむプのタむプミスには、定数のコピヌが含たれたす。 この堎合、倉数m_eTFSrcは定数eTF_BC6UHず2回比范されたす。これは、他の文字に眮き換える必芁がありたす。たずえば、1文字だけで区別したす-定数eTF_BC6SH



プロゞェクト内の2぀の類䌌した堎所

V517 「ifA{...} else ifA{...}」パタヌンの䜿甚が怜出されたした。 論理゚ラヌが存圚する可胜性がありたす。 行を確認しおください266、268。d3dhwshader.cpp 266

 int SD3DShader::Release(EHWShaderClass eSHClass, int nSize) { .... if (eSHClass == eHWSC_Pixel) return ((ID3D11PixelShader*)pHandle)->Release(); else if (eSHClass == eHWSC_Vertex) return ((ID3D11VertexShader*)pHandle)->Release(); else if (eSHClass == eHWSC_Geometry) // <= return ((ID3D11GeometryShader*)pHandle)->Release(); // <= else if (eSHClass == eHWSC_Geometry) // <= return ((ID3D11GeometryShader*)pHandle)->Release(); // <= else if (eSHClass == eHWSC_Hull) return ((ID3D11HullShader*)pHandle)->Release(); else if (eSHClass == eHWSC_Compute) return ((ID3D11ComputeShader*)pHandle)->Release(); else if (eSHClass == eHWSC_Domain) return ((ID3D11DomainShader*)pHandle)->Release() .... }
      
      





以䞋は、条件付きステヌトメントのカスケヌドの遅延コピヌの䟋で、コヌドの倉曎を忘れおいたした。



V517 「ifA{...} else ifA{...}」パタヌンの䜿甚が怜出されたした。 論理゚ラヌが存圚する可胜性がありたす。 行を確認しおください970、974。environmentalweapon.cpp 970

 void CEnvironmentalWeapon::UpdateDebugOutput() const { .... const char* attackStateName = "None"; if(m_currentAttackState & // <= EAttackStateType_EnactingPrimaryAttack) // <= { attackStateName = "Primary Attack"; } else if(m_currentAttackState & // <= EAttackStateType_EnactingPrimaryAttack) // <= { attackStateName = "Charged Throw"; } .... }
      
      





前のコヌドでは、コヌドのコピヌが倚すぎるために䜙分な条件が発生する可胜性が少なくずもあり、1぀のチェックは単に䞍芁です。 このコヌドフラグメントでは、同䞀の条件匏により、倉数attackStateNameは倀「Charged Throw」を想定したせん。



V519 「BlendFactor [2]」倉数には倀が2回連続しお割り圓おられたす。 おそらくこれは間違いです。 行を確認しおください1265、1266。ccrydxgldevicecontext.cpp 1266

 void CCryDXGLDeviceContext:: OMGetBlendState(...., FLOAT BlendFactor[4], ....) { CCryDXGLBlendState::ToInterface(ppBlendState, m_spBlendState); if ((*ppBlendState) != NULL) (*ppBlendState)->AddRef(); BlendFactor[0] = m_auBlendFactor[0]; BlendFactor[1] = m_auBlendFactor[1]; BlendFactor[2] = m_auBlendFactor[2]; // <= BlendFactor[2] = m_auBlendFactor[3]; // <= *pSampleMask = m_uSampleMask; }
      
      





プロゞェクトコヌドで、むンデックスのタむプミスのために、むンデックス3で芁玠を埋めるこずができなかったような関数を芋぀けたした BlendFactor [3] 。 この堎所は、タむプミスが蚱可されたコヌドがコピヌされたさらに2぀のフラグメントがアナラむザで芋぀からなかった堎合、タむプミスの興味深い堎所の1぀にすぎたせん。



V519 「m_auBlendFactor [2]」倉数には、倀が連続しお2回割り圓おられたす。 おそらくこれは間違いです。 行を確認しおください904、905。ccrydxgldevicecontext.cpp 905

 void CCryDXGLDeviceContext:: OMSetBlendState(....const FLOAT BlendFactor[4], ....) { .... m_uSampleMask = SampleMask; if (BlendFactor == NULL) { m_auBlendFactor[0] = 1.0f; m_auBlendFactor[1] = 1.0f; m_auBlendFactor[2] = 1.0f; // <= m_auBlendFactor[2] = 1.0f; // <= } else { m_auBlendFactor[0] = BlendFactor[0]; m_auBlendFactor[1] = BlendFactor[1]; m_auBlendFactor[2] = BlendFactor[2]; // <= m_auBlendFactor[2] = BlendFactor[3]; // <= } m_pContext->SetBlendColor(m_auBlendFactor[0], m_auBlendFactor[1], m_auBlendFactor[2], m_auBlendFactor[3]); m_pContext->SetSampleMask(m_uSampleMask); .... }
      
      





これは、むンデックス '3'で芁玠の入力をスキップし続ける堎所です。 しばらくの間、これは理にかなっおいるず思っおいたしたが、関数の最埌でm_auBlendFactor配列の4぀の芁玠すべおに魅力を感じたずき、この考えはすぐに私を去りたした。 ccrydxgldevicecontext.cppファむルがタむプミスコヌドのコピヌをいく぀か䜜成したようです。



V523 「then」ステヌトメントは「else」ステヌトメントず同等です。 d3dshadows.cpp 1410

 void CD3D9Renderer::ConfigShadowTexgen(....) { .... if ((pFr->m_Flags & DLF_DIRECTIONAL) || (!(pFr->bUseHWShadowMap) && !(pFr->bHWPCFCompare))) { //linearized shadows are used for any kind of directional //lights and for non-hw point lights m_cEF.m_TempVecs[2][Num] = 1.f / (pFr->fFarDist); } else { //hw point lights sources have non-linear depth for now m_cEF.m_TempVecs[2][Num] = 1.f / (pFr->fFarDist); } .... }
      
      





コピヌペヌストに関するセクションの終わりに、別の興味深い゚ラヌに぀いお説明したす。 条件匏の結果に関係なく、 m_cEF.m_TempVecs [2] [Num]の倀は垞に1぀の匏を䜿甚しお蚈算されたす。 このフラグメントの隣接するコヌドから刀断するず、むンデックスにタむプミスはありたせん。この状態では、むンデックス「2」の芁玠を正確に埋めたいず考えおいたす。 しかし、おそらく、蚈算匏は異なるものを䜿甚したかったのですが、行をコピヌした埌、コヌドを倉曎するのを忘れおいたした。



初期化の問題











V546クラスのメンバヌはそれ自䜓で初期化されたす 'eConfigMaxeConfigMax'。 particleparams.h 1013

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





クラスフィヌルドが固有倀を䜿甚しお初期化されたずきに、アナラむザヌがタむプミスを怜出したした。



V603オブゞェクトは䜜成されたしたが、䜿甚されおいたせん。 コンストラクタを呌び出す堎合は、「this-> SRenderingPassInfo :: SRenderingPassInfo....」を䜿甚する必芁がありたす。 i3dengine.h 2589

 SRenderingPassInfo() : pShadowGenMask(NULL) , nShadowSide(0) , nShadowLod(0) , nShadowFrustumId(0) , m_bAuxWindow(0) , m_nRenderStackLevel(0) , m_eShadowMapRendering(static_cast<uint8>(SHADOW_MAP_NONE)) , m_bCameraUnderWater(0) , m_nRenderingFlags(0) , m_fZoomFactor(0.0f) , m_pCamera(NULL) , m_nZoomInProgress(0) , m_nZoomMode(0) , m_pJobState(nullptr) { threadID nThreadID = 0; gEnv->pRenderer->EF_Query(EFQ_MainThreadList, nThreadID); m_nThreadID = static_cast<uint8>(nThreadID); m_nRenderFrameID = gEnv->pRenderer->GetFrameID(); m_nRenderMainFrameID = gEnv->pRenderer->GetFrameID(false); } SRenderingPassInfo(threadID id) { SRenderingPassInfo(); // <= SetThreadID(id); }
      
      





ここで、アナラむザヌは、コンストラクタヌの䞍適切な䜿甚を怜出したした。 プログラマヌはおそらく、別のコンストラクタヌ内のパラメヌタヌなしでこの方法でコンストラクタヌを呌び出すず、クラスのフィヌルドが初期化されるず考えたしたが、そうではありたせん。



このコヌドでは、次のこずが発生したす。タむプSRenderingPassInfoの新しい名前のないオブゞェクトがすぐに䜜成および砎棄されたす。 その結果、クラスフィヌルドは初期化されたせん。 ゚ラヌを修正する1぀の方法は、別個の初期化関数を䜜成し、異なるコンストラクタヌから呌び出すこずです。



V688 「m_cNewGeomMML」ロヌカル倉数は、クラスメンバヌの1぀ず同じ名前を持っおいるため、混乱を招く可胜性がありたす。 terrain_node.cpp 344

 void CTerrainNode::Init(....) { .... m_nOriginX = m_nOriginY = 0; // sector origin m_nLastTimeUsed = 0; // basically last time rendered uint8 m_cNewGeomMML = m_cCurrGeomMML = m_cNewGeomMML_Min .... m_pLeafData = 0; m_nTreeLevel = 0; .... }
      
      





アナラむザヌは、ロヌカル倉数cNewGeomMMLの名前ずクラスフィヌルドの䞀臎を怜出したした。 倚くの堎合、このコヌドぱラヌではありたせんが、ここではクラスの他のフィヌルドの初期化の背景に察しお非垞に疑わしいように芋えたす。



V575 「memset」機胜は「0」芁玠を凊理したす。 3番目の匕数を調べたす。 crythreadutil_win32.h 294

 void EnableFloatExceptions(....) { .... CONTEXT ctx; memset(&ctx, sizeof(ctx), 0); // <= .... }
      
      





アナラむザヌを䜿甚するず、非垞に興味深い゚ラヌが芋぀かりたした。 memset関数が呌び出されるず、枡された匕数が混同され、その結果、0バむトのメモリを埋めるために関数が呌び出されたした。 関数プロトタむプは次のようになりたす。

 void * memset ( void * ptr, int value, size_t num );
      
      





3番目の匕数はバッファのサむズである必芁があり、2番目の匕数はバッファを埋める必芁がある倀です。



修正されたオプション

 void EnableFloatExceptions(....) { .... CONTEXT ctx; memset(&ctx, 0, sizeof(ctx)); .... }
      
      





V630 「_alloca」関数は、コンストラクタヌを含むクラスであるオブゞェクトの配列にメモリを割り圓おるために䜿甚されたす。 command_buffer.cpp 62

 void CBuffer::Execute() { .... QuatT * pJointsTemp = static_cast<QuatT*>( alloca(m_state.m_jointCount * sizeof(QuatT))); .... }
      
      





プロゞェクトコヌドには、 alloca関数を䜿甚しお、オブゞェクトの配列にメモリが割り圓おられる堎所がありたす。 䞊蚘のコヌドでは、このメモリ割り圓お方法を䜿甚しお、 QuatTクラスのオブゞェクトはコンストラクタヌたたはデストラクタを呌び出したせん。 このようなコヌドは、初期化されおいない倉数やその他の゚ラヌを凊理する可胜性がありたす。



䞍審な堎所の党リスト

V583 「」挔算子は、その条件匏に関係なく、垞に1぀の同じ倀-1.8fを返したす。 posealignerc3.cpp 330

 ILINE bool InitializePoseAlignerPinger(....) { .... chainDesc.offsetMin = Vec3(0.0f, 0.0f, bIsMP ? -1.8f : -1.8f); chainDesc.offsetMax = Vec3(0.0f, 0.0f, bIsMP ? +0.75f : +1.f); .... }
      
      





プロゞェクトコヌドでは、䞉項挔算子が同じ倀を返す堎合がありたす。 おそらく前のケヌスで、圌らはコヌドの矎しさのためにそれを曞きたしたが、なぜこの断片でそれをしたのですか

 float predictDelta = inputSpeed < 0.0f ? 0.1f : 0.1f; // <= float dict = angle + predictDelta * ( angle - m_prevAngle) / dt ;
      
      





プロゞェクト内にあるこのような堎所はすべお

V570 「runtimeData.entityId」倉数はそれ自䜓に割り圓おられたす。 behaviortreenodes_ai.cpp 1771

 void ExecuteEnterScript(RuntimeData& runtimeData) { ExecuteScript(m_enterScriptFunction, runtimeData.entityId); runtimeData.entityId = runtimeData.entityId; // <= runtimeData.executeExitScriptIfDestructed = true; }
      
      





倉数自䜓ぞの疑わしい割り圓お。 開発者はこの堎所をチェックしおください。



操䜜の優先順䜍











V502おそらく、「?:」挔算子は予想ずは異なる方法で動䜜したす。 「」挔算子は、「+」挔算子よりも優先床が䜎くなりたす。 gpuparticlefeaturespawn.cpp 79

 bool HasDuration() { return m_useDuration; } void CFeatureSpawnRate::SpawnParticles(....) { .... SSpawnData& spawn = pRuntime->GetSpawnData(i); const float amount = spawn.amount; const int spawnedBefore = int(spawn.spawned); const float endTime = spawn.delay + HasDuration() ? spawn.duration : fHUGE; .... }
      
      





この機胜では時間蚈枬が正しく行われおいないようです。 加算挔算子の優先順䜍は、䞉項挔算子よりも高いため、倀0たたは1が最初にspawn.delayに远加され、倀spawn.durationたたはfHUGEが垞にendTime 倉数に曞き蟌たれたす 。 これはかなりよくある間違いです。 蚘事「 C / C ++の論理匏」で 、PVS-Studio゚ラヌデヌタベヌスで芋぀かった操䜜の優先床に関する興味深い゚ラヌパタヌンに぀いお説明したした。 専門家は間違っおいたすか 。



V634 「*」操䜜の優先順䜍は、「<<」操䜜の優先順䜍よりも高くなっおいたす。 匏で括匧を䜿甚する必芁がある堎合がありたす。 model.cpp 336

 enum joint_flags { angle0_locked = 1, .... }; bool CDefaultSkeleton::SetupPhysicalProxies(....) { .... for (int j = 0; .... ; j++) { // lock axes with 0 limits range m_arrModelJoints[i]....flags |= (....) * angle0_locked << j; } .... }
      
      





もう1぀の非垞に興味深い間違いは、乗算ずビット単䜍のシフト挔算の優先順䜍に関連しおいたす。 埌者は実行優先順䜍が䜎いため、各反埩での匏党䜓に1が乗算され angle0_locked定数は1に等しい、非垞に奇劙に芋えたす。



ほずんどの堎合、圌らは次のように曞きたいず思っおいたした。

 m_arrModelJoints[i]....flags |= (....) * (angle0_locked << j);
      
      





優先シフト挔算子を持぀35の疑わしい堎所のリストは、 CryEngine5_V634.txtファむルで提䟛されたす。



未定矩の動䜜



未定矩の動䜜は、特定の状況での䞀郚のプログラミング蚀語のプロパティであり、メモリ状態やトリガヌされた割り蟌みなどのランダムな芁因に䟝存する結果を生成したす。 蚀い換えれば、仕様では、可胜な状況での蚀語の動䜜は定矩されおいたせん。 プログラム内のこのような状況を認めるこずは間違いであり、プログラムが䞀郚のコンパむラで正垞に実行されたずしおも、クロスプラットフォヌムではなく、別のマシン、別のOS、および他のコンパむラ蚭定でも倱敗する可胜性がありたす。











V610未定矩の動䜜。 シフト挔算子「<<」を確認しおください。 巊のオペランド '-1'は負です。 physicalplaceholder.h 25

 #ifndef physicalplaceholder_h #define physicalplaceholder_h #pragma once .... const int NO_GRID_REG = -1<<14; const int GRID_REG_PENDING = NO_GRID_REG+1; ....
      
      





最新のC ++暙準によれば、負の数の巊シフトは未定矩の動䜜に぀ながりたす。 さらに、CryEngine Vコヌドにはさらにいく぀かの堎所がありたす。

V567未定矩の動䜜。 'm_current'倉数は、シヌケンスポむント間で2回䜿甚されおいる間に倉曎されたす。 operatorqueue.cpp 105

 bool COperatorQueue::Prepare(....) { ++m_current &= 1; m_ops[m_current].clear(); return true; }
      
      





アナラむザヌは、未定矩のプログラム動䜜に぀ながる匏を怜出したした。 倉数は、倀が倉化する間、連続する2぀のポむント間で繰り返し䜿甚されたす。 その結果、この匏の結果を予枬するこずは䞍可胜です。



より䞍審な堎所

条件のその他の゚ラヌ











V579 memcmp関数は、ポむンタヌずそのサむズを匕数ずしお受け取りたす。 間違いかもしれたせん。 3番目の匕数を調べたす。 graphicspipelinestateset.h 58

 bool operator==(const SComputePipelineStateDescription& other) const { return 0 == memcmp(this, &other, sizeof(this)); // <= }
      
      





オブゞェクトのサむズではなく、ポむンタヌのサむズを枡すこずにより、等倀挔算子がmemcmpの呌び出しを間違えたした。 珟圚、オブゞェクトは最初の数バむトで比范されたす。



修正されたオプション

 memcmp(this, &other, sizeof(*this));
      
      





残念ながら、プロゞェクトにはさらに3぀の堎所があり、確認する䟡倀がありたす。

V640コヌドの操䜜ロゞックがそのフォヌマットに察応しおいたせん。 2番目のステヌトメントは垞に実行されたす。 䞭括匧が欠萜しおいる可胜性がありたす。 livingentity.cpp 181

 CLivingEntity::~CLivingEntity() { for(int i=0;i<m_nParts;i++) { if (!m_parts[i].pPhysGeom || ....) delete[] m_parts[i].pMatMapping; m_parts[i].pMatMapping=0; } .... }
      
      





ゲヌム゚ンゞンのコヌドでは、開発者が1行に耇数のステヌトメントを曞く堎所がたくさんあるこずに気付きたした。 倚くの堎合、これらは通垞の割り圓おではなく、ルヌプ、条件、関数呌び出し、そしお時にはこれらすべおが混同されおいたす図3。











図3-䞍十分なコヌド圢匏



このようなプログラミングスタむルでは、倧量の゚ラヌを回避するこずはほが䞍可胜です。 そのため、怜蚎䞭のケヌスでは、特定の条件䞋で、オブゞェクトの配列の䞋からメモリを解攟し、ポむンタをリセットするこずが蚈画されおいたした。 しかし、䞍適切な曞匏蚭定のため、 m_parts [i] .pMatMappingポむンタヌはルヌプの各反埩で無効になりたす。 これがどんな負の結果をもたらすかは予枬できたせんが、コヌドは明らかに譊戒しおいたす。



疑わしい曞匏蚭定のいく぀かの堎所

V695範囲の亀差は条件匏内で可胜です。 䟋ifA <5{...} else ifA <2{...}。 行を確認しおください538、540。statobjrend.cpp 540

 bool CStatObj::RenderDebugInfo(....) { .... ColorB clr(0, 0, 0, 0); if (nRenderMats == 1) clr = ColorB(0, 0, 255, 255); else if (nRenderMats == 2) clr = ColorB(0, 255, 255, 255); else if (nRenderMats == 3) clr = ColorB(0, 255, 0, 255); else if (nRenderMats == 4) clr = ColorB(255, 0, 255, 255); else if (nRenderMats == 5) clr = ColorB(255, 255, 0, 255); else if (nRenderMats >= 6) // <= clr = ColorB(255, 0, 0, 255); else if (nRenderMats >= 11) // <= clr = ColorB(255, 255, 255, 255); .... }
      
      





このコヌドでは、プログラマがミスを犯したため、 ColorBカラヌ255、255、255、255は遞択されたせん。 最初に、 nRenderMatsの倀は1から5たでの数字ず順番に比范されたすが、開発者が倀の範囲での䜜業に切り替えたずき、11を超える倀がすでに6を超える範囲に入るこずを考慮しなかったため、最埌の条件が満たされるこずはありたせん。



この䞀連の条件は、もう1぀の堎所に完党にコピヌされたした。

V695範囲の亀差は条件匏内で可胜です。 䟋ifA <5{...} else ifA <2{...}。 行を確認しおください393、399。xmlcpb_nodelivewriter.cpp 399

 enum eNodeConstants { .... CHILDBLOCKS_MAX_DIST_FOR_8BITS = BIT(7) - 1, // 127 CHILDBLOCKS_MAX_DIST_FOR_16BITS = BIT(6) - 1, // 63 .... }; void CNodeLiveWriter::Compact() { .... if (dist <= CHILDBLOCKS_MAX_DIST_FOR_8BITS) // dist <= 127 { uint8 byteDist = dist; writeBuffer.AddData(&byteDist, sizeof(byteDist)); isChildBlockSaved = true; } else if (dist <= CHILDBLOCKS_MAX_DIST_FOR_16BITS) // dist <= 63 { uint8 byteHigh = CHILDBLOCKS_USING_MORE_THAN_8BITS | ....); uint8 byteLow = dist & 255; writeBuffer.AddData(&byteHigh, sizeof(byteHigh)); writeBuffer.AddData(&byteLow, sizeof(byteLow)); isChildBlockSaved = true; } .... }
      
      





このコヌドフラグメントの条件で同様の゚ラヌが発生したしたが、コントロヌルがかなり倧きなコヌドフラグメントを受け取らないようになりたした。 定数CHILDBLOCKS_MAX_DIST_FOR_8BITSおよびCHILDBLOCKS_MAX_DIST_FOR_16BITSの倀には、2番目の条件が真にならないような倀がありたす。



V547匏 'pszScript [iSrcBufPos]=' == ''は垞にtrueです。 char型の倀の範囲[-128、127]。 luadbg.cpp 716

 bool CLUADbg::LoadFile(const char* pszFile, bool bForceReload) { FILE* hFile = NULL; char* pszScript = NULL, * pszFormattedScript = NULL; .... while (pszScript[iSrcBufPos] != ' ' && .... pszScript[iSrcBufPos] != '=' && pszScript[iSrcBufPos] != '==' && // <= pszScript[iSrcBufPos] != '*' && pszScript[iSrcBufPos] != '+' && pszScript[iSrcBufPos] != '/' && pszScript[iSrcBufPos] != '~' && pszScript[iSrcBufPos] != '"') {} .... }
      
      





倧芏暡な条件匏には、垞に真ずなる郚分匏がありたす。 リテラル '=='はint型であり、15677に等しくなりたす。pszScript配列はchar型の芁玠で構成されたす。 char倀を15677に等しくするこずはできたせん。そのため、 pszScript [iSrcBufPos]= '=='匏は垞にtrueです。



V734過剰な衚珟。 サブストリング「_ddn」および「_ddna」を調べたす。 texture.cpp 4212

 void CTexture::PrepareLowResSystemCopy(byte* pTexData, ....) { .... // make sure we skip non diffuse textures if (strstr(GetName(), "_ddn") // <= || strstr(GetName(), "_ddna") // <= || strstr(GetName(), "_mask") || strstr(GetName(), "_spec.") || strstr(GetName(), "_gloss") || strstr(GetName(), "_displ") || strstr(GetName(), "characters") || strstr(GetName(), "$") ) return; .... }
      
      





strstr関数は、指定された郚分文字列が別の文字列で最初に珟れる堎所を怜玢し、郚分文字列が最初に珟れる堎所ぞのポむンタたたはnullポむンタを返したす。 サブストリング「_ddn」が最初に怜玢され、次に「_ddna」が怜玢されたす。これは、短いサブストリングが芋぀かった堎合に条件が満たされるこずを意味したす。 おそらく、プログラマヌが蚈画したずおりにコヌドが機胜しない可胜性がありたす。 さお、たたは少なくずも匏は冗長であり、䜙分なチェックを削陀するこずで簡略化できたす。



V590この衚珟を調べるこずを怜蚎しおください。 衚珟が過剰であるか、誀怍が含たれおいたす。 goalop_crysis2.cpp 3779

 void COPCrysis2FlightFireWeapons::ParseParam(....) { .... else if (!paused && (m_State == eFP_PAUSED) && // <= (m_State != eFP_PAUSED_OVERRIDE)) // <= .... }
      
      





ParseParam関数の条件匏は、結果が郚分匏 m_State= EFP_PAUSED_OVERRIDE に䟝存しないように蚘述されおいたす。



簡単な䟋を考えおみたしょう。

 if ( err == code1 && err != code2) { .... }
      
      





条件匏党䜓の結果は郚分匏err= Code2の結果に䟝存したせん。これは、この䟋の真理倀衚を䜜成するずきに明確に芋られたす図4











図4-論理匏の真理倀衚

笊号なしの数倀ずれロの比范











テストされたプロゞェクトでは、倚くの堎合、れロず笊号なしの数倀の比范が怜出され、その結果は垞にtrueたたはfalseになりたす。このようなコヌドには、必ずしも重倧な゚ラヌが含たれるずは限りたせん。倚くの堎合、これは過床の泚意の結果であるか、倉数の型を笊号付きから笊号なしに倉曎した埌もチェックが残りたす。いずれにしおも、そのような比范は慎重に確認する必芁がありたす。



V547匏 'm_socket <0'は垞にfalseです。笊号なしの型の倀が<0になるこずはありたせんservicenetwork.cpp 585

 typedef SOCKET CRYSOCKET; // Internal socket data CRYSOCKET m_socket; bool CServiceNetworkConnection::TryReconnect() { .... // Create new socket if needed if (m_socket == 0) { m_socket = CrySock::socketinet(); if (m_socket < 0) { .... return false; } } .... }
      
      





異なるプラットフォヌムで眲名および眲名解陀できるSOCKETタむプに぀いお詳しく説明したす。したがっお、このタむプを䜿甚するには、暙準ヘッダヌファむルで定矩された特別なマクロず定数を䜿甚するこずを匷くお勧めしたす。



クロスプラットフォヌムプロゞェクトでは、0たたは-1の倀ずの比范が頻繁に怜出され、゚ラヌコヌドの誀った解釈に぀ながりたす。CryEngine Vプロゞェクトも䟋倖ではありたせんが、正しいチェックが存圚する堎所もありたす。たずえば

 if (m_socket == CRY_INVALID_SOCKET)
      
      





しかし、倚くの堎所でコヌドは異なる怜蚌方法を䜿甚しおいたす。



眲名されおいない倉数がれロず比范しお疑わしい47箇所がCryEngine5_V547.txtファむルに移動されたした。開発者は指定された堎所を確認するこずをお勧めしたす。



危険なポむンタヌ











蚺断ルヌルV595は、コヌド内で逆参照ポむンタヌを芋぀けたす。その有効性はコヌドの䞋で実行されたす。ポむンタヌを䜿甚した埌。実際には、この蚺断は非垞に急な゚ラヌを芋぀けたす。ポむンタヌが間接的にチェックされるずいう事実により、少数の誀怜知が発生したす。1぀たたは耇数の他の倉数を䜿甚したすが、そのようなコヌドを理解するのは非垞に難しいこずに同意したす。この蚺断のトリガヌの3぀の䟋を挙げたすが、このようなコヌドがどのように機胜するかは特に驚くこずですが、残りはCryEngine5_V595.txtファむルにありたす。



䟋1



V595 nullptrに察しお怜蚌される前に、「m_pPartManager」ポむンタヌが䜿甚されたした。チェック行1441、1442。3denginerender.cpp 1441

 void C3DEngine::RenderInternal(....) { .... m_pPartManager->GetLightProfileCounts().ResetFrameTicks(); if (passInfo.IsGeneralPass() && m_pPartManager) m_pPartManager->Update(); .... }
      
      





m_pPartManagerポむンタヌの逆参照ずチェック。



䟋2



V595 nullptrに察しお怜蚌される前に、「gEnv-> p3DEngine」ポむンタヌが䜿甚されたした。行を確認しおください1477、1480。gameserialize.cpp 1477

 bool CGameSerialize::LoadLevel(....) { .... // can quick-load if (!gEnv->p3DEngine->RestoreTerrainFromDisk()) return false; if (gEnv->p3DEngine) { gEnv->p3DEngine->ResetPostEffects(); } .... }
      
      





gEnv-> p3DEngine pointerの逆参照ずチェック。



䟋3



V595 nullptrに察しお怜蚌される前に、「pSpline」ポむンタヌが䜿甚されたした。行を確認しおください158、161。facechannelkeycleanup.cpp 158

 void FaceChannel::CleanupKeys(....) { CFacialAnimChannelInterpolator backupSpline(*pSpline); // Create the key entries array. int numKeys = (pSpline ? pSpline->num_keys() : 0); .... }
      
      





pSplineポむンタヌの逆参照ずチェック。



その他の譊告











V622「switch」ステヌトメントの怜査を怜蚎しおください。最初の「ケヌス」挔算子が欠萜しおいる可胜性がありたす。mergedmeshrendernode.cpp 999

 static inline void ExtractSphereSet(....) { .... switch (statusPos.pGeom->GetType()) { if (false) { case GEOM_CAPSULE: statusPos.pGeom->GetPrimitive(0, &cylinder); } if (false) { case GEOM_CYLINDER: statusPos.pGeom->GetPrimitive(0, &cylinder); } for (int i = 0; i < 2 && ....; ++i) { .... } break; .... }
      
      





おそらく、コヌドのこの䜜品は、プロゞェクトCryEngine Vに発生したすべおの奇劙であるラベルを遞択する堎合は、条件文に䟝存しない堎合があっおも、停の堎合は。声明のスむッチは、それが衚珟満たす堎合、ラベルに無条件ゞャンプを実行しおいるスむッチを。break挔算子を䜿甚せずに、このようなコヌドを䜿甚するず、䞍芁な挔算子の実行を「バむパス」できたすが、すべおの開発者がこのような明癜でないコヌドに付随できるわけではありたせん。そしお、なぜラベルぞの移行GEOM_CAPSULEずGEOM_CYLINDERは、同じコヌドを実行したすか



V510'LogError'関数は、クラスタむプ倉数を2番目の実匕数ずしお受け取るこずは想定されおいたせん。 behaviortreenodes_action.cpp 143

 typedef CryStringT<char> string; // The actual fragment name. string m_fragName; //! cast to C string. const value_type* c_str() const { return m_str; } const value_type* data() const { return m_str; }; void LogError(const char* format, ...) const { .... } void QueueAction(const UpdateContext& context) { .... ErrorReporter(*this, context).LogError("....'%s'", m_fragName); .... }
      
      





関数の説明ですべおの有効なパラメヌタヌの数ずタむプを瀺すこずができない堎合、仮パラメヌタヌのリストは省略蚘号...で終わりたす。これは、「そしお、おそらく、さらにいく぀かの匕数」を意味したす。PODPlain Old Dataタむプのみが省略蚘号の実際のパラメヌタヌずしお機胜できたす。関数の省略蚘号がクラスオブゞェクトにパラメヌタヌずしお枡される堎合、ほずんどの堎合、プログラムの゚ラヌを瀺したす。文字列ぞのポむンタの代わりに、オブゞェクトの内容はスタックに萜ちたす。このようなコヌドは、バッファ内の「意味䞍明」の圢成、たたはプログラムの異垞終了に぀ながりたす。CryEngine Vコヌドは独自の文字列クラスを䜿甚し、すでに適切なc_strメ゜ッドを持っおいたす。



修正されたオプション

 LogError("....'%s'", m_fragName.c_str();
      
      





さらにいく぀かの疑わしい堎所

V529奇数セミコロン ';' 「for」挔算子の埌。boolean3d.cpp 1314

 int CTriMesh::Slice(....) { .... bop_meshupdate *pmd = new bop_meshupdate, *pmd0; pmd->pMesh[0]=pmd->pMesh[1] = this; AddRef();AddRef(); for(pmd0=m_pMeshUpdate; pmd0->next; pmd0=pmd0->next); // <= pmd0->next = pmd; .... }
      
      





非垞に疑わしいコヌド。forルヌプの埌にセミコロンを入れたすが、コヌドをフォヌマットするず、ルヌプ内に本文が存圚するこずを意味したす。



V535倉数 'j'は、このルヌプず倖偎のルヌプに䜿甚されおいたす。行を確認しおください3447、3490。physicalworld.cpp 3490

 void CPhysicalWorld::SimulateExplosion(....) { .... for(j=0;j<pmd->nIslands;j++) // <= line 3447 { .... for(j=0;j<pcontacts[ncont].nborderpt;j++) // <= line 3490 { .... }
      
      





プロゞェクトコヌドには、たずえば、ネストルヌプず倖郚ルヌプで1぀のカりンタヌを䜿甚するなど、他の危険なコヌドもいっぱいです。゜ヌスコヌド付きの倧きなファむルには、さたざたな堎所で耇雑な曞匏蚭定ず倉数の倉曎が含たれおいたす。ここでは静的分析を行う方法はありたせん。



さらにいく぀かの疑わしいサむクル

V539関数「erase」ぞの匕数ずしお枡されるむテレヌタヌの怜査を怜蚎しおください。frameprofilerender.cpp 1090

 float CFrameProfileSystem::RenderPeaks() { .... std::vector<SPeakRecord>& rPeaks = m_peaks; // Go through all peaks. for (int i = 0; i < (int)rPeaks.size(); i++) { .... if (age > fHotToColdTime) { rPeaks.erase(m_peaks.begin() + i); // <= i--; } .... }
      
      





アナラむザヌは、別のコンテナヌのむテレヌタヌが、コンテナヌでアクションを実行する関数に枡されるこずを蚈算したした。このコヌドでは、そうではなく、゚ラヌはありたせん。rPeaks倉数はm_peaksぞの参照です。ただし、このようなコヌドは、コヌドアナラむザヌだけでなく、コヌドに同行する人にずっおも誀解を招く可胜性がありたす。そのように曞かないでください。



V713ポむンタヌpCollisionは、同じ論理匏のnullptrに察しお怜蚌される前に、論理匏で䜿甚されたした。actiongame.cpp 4235

 int CActionGame::OnCollisionImmediate(const EventPhys* pEvent) { .... else if (pMat->GetBreakability() == 2 && pCollision->idmat[0] != pCollision->idmat[1] && (energy = pMat->GetBreakEnergy()) > 0 && pCollision->mass[0] * 2 > energy && .... pMat->GetHitpoints() <= FtoI(min(1E6f, hitenergy / energy)) && pCollision) // <= return 0; .... }
      
      





オペレヌタがあれば、垞にポむンタのアクセスは非垞に条件匏、含たれおいpCollisionを。゚ラヌは、れロによるpCollisionポむンタヌの最新の倀のチェックです。耇数の間接参照の埌。



V758関数によっお返されるスマヌトポむンタヌが砎棄されるず、「commandList」参照が無効になりたす。renderitemdrawer.cpp 274

 typedef std::shared_ptr<....> CDeviceGraphicsCommandListPtr; CDeviceGraphicsCommandListPtr CDeviceObjectFactory::GetCoreGraphicsCommandList() const { return m_pCoreCommandList; } void CRenderItemDrawer::DrawCompiledRenderItems(....) const { .... { auto& RESTRICT_REFERENCE commandList = *CCryDeviceWrapper:: GetObjectFactory().GetCoreGraphicsCommandList(); passContext....->PrepareRenderPassForUse(commandList); } .... }
      
      





スマヌトポむンタヌが保持する倀ぞの参照は、commandList倉数に栌玍されたす。オブゞェクトがスマヌトポむンタヌによっお砎棄されるず、リンクは無効になりたす。



さらにいく぀かのそのような堎所

おわりに



テスタヌの䜜業段階にある゚ラヌずは異なり、コヌド䜜成䞭の゚ラヌの修正にはほずんど費甚がかかりたせん。たた、リリヌスされた補品の゚ラヌにはすでに莫倧な費甚がかかりたす。䜿甚するアナラむザヌに関係なく、静的コヌド分析の方法論は、コヌドおよび゜フトりェア補品党䜓の品質を制埡する非垞に効果的な方法であるこずが長い間蚌明されおいたす。



Epic Gamesずのコラボレヌションは、Unreal Engine 4に静的アナラむザヌを実装する利点を実蚌しおいたす。開発者がアナラむザヌ統合のあらゆる問題を支揎し、発芋した゚ラヌを修正しお、新しいプロゞェクトコヌドを定期的にチェックできるようにしたした。 Crytekず同様の協力の準備ができおいたす。PVS-Studio



をお詊しくださいC / C ++ / Cプロゞェクト。



CryEngineV開発者はプロゞェクトレビュヌの前に通知されたため、䞀郚の゚ラヌはすでに修正されおいる可胜性がありたす。





英語を話す聎衆ずこの蚘事を共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいSvyatoslav Razmyslov。CryEngineのチェックむンロングVを埅っおいたした。



蚘事を読んで質問がありたすか
倚くの堎合、蚘事には同じ質問が寄せられたす。 ここで回答を集めたした PVS-Studioバヌゞョン2015に関する蚘事の読者からの質問ぞの回答 。 リストをご芧ください。




All Articles