粉末玩具シミュレーターの確認

Powder Toyは、空気、熱、重力、さまざまな物質間の無数の相互作用の圧力と速度をシミュレートする無料の物理学を備えたサンドボックスです。 このゲームは、複雑な車、武器、爆弾、現実的な地形などを構築するために使用できるさまざまな建築材料、液体、ガス、電子部品を提供します。 作成された数千の異なる建物を表示および再生できます。 しかし、ゲームはそれほど素晴らしいものではないことが判明しました。350ファイルまでの小さなプロジェクトでは、非常に多くの静的アナライザー警告が受信されました。 この記事では、最も興味深い場所について説明します。



Powder Toyは 、PVS-Studio 5.20を使用してテストされました。 このプロジェクトは、Pythonスクリプトを使用してmsysのWindows上でビルドされるため、検証に特別なユーティリティPVS-Studio Standaloneを使用しました 簡単ですぐに使用できます



検証結果



V501 「||」の左と右に同一のサブ式があります 演算子:!s [1] ||!s [2] ||!s [1] graphics.cpp 829

void Graphics::textsize(const char* s, int& width, int& height) { .... else if (*s == '\x0F') { if(!s[1] || !s[2] || !s[1]) break; //<== s+=3; //<== } .... }
      
      





特定の条件下では、文字配列の3つの連続する要素がチェックされますが、タイプミスにより要素s [3]がチェックされなかったため、状況によってはプログラムが正しく動作しない可能性があります。



V523 「then」ステートメントは「else」ステートメントと同等です。 button.cpp 142

 void Button::Draw(const Point& screenPos) { .... if(Enabled) if(isButtonDown || (isTogglable && toggle)) { g->draw_icon(Position.X+iconPosition.X, Position.Y+iconPosition.Y, Appearance.icon, 255, iconInvert); } else { g->draw_icon(Position.X+iconPosition.X, Position.Y+iconPosition.Y, Appearance.icon, 255, iconInvert); } else g->draw_icon(Position.X+iconPosition.X, Position.Y+iconPosition.Y, Appearance.icon, 180, iconInvert); .... }
      
      





疑わしく同一のコードを持つ関数の断片。 条件式には多数の論理演算が含まれているため、このコードフラグメントには無駄なチェックが含まれていないと想定できますが、draw_icon()関数の最後から2番目のパラメーターで入力ミスが許可されます。 つまり どこかに値「255」を書き込まないでください。



同様の場所:



V530関数 'empty'の戻り値を使用する必要があります。 requestbroker.cpp 309

 std::vector<Request*> Children; RequestBroker::Request::~Request() { std::vector<Request*>::iterator iter = Children.begin(); while(iter != Children.end()) { delete (*iter); iter++; } Children.empty(); //<== }
      
      





ベクトルをクリアする代わりに、関数 'empty()'が呼び出されましたが、これはベクトルを変更しません。 コードはデストラクタ内にあるため、おそらくエラーはプログラムの実行に影響しません。 しかし、それにもかかわらず、私はこの瞬間を表明することにしました。



V547式 'partsData [i]> = 256'は常にfalseです。 符号なしchar型の値の範囲:[0、255]。 gamesave.cpp 816:



 #define PT_DMND 28 //#define PT_NUM 161 #define PT_NUM 256 unsigned char *partsData = NULL, void GameSave::readOPS(char * data, int dataLength) { .... if(partsData[i] >= PT_NUM) partsData[i] = PT_DMND; //Replace all invalid elements.... .... }
      
      





コードには、作成者にのみ明らかな疑わしい場所が含まれています。 以前は、 'partsData'配列のi番目の要素が161以上の場合、値28が要素に書き込まれましたが、定数161がコメント化され、256に置き換えられました。その結果、条件は満たされません。 'unsigned char'の最大値:255。



V547式は常に偽です。 おそらく '||' ここで演算子を使用する必要があります。 previewview.cpp 449:



 void PreviewView::NotifySaveChanged(PreviewModel * sender) { .... if(savePreview && savePreview->Buffer && !(savePreview->Width == XRES/2 && //<== savePreview->Width == YRES/2)) //<== { pixel * oldData = savePreview->Buffer; float factorX = ((float)XRES/2)/((float)savePreview->Width); float factorY = ((float)YRES/2)/((float)savePreview->Height); float scaleFactor = factorY < factorX ? factorY : factorX; savePreview->Buffer = Graphics::resample_img(....); delete[] oldData; savePreview->Width *= scaleFactor; savePreview->Height *= scaleFactor; } .... }
      
      





運のおかげで、条件の一部は常に真実です。 ここにタイプミスの可能性が高い:おそらく '||'演算子を使用する必要がある 「&&」の代わりに、またはある場合には、例えば「savePreview-> Height」をチェックする必要があります。



V560条件式の一部は常に真です:0x00002。 frzw.cpp 34

 unsigned int Properties; Element_FRZW::Element_FRZW() { .... Properties = TYPE_LIQUID||PROP_LIFE_DEC; .... }
      
      





変数「Properties」を持つコード全体で、ビットごとの演算が実行されますが、2つの場所で「||」が使用されます 「|」の代わりに。 値1がプロパティに書き込まれることがわかります。



2番目のそのような場所:

V567未定義の動作。 'sandcolour_frame'変数は、シーケンスポイント間で2回使用されている間に変更されます。 Simulation.cpp 4744

 void Simulation::update_particles() { .... sandcolour_frame = (sandcolour_frame++)%360; .... }
      
      





変数 'sandcolour_frame'は、シーケンスの同じポイントで2回使用されます。 その結果、そのような式の結果を予測することは不可能です。 詳しくは、 V567診断の説明をご覧ください



V570 「parts [i] .colour」変数はそれ自体に割り当てられます。 fwrk.cpp 82

 int Element_FWRK::update(UPDATE_FUNC_ARGS) { .... parts[i].life=rand()%10+18; parts[i].ctype=0; parts[i].vx -= gx*multiplier; parts[i].vy -= gy*multiplier; parts[i].dcolour = parts[i].dcolour; //<== .... }
      
      





固有値による疑わしいフィールドの初期化。



V576形式が正しくありません 。 'printf'関数の3番目の実引数を確認することを検討してください。 ポインターの値を出力するには、「%p」を使用する必要があります。 powdertoysdl.cpp 3247

 int SDLOpen() { .... SDL_SysWMinfo SysInfo; SDL_VERSION(&SysInfo.version); if(SDL_GetWMInfo(&SysInfo) <= 0) { printf("%s : %d\n", SDL_GetError(), SysInfo.window); exit(-1); } .... }
      
      





ポインターを印刷するには、%p指定子を使用します。



V595 nullptrに対して検証される前に、「gameSave」ポインターが使用されました。 行を確認してください:1063、1070。gamecontroller.cpp 1063

 void GameController::OpenLocalSaveWindow(bool asCurrent) { Simulation * sim = gameModel->GetSimulation(); GameSave * gameSave = sim->Save(); //<== gameSave->paused = gameModel->GetPaused(); gameSave->gravityMode = sim->gravityMode; gameSave->airMode = sim->air->airMode; gameSave->legacyEnable = sim->legacy_enable; gameSave->waterEEnabled = sim->water_equal_test; gameSave->gravityEnable = sim->grav->ngrav_enable; gameSave->aheatEnable = sim->aheat_enable; if(!gameSave) //<== { new ErrorMessage("Error", "Unable to build save."); } .... }
      
      





最初に「gameSave」ポインタのゼロをチェックしてから、フィールドに入力する方が論理的です。



さらにいくつかのそのような場所:

V611メモリーは「new T []」演算子を使用して割り当てられましたが、「delete」演算子を使用して解放されました。 このコードを調べることを検討してください。 「delete [] userSession;」を使用することをお勧めします。 apirequest.cpp 106

 RequestBroker::ProcessResponse APIRequest::Process(RequestBroker & rb) { .... if(Client::Ref().GetAuthUser().ID) { User user = Client::Ref().GetAuthUser(); char userName[12]; char *userSession = new char[user.SessionID.length() + 1]; .... delete userSession; //<== } .... }
      
      





演算子new、new []、delete、delete []は、対応するペアで使用する必要があります。 ここで正しいでしょう: "delete [] userSession;"。



そして、この場所だけではありません:

V614未初期化ポインター 'ndata'が使用されました。 Simulation.cpp 1688

 void *Simulation::transform_save(....) { void *ndata; .... //ndata = build_save(....); //TODO: IMPLEMENT .... return ndata; }
      
      





この場所が計画的に完了する前に、関数は初期化されていないポインターを返します。



同様の場所:

おわりに



Powder Toyは、ゲーム、トレーニング、実験に使用できる興味深いクロスプラットフォームプロジェクトです。 プロジェクトの規模は小さいにも関わらず、それを理解することは興味深いものでした。 著者がソースコードの分析に注意を払い、完全な検証ログを分析することを願っています。



静的分析を定期的に使用すると、より便利なタスクやTODOを解決するのに多くの時間を節約できます。



この記事は英語です。



英語を話す聴衆とこの記事を共有したい場合は、翻訳へのリンクを使用してください:Svyatoslav Razmyslov。 The Powder Toy Simulatorの分析



記事を読んで質問がありますか?
多くの場合、記事には同じ質問が寄せられます。 ここでそれらに対する回答を収集しました: PVS-StudioおよびCppCatバージョン2014に関する記事の読者からの質問への回答 。 リストをご覧ください。




All Articles