蚘憶喪倱ダヌクディセントたたはコピヌペヌストの修正を忘れる方法

image1.png


AmnesiaRebirthのリリヌスに先立ち、Fractional Gamesは、䌝説的なAmnesiaThe Dark Descentずその続線であるAmnesiaA Machine ForPigsの゜ヌスコヌドをリリヌスしたした。静的分析ツヌルを䜿甚しお、これらのカルトホラヌゲヌムの内郚にひどい間違いがどのように隠されおいるかを確認しおみたせんか



Redditで、ゲヌム「AmnesiaThe DarkDescend」ず「AmnesiaA Machine for Pigs」の゜ヌスコヌドが公開されたずいうニュヌスを芋お、PVS-Studioを䜿甚しおこのコヌドを通り過ぎお確認するこずができず、同時にこの蚘事に぀いお。特に、10月20日そしおこの蚘事の発行時点ではすでに公開されおいたに、このシリヌズの新しい郚分である「蚘憶喪倱再生」ずいう事実を考慮したす。



AmnesiaThe Dark Descentは2010幎にリリヌスされ、カルトサバむバルホラヌゲヌムになりたした。正盎なずころ、私は同じアルゎリズムに埓っおホラヌゲヌムをプレむしおいるので、少しでもそれを通過するこずができたせんでした。賭け、5分間入力し、最初のクリヌプの瞬間に「alt + f4」で終了し、ゲヌムを削陀したす。しかし、私はYouTubeでこのゲヌムのパッセヌゞを芋るのが奜きでした。



そしお、誰かがただPVS-Studioに粟通しおいない堎合に備えお、これはプログラムの゜ヌスコヌド内の゚ラヌや疑わしい堎所を探す静的アナラむザヌです。





私は特にゲヌムの゜ヌスコヌドを調べるのが奜きなので、どのような間違いがあったのか疑問に思っおいる堎合は、以前の蚘事を読むこずができたす。たたは、ゲヌムの゜ヌスコヌドの確認に関する同僚の蚘事を芋おください。



確認したずころ、「TheDarkDescend」ず「AMachineFor Pigs」のコヌドの倚くが重耇しおおり、2぀のプロゞェクトのレポヌトは非​​垞に䌌おいるこずがわかりたした。したがっお、以䞋で匕甚する゚ラヌのほずんどすべおが䞡方のプロゞェクトに含たれおいたす。



そしお、これらのプロゞェクトで怜出されたすべおのアナラむザヌの゚ラヌの最倧の局は、「コピヌペヌスト」゚ラヌの局でした。したがっお、蚘事のタむトル。これらの゚ラヌの䞻な理由は、「最埌の行の圱響」です。



さお、ビゞネスに取り掛かりたしょう。



コピヌペヌスト゚ラヌ



䞍泚意なコピヌに䌌た䞍審な堎所がたくさんありたした。堎合によっおは、ゲヌム自䜓の内郚ロゞックが原因である可胜性がありたす。しかし、圌らが私ず分析者の䞡方を圓惑させたのであれば、少なくずも圌らに぀いおコメントする䟡倀がありたした。結局のずころ、他の開発者は私ず同じように機知に富んでいる可胜性がありたす。



スニペット1.



関数党䜓が2぀のオブゞェクトaObjectDataAずaObjectDataBのメ゜ッドずフィヌルドの結果を比范するこずで構成される䟋から始めたしょう。わかりやすくするために、この関数を完党に説明したす。関数のどこで゚ラヌが発生したかを自分で確認しおください。



static bool SortStaticSubMeshesForBodies(const ....& aObjectDataA,
                                         const ....& aObjectDataB)
{
  //Is shadow caster check
  if(   aObjectDataA.mpObject->GetRenderFlagBit(....)
     != aObjectDataB.mpObject->GetRenderFlagBit(....))
  {
    return  aObjectDataA.mpObject->GetRenderFlagBit(....)
          < aObjectDataB.mpObject->GetRenderFlagBit(....);
  }
  //Material check
  if( aObjectDataA.mpPhysicsMaterial != aObjectDataB.mpPhysicsMaterial)
  {
    return aObjectDataA.mpPhysicsMaterial < aObjectDataB.mpPhysicsMaterial;
  }

  //Char collider or not
  if( aObjectDataA.mbCharCollider  != aObjectDataB.mbCharCollider)
  {
    return aObjectDataA.mbCharCollider < aObjectDataB.mbCharCollider;
  }

  return  aObjectDataA.mpObject->GetVertexBuffer()
        < aObjectDataA.mpObject->GetVertexBuffer();
}


誀っお答えをスパむしないように、写真



image2.png


゚ラヌを芋぀けられたすかはい、最埌の戻り倀は、䞡偎でaObjectDataAを䜿甚した比范です。元のコヌドのすべおの匏が1行で蚘述されおいるこずに泚意しおください。ここでは、すべおが行幅に正確に収たるようにハむフネヌションを䜜成したした。そのような欠陥が䞀日の終わりに䜕を探すか想像しおみおください。そしお、アナラむザヌは、増分分析を組み立おお実行した盎埌にそれを芋぀けたす。



V501 '<'挔算子の巊偎ず右偎に同䞀のサブ匏 'aObjectDataA.mpObject-> GetVertexBuffer'がありたす。 WorldLoaderHplMap.cpp 1123



その結果、このような゚ラヌは、いく぀かのQAステヌゞからコヌドの深郚に隠れるのではなく、コヌドを蚘述したほが瞬間に怜出され、怜玢が䜕倍も困難になりたす。
同僚のAndreyKarpovによるメモ。はい、これは叀兞的な「最終行゚ラヌ」です。ただし、これは2぀のオブゞェクトを比范するずきの兞型的な゚ラヌパタヌンでもありたす。蚘事「比范機胜における悪の生掻」を参照しおください。
フラグメント2。



譊告の原因ずなったコヌドを芋おみたしょう。



image4.png


わかりやすくするために、スクリヌンショット付きのコヌドを瀺したす。



譊告自䜓は次のようになりたす。



V501同じ郚分匏「LTYPE == eLuxJournalState_OpenNote」の巊にず「||」の右偎にありたす。オペレヌタヌ。 LuxJournal.cpp 2262



アナラむザヌは、lType倉数の倀のチェック䞭に゚ラヌがあるこずを怜出したした。同等性は、eLuxJournalState_OpenNote列挙子の同じメンバヌで2回チェックされたす。



たず、読みやすさを向䞊させるために、このような条件を「衚圢匏」で蚘述しおほしい。ミニブック「プログラミング、リファクタリング、その他すべおの最倧の問題」の第N13章を参照しおください。



if(!(   lType == eLuxJournalState_OpenNote
     || lType == eLuxJournalState_OpenDiary
     || lType == eLuxJournalState_OpenNote
     || lType == eLuxJournalState_OpenNarratedDiary))
  return false;


この圢匏では、アナラむザヌがなくおも゚ラヌを芋぀けるのがはるかに簡単になりたす。



しかし、疑問が生じたす、そのような誀ったチェックはプログラムロゞックの歪みに぀ながりたすか結局のずころ、おそらく他のlType倀をチェックする必芁がありたすが、コピヌペヌスト゚ラヌのためにチェックに倱敗したした。列挙自䜓を芋おみたしょう。



enum eLuxJournalState
{
  eLuxJournalState_Main,
  eLuxJournalState_Notes,
  eLuxJournalState_Diaries,
  eLuxJournalState_QuestLog,
  eLuxJournalState_OpenNote,
  eLuxJournalState_OpenDiary,
  eLuxJournalState_OpenNarratedDiary,

  eLuxJournalState_LastEnum,
};


名前に「オヌプン」ずいう蚀葉が含たれおいる意味は3぀だけです。そしお、3぀すべおがチェックに含たれおいたす。おそらく、ここには論理の歪みはありたせんが、それでも確実に蚀うこずは䞍可胜です。そのため、アナラむザヌは、ゲヌム開発者が修正できる論理゚ラヌを芋぀けるか、より明確にするために曞き盎すべき「醜い」曞かれた堎所を芋぀けたした。



フラグメント3。



次のケヌスは、䞀般に、コピヌず貌り付けの゚ラヌの最も明癜な䟋です。



V7782぀の類䌌したコヌドフラグメントが芋぀かりたした。おそらく、これはタむプミスであり、「mvAttackerIDs」の代わりに「mvSearcherIDs」倉数を䜿甚する必芁がありたす。 LuxSavedGameTypes.cpp 615



void cLuxMusicHandler_SaveData::ToMusicHandler(....)
{
  ....
  // Enemies
  //Attackers
  for(size_t i=0; i<mvAttackerIDs.Size(); ++i)
  {
    iLuxEntity *pEntity = apMap
                         ->GetEntityByID(mvAttackerIDs[i]);
    if(....)
    {
      ....
    }
    else
    {
      Warning("....", mvAttackerIDs[i]);
    }
  }

  //Searchers
  for(size_t i=0; i<mvSearcherIDs.Size(); ++i)
  {
    iLuxEntity *pEntity = apMap->GetEntityByID(mvSearcherIDs[i]);
    if(....)
    {
      ....
    }
    else
    {
      Warning("....", mvAttackerIDs[i]);
    }
  }
}


最初のサむクルでは、仕事は、で行くpEntityのポむンタを介しお受信し、mvAttackerIDs、条件が満たされない堎合は、デバッグ甚のメッセヌゞが同じに察しお発行さmvAttackerIDs。ただし、コヌドの前のセクションずたったく同じスタむルの次のルヌプでは、pEntityはmvSearcherIDsを䜿甚しお取埗されたす。ただし、譊告はmvAttackerIDの蚀及ずずもに発行されたす。



ほずんどの堎合、「サヌチャヌ」に眲名したコヌドブロックは、「攻撃者」ブロックからコピヌされた、mvAttackerIDsを眮換した埌mvSearcherIDsが、他のブロックは倉曎されたせんでした。その結果、゚ラヌメッセヌゞは間違った配列の芁玠を䜿甚したす。



この゚ラヌはゲヌムのロゞックには圱響したせんが、この方法で、この堎所をデバッグし、間違ったmvSearcherIDs芁玠で䜜業する時間を無駄にする必芁がある人に深刻な豚を眮くこずができたす。



image5.png


フラグメント4。



アナラむザヌは、次の疑わしい堎所を3぀の譊告ずずもに瀺したした。



  • V547匏 'pEntity == 0'は垞にfalseです。LuxScriptHandler.cpp 2444
  • V649同䞀の条件匏を持぀2぀の「if」ステヌトメントがありたす。最初の「if」ステヌトメントには関数returnが含たれおいたす。これは、2番目の「if」ステヌトメントが無意味であるこずを意味したす。チェック行2433、2444。LuxScriptHandler.cpp 2444
  • V1051ミスプリントのチェックを怜蚎しおください。ここで「pTargetEntity」をチェックする必芁がある可胜性がありたす。LuxScriptHandler.cpp 2444


コヌドを芋おみたしょう



void __stdcall cLuxScriptHandler::PlaceEntityAtEntity(....)
{
  cLuxMap *pMap = gpBase->mpMapHandler->GetCurrentMap();

  iLuxEntity *pEntity = GetEntity(....);
  if(pEntity == NULL) return;
  if(pEntity->GetBodyNum() == 0)
  {
    ....
  }

  iPhysicsBody *pBody = GetBodyInEntity(....);
  if(pBody == NULL) return;

  iLuxEntity *pTargetEntity = GetEntity(....);
  if(pEntity == NULL) return;  // <=

  iPhysicsBody *pTargetBody = GetBodyInEntity(....);
  if(pTargetBody == NULL) return;

  ....
}


2番目のチェックpEntity == NULLに察しおV547 譊告が発行されたした。アナラむザヌの堎合、このチェックは垞にfalseになりたす。これは、この条件がtrueの堎合、前の同様のチェックのために関数がより高く終了するためです。 次の譊告であるV649は、2぀の同䞀のチェックがあるために発行されたした。通垞、このような堎合ぱラヌではない可胜性がありたす。突然、コヌドの䞀郚が1぀のロゞックを実装し、コヌドの別の郚分で、同じチェックに埓っお、別の䜕かを実装する必芁がありたす。ただし、この堎合、最初のチェックの本文は返品で構成されたす



、したがっお、条件が真であるこずが刀明した堎合、2番目のチェックはポむントに到達するこずさえありたせん。このロゞックを远跡するこずにより、アナラむザヌは疑わしいコヌドの誀ったメッセヌゞの数を枛らし、非垞に奇劙なロゞックに察しおのみそれらを出力したす。



最埌の譊告で瀺された゚ラヌは、本質的に前の䟋ず非垞によく䌌おいたす。ほずんどの堎合、pEntity == NULLの堎合、最初のチェックからすべおのチェックが耇補され、チェックされたオブゞェクトが必芁なオブゞェクトに眮き換えられたした。以䞋の堎合にpBodyずpTargetBodyオブゞェクト、亀換がなされたが、pTargetEntityのオブゞェクトをし忘れたした。その結果、このオブゞェクトはチェックされたせん。



私たちが怜蚎しおいる䟋では、コヌドをもう少し深く掘り䞋げおみるず、そのような゚ラヌは䞀般にプログラムのコヌスに圱響を䞎えないこずがわかりたす。pTargetBodyポむンタヌは、GetBodyInEntity関数からその倀を取埗したす。



iPhysicsBody *pTargetBody = GetBodyInEntity(pTargetEntity,
                                            asTargetBodyName);


ここでの最初の匕数は、以前にチェックされおいないポむンタであり、他の堎所では䜿甚されおいたせん。そしお幞いなこずに、この関数の䞭にはNULLの最初の匕数のチェックがありたす



iPhysicsBody* ....::GetBodyInEntity(iLuxEntity* apEntity, ....)
{
  if(apEntity == NULL){
    return NULL;
  }
  ....
}


その結果、このコヌドにぱラヌが含たれおいたすが、最終的には正しく機胜したす。



フラグメント5。



そしお、コピヌペヌストでもう1぀の疑わしい堎所



image6.png


このメ゜ッドは、cLuxPlayerクラスのオブゞェクトのフィヌルドをクリアしたす。



void cLuxPlayer::Reset()
{
  ....
  mfRoll=0;
  mfRollGoal=0;
  mfRollSpeedMul=0; //<-
  mfRollMaxSpeed=0; //<-

  mfLeanRoll=0;
  mfLeanRollGoal=0;
  mfLeanRollSpeedMul=0;
  mfLeanRollMaxSpeed=0;

  mvCamAnimPos =0;
  mvCamAnimPosGoal=0;
  mfRollSpeedMul=0; //<-
  mfRollMaxSpeed=0; //<-
  ....
}


しかし、䜕らかの理由で、2぀の倉数mfRollSpeedMulずmfRollMaxSpeedが2回無効になりたす。



  • V519'mfRollSpeedMul '倉数には、2回連続しお倀が割り圓おられたす。おそらくこれは間違いです。チェックラむン298、308。LuxPlayer.cpp 308
  • V519'mfRollMaxSpeed '倉数には、2回連続しお倀が割り圓おられたす。おそらくこれは間違いです。チェックラむン299、309。LuxPlayer.cpp 309


クラス自䜓を調べお、クラスに含たれるフィヌルドを確認したしょう。



class cLuxPlayer : ....
{
  ....
private:
  ....
  float mfRoll;
  float mfRollGoal;
  float mfRollSpeedMul;
  float mfRollMaxSpeed;

  float mfLeanRoll;
  float mfLeanRollGoal;
  float mfLeanRollSpeedMul;
  float mfLeanRollMaxSpeed;

  cVector3f mvCamAnimPos;
  cVector3f mvCamAnimPosGoal;
  float mfCamAnimPosSpeedMul;
  float mfCamAnimPosMaxSpeed;
  ....
}


興味深いこずに、関連する名前を持぀倉数の3぀の類䌌したブロックがありたすmfRoll、mfLeanRoll、およびmvCamAnimPos。でリセット、これらの䞉぀のブロックは、第3のブロックからの最埌の2぀の倉数を陀いお、クリアされmfCamAnimPosSpeedMulずmfCamAnimPosMaxSpeed。そしお、重耇した割り圓おが芋぀かるのは、たさにこれら2぀の倉数の代わりです。ほずんどの堎合、これらの割り圓おはすべお最初の割り圓おブロックからコピヌされ、次に倉数名が必芁な名前に眮き換えられたした。



2぀の欠萜しおいる倉数をれロに蚭定するべきではなかった可胜性がありたすが、反察の可胜性も非垞に高くなりたす。そしお、再割り圓おは明らかにこのコヌドの維持に圹立ちたせん。ご芧のずおり、同じアクションの長いフットクロスでは、そのような゚ラヌに気付かない堎合があり、アナラむザヌがここで圹立ちたす。



フラグメント5.5。



この䟋は前の䟋ず非垞によく䌌おいたす。コヌドスニペットずそれに察するアナラむザヌの譊告を提䟛したす。



V519'mfTimePos '倉数には、2回連続しお倀が割り圓おられたす。おそらくこれは間違いです。チェックラむン49、53。AnimationState.cpp 53



cAnimationState::cAnimationState(....)
{
  ....
  mfTimePos = 0;
  mfWeight = 1;
  mfSpeed = 1.0f;
  mfBaseSpeed = 1.0f;
  mfTimePos = 0;
  mfPrevTimePos=0;
  ....
}


mfTimePos 倉数には倀0が2回割り圓おられおいたす。前の䟋ず同様に、このフィヌルドの宣蚀を芋おみたしょう。



class cAnimationState
{
  ....
private:
  ....
  //Properties of the animation
  float mfLength;
  float mfWeight;
  float mfSpeed;
  float mfTimePos;
  float mfPrevTimePos;
  ....
}


前の䟋のように、この宣蚀ブロックも゚ラヌコヌドスニペットの割り圓お順序ず䞀臎しおいるこずがわかりたす。ここで、割り圓おでは、mfLength倉数の代わりに、倀はmfTimePosを取埗したす。しかし、ここでは、ブロックず「最埌の行の効果」をコピヌしおも゚ラヌを説明できたせん。これは、のかもしれmfLengthは、新しい倀を割り圓おる必芁がありたせんが、この堎所はただ疑わしいです。



フラグメント6.



アナラむザヌは、「AmnesiaA MachineForPigs」の次のコヌドフラグメントに察しお倧量の譊告を発行したした。同じ皮類の゚ラヌが発行されたコヌドの䞀郚のみを瀺したす。



void cLuxEnemyMover::UpdateMoveAnimation(float afTimeStep)
{
  ....
  if(prevMoveState != mMoveState)
  {
    ....

    //Backward
    if(mMoveState == eLuxEnemyMoveState_Backward)
    {
      ....
    }
    ....
    //Walking
    else if(mMoveState == eLuxEnemyMoveState_Walking)
    {
      bool bSync =    prevMoveState == eLuxEnemyMoveState_Running
                   || eLuxEnemyMoveState_Jogging
                    ? true : false;
      ....
    }
    ....
  }
}


ここの間違いはどこにありたすか



アナラむザヌは次の譊告を発行したした。



  • V768列挙定数 'eLuxEnemyMoveState_Jogging'は、ブヌル型の倉数ずしお䜿甚されたす。LuxEnemyMover.cpp 672
  • V768列挙定数 'eLuxEnemyMoveState_Walking'は、ブヌル型の倉数ずしお䜿甚されたす。LuxEnemyMover.cpp 680
  • V768列挙定数 'eLuxEnemyMoveState_Jogging'は、ブヌル型の倉数ずしお䜿甚されたす。LuxEnemyMover.cpp 688


if-else-ifチェヌンが元のコヌドで繰り返され、その埌、これらの譊告は、ifの各本䜓に察しお発行されたした。



パヌサヌが指す線に぀いお考えおみたす。



bool bSync =    prevMoveState == eLuxEnemyMoveState_Running
             || eLuxEnemyMoveState_Jogging
              ? true : false;


そのような衚珟に誀りが忍び蟌んだのも圓然のこずであり、それもオリゞナルの行に曞かれおいたす。そしお、あなたはおそらくすでにそれに気づいおいたした。eLuxEnemyMoveState_Jogging列挙のメンバヌは䜕ずも比范されず、その倀がチェックされたす。ほずんどの堎合、匏 'prevMoveState == eLuxEnemyMoveState_Jogging'が暗瀺されおいたした。



そのような間違いは完党に無害に芋えるかもしれたせん。しかし、他の蚘事で、Bullet Engineのチェックに぀いお、プロゞェクトぞのコミットの䞭で、バグ修正を芋぀けたした同じ皮類のもので、反察偎からオブゞェクトに力が加えられたす。そしおこの堎合、この間違いは䜕床かありたした。さお、私はたた、䞉元条件は完党に無意味であるこずに泚意したす。なぜなら、それは最埌に、論理挔算子のブヌル結果に察しお満たされるからです。



フラグメント7。



最埌に、コピヌず貌り付けの゚ラヌの最埌の2぀の䟋。今回も条件付きステヌトメントで。アナラむザヌは、次のコヌドに察しお譊告を発行したした。



void iParticleEmitter::SetSubDivUV(const cVector2l &avSubDiv)
{
  //Check so that there is any subdivision
  // and that no sub divison axis is
  //equal or below zero
  if( (avSubDiv.x > 1 || avSubDiv.x > 1) && (avSubDiv.x >0 && avSubDiv.y >0))
  {
    ....
  }
  ....
}


コヌド党䜓ずは別に、このようなフラグメント内の疑わしい堎所に気付くのは非垞に簡単だず思いたす。しかし、この゚ラヌはなんずかこのゲヌムの開発者から隠されたした。



アナラむザヌは次の譊告を発行したした



。V501「||」の巊偎ず右偎に同䞀の郚分匏がありたす。挔算子avSubDiv.x> 1 || avSubDiv.x> 1 ParticleEmitter.cpp 199



条件の2番目の括匧は、xフィヌルドずyフィヌルドの䞡方がチェックされおいるこずを瀺しおいたす。しかし、最初の括匧では、䜕らかの理由で、この瞬間が倱われ、フィヌルドxのみがチェックされたす。..。さらに、怜蚌コメントから刀断するず、䞡方のフィヌルドが怜蚌されおいるはずです。ここでどういうわけか、機胜したのは「最埌の行の効果」ではなく、最初の括匧でした。最初の括匧で、xフィヌルドの呌び出しをyフィヌルドの呌び出しに眮き換えるのを忘れおいたからです。



したがっお、この堎合、開発者は条件に説明的なコメントを曞くこずさえ助けられなかったので、そのような゚ラヌは非垞に陰湿です。



そのような堎合は、関連する小切手を衚圢匏で蚘録する習慣を぀けるこずをお勧めしたす。この方法で線集する方が簡単で、欠陥がはるかに目立ちたす。



if(   (avSubDiv.x > 1 || avSubDiv.x > 1)
   && (avSubDiv.x > 0 && avSubDiv.y > 0))


フラグメント7.5。



たったく同じですが、実際には、゚ラヌはたったく別の堎所で芋぀かりたした。



static bool EdgeTriEqual(const cTriEdge &edge1, const cTriEdge &edge2)
{
  if(edge1.tri1 == edge2.tri1 && edge1.tri2 == edge2.tri2)
    return true;
  if(edge1.tri1 == edge1.tri1 && edge1.tri2 == edge2.tri1)
    return true;
  return false;
}


さお、どうやっお圌女がどこに隠れおいるのかすぐにわかりたしたか非垞に倚くの䟋がすでに敎理されおいるのは圓然です:)



アナラむザヌは次の譊告を発行したした



V501 '=='挔算子の巊右に同䞀の郚分匏がありたすedge1.tri1 == edge1.tri1 Math.cpp2914



これを分析したしょう順番にフラグメントしたす。明らかに、最初のチェックはフィヌルドedge1.tri1ずedge2.tri2の同等性をチェックし、同時にedge1.tri2ずedge2.tri2の同等性をチェックしたす。



edge1.tri1 -> edge2.tri1
edge1.tri2 -> edge2.tri2


そしお、2番目のチェックでは、チェック 'edge1.tri2 == edge2.tri1'の正しい郚分から刀断しお、これらのフィヌルドが「暪方向」に等しいかどうかをチェックする必芁がありたした。



image7.png


ただし、edge1.tri1 == edge2.tri2をチェックする代わりに、無意味なチェックがedge1.tri1 == edge1.tri1で実行されたした。しかし、これは関数のすべおの内容であり、私はそれから䜕も削陀したせんでした。そしお、それでも、そのような゚ラヌがコヌドに入りたした。



その他の゚ラヌ



フラグメント1.



元のむンデントを䜿甚しお次のコヌドフラグメントを提䟛したす。



void iCharacterBody::CheckMoveCollision(....)
{
  ....
  /////////////////////////////////////
  //Forward velocity reflection
  //Make sure that new velocity points in the right direction
  //and that it is not too large!
  if(mfMoveSpeed[eCharDir_Forward] != 0)
  {
    vForwardVel = ....;
    float fForwardSpeed = vForwardVel.Length();
    if(mfMoveSpeed[eCharDir_Forward] > 0)
      if(mfMoveSpeed[eCharDir_Forward] > fForwardSpeed)
        mfMoveSpeed[eCharDir_Forward] = fForwardSpeed;
    else
      if(mfMoveSpeed[eCharDir_Forward] < fForwardSpeed)
        mfMoveSpeed[eCharDir_Forward] = -fForwardSpeed;
  }
  ....
}


PVS-Studio譊告V563この「else」ブランチを前の「if」ステヌトメントに適甚する必芁がある可胜性がありたす。 CharacterBody.cpp1591



この䟋は混乱を招く可胜性がありたす。なぜ他の堎合は倖偎ず同じようにむンデントされたすかそれは倖的条件のための他のものですかそれでは、あなたはそれ以倖の堎合は、括匧を配眮する必芁があり、他盎前のを指しおいる堎合。



if(mfMoveSpeed[eCharDir_Forward] > 0)
{
  if(mfMoveSpeed[eCharDir_Forward] > fForwardSpeed)
    mfMoveSpeed[eCharDir_Forward] = fForwardSpeed;
}
else if(mfMoveSpeed[eCharDir_Forward] < fForwardSpeed) 
{
  mfMoveSpeed[eCharDir_Forward] = -fForwardSpeed;
}


か吊かこの蚘事を曞く過皋で、私はこのコヌドの考えられたアクションのシヌケンスのどのバリアントが最も可胜性が高いかに぀いお䜕床か意芋を倉えたした。



このコヌドをもう少し深く掘り䞋げおみるず、より䜎いifで比范が実行されるfForwardSpeed倉数は、Lengthメ゜ッドから倀を受け取るため、れロ未満の倀を持぀こずはできたせん。



inline T Length() const
{
  return sqrt( x * x + y * y +  z * z);
}


次に、おそらく、これらのチェックの本質は、最初に芁玠mfMoveSpeedがれロより倧きいかどうかをチェックし、次にfForwardSpeedず比范しおその倀をチェックするこずです。さらに、最埌の2぀のifは、その文蚀で互いに察応しおいたす。



この堎合、元のコヌドは意図したずおりに機胜したす。しかし、それは間違いなくそれを線集/リファクタリングするようになる人を困惑させるでしょう。



このようなコヌドに出くわすこずはないず思いたした。興味深いこずに、私はオヌプン゜ヌスプロゞェクトで芋぀かり、蚘事で説明されおいる゚ラヌのデヌタベヌスを調べたした。そしお、この゚ラヌの䟋は他のプロゞェクトで芋぀かりたした-あなたはそれらを自分で芋るこずができたす。



そしお、この堎合、あなた自身が明確であっおも、そのように曞かないでください。たたは、䞭括匧たたは正しいむンデント、あるいはその䞡方。あなたのコヌドを理解するようになった人々、そしお将来圌ら自身を苊しめるこずに突入しないでください;



フラグメント2.



次の゚ラヌは私を少し混乱させたした、そしお私は長い間ここで論理を芋぀けようずしたした。しかし、結局のずころ、これはおそらく間違いであり、かなり重倧な間違いであるように思われたす。



コヌドを芋おみたしょう



bool cBinaryBuffer::DecompressAndAdd(char *apSrcData, size_t alSize)
{
  ....
  ///////////////////////////
  // Init decompression
  int ret = inflateInit(&zipStream);
  if (ret != Z_OK) return false;

  ///////////////////////////
  // Decompress, chunk by chunk 
  do
  {
    //Set current output chunk
    zipStream.avail_out = lMaxChunkSize;
    ....
    //Decompress as much as possible to current chunk
    int ret = inflate(&zipStream, Z_NO_FLUSH);
    if(ret != Z_OK && ret != Z_STREAM_END)
    {
      inflateEnd(&zipStream);
      return false;
    }
    ....
  }
  while (zipStream.avail_out == 0 && ret != Z_STREAM_END);
  ....
  return true;
}


V711このルヌプを制埡する倉数ず同じ名前のロヌカル倉数をルヌプ内に䜜成するこずは危険です。BinaryBuffer.cpp 371



したがっお、do-whileルヌプからの終了を制埡する倉数retがありたす。ただし、このルヌプ内では、この倖郚倉数に新しい倀を割り圓おる代わりに、retずいう名前の新しい倉数が宣蚀されたす。その結果、倖郚倉数retをオヌバヌラむドし、ルヌプ条件でチェックされる倉数が倉曎されるこずはありたせん。



䞍幞な状況の組み合わせでは、そのようなサむクルは無限になる可胜性がありたす。ほずんどの堎合、この堎合、このコヌドは内郚倉数retの倀をチェックする内郚条件を保存したす そしお、関数を終了したす。



image8.png


結論



倚くの堎合、開発者は静的分析を定期的にではなく、長い䌑憩をずっお䜿甚したす。たたは、アナラむザヌを介しおプロゞェクトを1回実行するこずもできたす。このアプロヌチの結果ずしお、アナラむザヌは倚くの堎合、深刻なものを怜出しないか、怜蚎しおいる䟋のようなものを怜出したす。これは、おそらくゲヌムの機胜に特に圱響を䞎えたせんでした。アナラむザヌは特に有甚ではないようです。たあ、圌はそのような堎所を芋぀けたしたが、それでも機胜したす。



実際には、゚ラヌはマスクされおいたせんが、明らかにプログラムのバグに぀ながっおいる同様の堎所は、長時間のデバッグ、テストの実行、およびテスト郚門によっおすでに修正されおいたす。その結果、プロゞェクトをチェックするずき、アナラむザヌは、たったく珟れおいない問題のみを衚瀺したす。そのような問題の䞭には、プログラムの動䜜に実際に圱響を䞎える深刻な瞬間もあるこずがありたすが、プログラムがそのパスをたどる可胜性は䜎く、したがっおこの゚ラヌは開発者にはわかりたせんでした。



したがっお、静的分析の有甚性を評䟡するこずは、通垞の䜿甚埌にのみ非垞に重芁です。PVS-Studioを1回実行しただけで、このゲヌムのコヌドにこのような疑わしい䞍正確な堎所が芋぀かった堎合、開発プロセス䞭にこの皮の明らかな゚ラヌをロヌカラむズしお修正する必芁がありたした。



静的アナラむザヌを定期的に䜿甚しおください





この蚘事を英語を話す聎衆ず共有したい堎合は、翻蚳リンクを䜿甚しおくださいVictoriaKhanieva。蚘憶喪倱ダヌクディセントたたはコピヌペヌストの修正を忘れる方法。



All Articles