2016年のC ++オープンプロジェクトのトップ10の間違い















第89回アカデミー賞は世界中で議論されており、俳優とその衣装のさまざまな評価がまとめられていますが、IT分野のレビュー記事を準備することにしました。 2016年にオープンソースプロジェクトで犯された最も興味深い間違いについてです。 今年は、PVS-StudioアナライザーがLinuxベースのオペレーティングシステムで利用可能になったことで注目に値します。 表示されるエラーはおそらく既に修正されており、各読者は開発者が作成するエラーの深刻さを確信できます。



それでは、 PVS-Studioアナライザーが2016年に発見した興味深いエラーを見てみましょう。 コードに加えて、エラーが特定された診断と、このエラーが最初に説明された記事が提供されます。



セクションは、バグの美しさについての私の考えに従ってソートされています:)。



10位



ソース: PVS-Studioアナライザーを使用してGCCコンパイラコードのエラーを検索



V519 「bb_copy」変数には、連続して2回値が割り当てられます。 おそらくこれは間違いです。 行を確認してください:1076、1078。cfg.c 1078



void free_original_copy_tables (void) { gcc_assert (original_copy_bb_pool); delete bb_copy; bb_copy = NULL; // <= delete bb_original; // <= bb_copy = NULL; // <= delete loop_copy; loop_copy = NULL; delete original_copy_bb_pool; original_copy_bb_pool = NULL; }
      
      





bb_copyポインターは誤って2回無効化され、 bb_originalポインターは未設定のままになります。



9位



出典: 待望のCryEngine Vテスト



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; }
      
      





記事の公開後すぐに修正された不幸なタイプミス。 ちなみに、このコードは、プロジェクトのさまざまな場所でエラーとともに数回コピーされました。 彼らはまた、アナライザーを見つけました。



8位



出典: GDBは困難なナットであることが判明



V579 read_memory関数は、ポインターとそのサイズを引数として受け取ります。 間違いかもしれません。 3番目の引数を調べます。 jv-valprint.c 111



 extern void read_memory (CORE_ADDR memaddr, gdb_byte *myaddr, ssize_t len); void java_value_print (....) { .... gdb_byte *buf; buf = ((gdb_byte *) alloca (gdbarch_ptr_bit (gdbarch) / HOST_CHAR_BIT)); .... read_memory (address, buf, sizeof (buf)); .... }
      
      





sizeof(buf)演算子は、バッファーのサイズではなく、ポインターのサイズを計算します。 その結果、不十分なデータバイトがメモリから取得されます。



7位



出典: PVS-Studioチームは技術的なブレークスルーを準備していますが、今のところ、Blenderを再確認しましょう



V522 NULLポインター「ve」の参照が行われる場合があります。 functions1d.cpp 107



 int QuantitativeInvisibilityF1D::operator()(....) { ViewEdge *ve = dynamic_cast<ViewEdge*>(&inter); if (ve) { result = ve->qi(); return 0; } FEdge *fe = dynamic_cast<FEdge*>(&inter); if (fe) { result = ve->qi(); // <= return 0; } .... }
      
      





ここで、名前のタイプミスは、より深刻な間違いにつながりました。 どうやら、2番目のコードフラグメントはCopy-Pasteを使用して作成されたようです。 そして偶然にも、変数名vefeに変更するのを忘れていました。 その結果、未定義のプログラムの動作が発生し、たとえばプログラムがクラッシュする可能性があります。



6位



ソース: Bad Toonz 2Dアニメーションパッケージコード



V546クラスのメンバーはそれ自体で初期化されます: 'm_subId(m_subId)'。 tfarmcontroller.cpp 572



 class TaskId { int m_id; int m_subId; public: TaskId(int id, int subId = -1) : m_id(id), m_subId(m_subId){};
      
      





クラス初期化リストの興味深いエラー。 m_subldフィールドそれ自体で初期化されますが、おそらくm_subId(subId)を書きたいと思うでしょう。



5位



出典: PVS-StudioによるCERNの救助:Geant4プロジェクトの検証



V603オブジェクトは作成されましたが、使用されていません。 コンストラクターを呼び出す場合は、「this-> G4PhysicsModelCatalog :: G4PhysicsModelCatalog(....)」を使用する必要があります。 g4physicsmodelcatalog.cc 51



 class G4PhysicsModelCatalog { private: .... G4PhysicsModelCatalog(); .... static modelCatalog* catalog; .... }; G4PhysicsModelCatalog::G4PhysicsModelCatalog() { if(!catalog) { static modelCatalog catal; catalog = &catal; } } G4int G4PhysicsModelCatalog::Register(const G4String& name) { G4PhysicsModelCatalog(); .... }
      
      





まれな間違いですが、一部のプログラマーは、このようなコンストラクターの呼び出しがクラスフィールドを初期化するとまだ考えています。 現在のオブジェクトにアクセスする代わりに、新しい一時オブジェクトが作成され、すぐに破棄されます。 その結果、オブジェクトフィールドは初期化されません。 コンストラクタの外部でフィールドの初期化を使用する必要がある場合は、このために別の関数を作成して呼び出すことをお勧めします。



4位



出典: カサブランカ:可能性のあるユニコーン



V554 shared_ptrの誤った使用。 「new []」で割り当てられたメモリは、「delete」を使用して消去されます。 BlackJack_Server140 table.cpp 471



 void DealerTable::FillShoe(size_t decks) { std::shared_ptr<int> ss(new int[decks * 52]); .... }
      
      





デフォルトでは、オブジェクトを破壊するshared_ptr型のスマートポインターは、角括弧[]なしで削除演算子を呼び出します。 この場合、これは間違っています。



コードの正しいバージョンは次のようになります。



 std::shared_ptr<int> ss(new int[decks * 52], std::default_delete<int[]>());
      
      





3位



ソース: Serious Samシューティングゲームの記念日のためのSerious Engine v.1.10ゲームエンジンのソースコードの確認



V541文字列「achrDefaultScript」をそれ自体に印刷することは危険です。 dlgcreateanimatedtexture.cpp 359



 BOOL CDlgCreateAnimatedTexture::OnInitDialog() { .... // allocate 16k for script char achrDefaultScript[ 16384]; // default script into edit control sprintf( achrDefaultScript, ....); // <= .... // add finishing part of script sprintf( achrDefaultScript, // <= "%sANIM_END\r\nEND\r\n", // <= achrDefaultScript); // <= .... }
      
      





ここでは、バッファ内に特定の行を形成します。 次に、新しい行を取得し、その行の以前の値を保持し、さらに2つの単語を追加したいと考えています。 すべてがシンプルなようです。



ここで予期しない結果が発生する理由を説明するために、V541診断ドキュメントから簡単な例を引用します。



 char s[100] = "test"; sprintf(s, "N = %d, S = %s", 123, s);
      
      





このコードの結果として、次の行を取得します。



 N = 123, S = test
      
      





しかし、実際には、バッファー内に行が形成されます。



 N = 123, S = N = 123, S =
      
      





sprintf関数の実装に依存するため、このケースで何が起こるかを予測することは困難です。 プログラマーが期待どおりにコードが機能する可能性があります。 または、間違ったオプションを取得するか、プログラムをクラッシュさせることができます。 新しいバッファを使用して結果を保存する場合、コードを修正できます。



2位



出典: FreeBSDカーネルに詰め込まれたPVS-Studio



V733マクロ展開の結果、評価順序が誤っている可能性があります。 式を確認:chan-1 * 20. isp.c 2301



 static void isp_fibre_init_2400(ispsoftc_t *isp) .... if (ISP_CAP_VP0(isp)) off += ICB2400_VPINFO_PORT_OFF(chan); else off += ICB2400_VPINFO_PORT_OFF(chan - 1); // <= .... }
      
      





一見したところ、このコードについて疑わしいものはありません。 値 'chan'が使用されることもあれば、値が1つ少なくなることもあります: 'chan-1'ですが、マクロの定義を見てみましょう。



 #define ICB2400_VPOPT_WRITE_SIZE 20 #define ICB2400_VPINFO_PORT_OFF(chan) \ (ICB2400_VPINFO_OFF + \ sizeof (isp_icb_2400_vpinfo_t) + \ (chan * ICB2400_VPOPT_WRITE_SIZE)) // <=
      
      





バイナリ式がマクロに送信されると、そこで計算のロジックが劇的に変わります。 申し立てられた表現「(chan-1)* 20」は「chan-1 * 20」になります。 「chan-20」で、プログラムは誤って計算されたサイズを使用します。



残念ながら、このエラーはまだ修正されていません。 おそらく、開発者はこの記事に気づかなかったか、修正する時間を見つけられなかったかもしれませんが、コードはまだ誤っているように見えます。 したがって、FreeBSDはバグ評価のほぼ1位です。



一位



ソース: Oracle VM VirtualBoxコードの見直し



V547式は常に偽です。 符号なしの型の値が<0になることはありません。dt_subr.c715



 #define vsnprintf RTStrPrintfV int dt_printf(dtrace_hdl_t *dtp, FILE *fp, const char *format, ...) { .... if (vsnprintf(&dtp->dt_buffered_buf[dtp->dt_buffered_offs], // <= avail, format, ap) < 0) { rval = dt_set_errno(dtp, errno); va_end(ap); return (rval); } .... }
      
      





2016 VirtualBoxプロジェクトで最も興味深いエラーのランキングのトップ。 PVS-Studioを使用して繰り返しテストされ、多くのエラーが検出されました。 しかし、このエラーはコードの作成者だけでなく、アナライザーの開発者でさえ、このコードの何が問題で、PVS-Studioが奇妙な警告を発行したのかを長い間掘り下げました。



Windows用にコンパイルされたコードでは、関数の置換がありました。 新しい関数は符号なしの型の値を返し、ほとんど目に見えないエラーをコードに追加しました。 使用される関数のプロトタイプは次のようになります。



 size_t RTStrPrintfV(char *, size_t, const char *, va_list args); int vsnprintf (char *, size_t, const char *, va_list arg );
      
      





おわりに



結論として、私は多くの熱烈なコメントを受け取った記事に最も人気のある写真を持ち込みたいと思います。 記事「 PVS-Studioを使用したOpenJDKプロジェクトのテスト 」の画像:



















これで、WindowsまたはLinux用のGitHubを使用し検証用のプロジェクトを提供できるようになりました。これにより、オープンソースプロジェクトでさらに多くのエラーを検出し、改善することができます。



このリンクからPVS-Studioをダウンロードして試すことができます。



ライセンスオプション、価格、割引オプションについては、 サポートにお問い合わせください。



すべてのコードレスコードをお祈りします!







英語を話す聴衆とこの記事を共有したい場合は、翻訳へのリンクを使用してください:Svyatoslav Razmyslov。 2016年にチェックされたC ++オープンソースプロジェクトの上位10個のバグ



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



All Articles