PVS-Studio の Linux バージョンは、CodeLite をチェックせずにはいられませんでした

読者の皆様にはすでにご存じのとおり、PVS-Studio スタティック アナライザーは新しい開発の方向性を探っています。それは Linux プラットフォームです。以前の記事からお気づきかもしれませんが、順調に進んでいます。この記事では、Linux 版のアナライザーを使用してプロジェクトを簡単にチェックできることを示します。Linux 用の PVS-Studio は単純であるほど、より多くの支持者が得られるからです。今回は CodeLite プロジェクトを選択しました。 CodeLite は Linux でコンパイルおよびテストされました。得られた結果を見てみましょう。

プロジェクトについて

CodeLite は無料のオープン ソースのクロス プラットフォーム C、C++、PHP、および Node.js IDE であり、wxWidgets ツールキットを使用します。オープン ソース ソフトウェアの精神に準拠するために、CodeLite は無料のツール (MinGW および GDB) のみを使用してコンパイルおよびデバッグされます。

CodeLite の機能:プロジェクト管理、コード補完 (ctags + clang)、コード リファクタリング、構文強調表示、Subversion および Git への統合、Cscope 統合、UnitTest++ 統合、GDB 上に構築された対話型デバッガー、強力なソース コード エディター (Scintilla ベース) .

Codelite は、GNU General Public License v2 以降の下で配布されます。無料です。 Codelite は十分に開発およびデバッグされており、開発プラットフォームとして使用できます。

CodeLite の最新バージョンは、PHP と Node.js のプロジェクトもサポートしています。

CodeLite のソース コードは GitHub で入手できます

分析結果

チェックを行うために、Linux 用の PVS-Studio を使用しました。ワークフローについて簡単に説明します。

作業を開始する前に、Linux 用の PVS-Studio の実行と使用に関する説明を読みました。アナライザーは 2 つの方法で使用できます:ビルド システムに統合する (最良の方法と見なされる) か、ユーティリティ pvs-studio-analyzer として使用します。チェックをすばやく実行してエラーの分析を開始するために、2 番目の方法を使用することにしました。

では、どうぞ。

まず、プロジェクトのソースコードをダウンロードしました。

次に、単純な構成ファイル (PVS-Studio.cfg) を作成し、次のように記述しました。

exclude-path = /usr/include/
lic-file = /path/to/PVS-Studio.lic
output-file = /path/to/PVS-Studio.log

CodeLite は cmake プロジェクトであるため、アナライザーでさらに作業するために必要なフラグを使用してビルドするために cmake ユーティリティを使用しました。

$ mkdir codelite/build
$ cd build
$ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=On ../

プロジェクトが正常に構築された後、分析を開始しました:

$ pvs-studio-analyzer analyze --cfg /path/to/PVS-Studio.cfg -j4

その結果、PVS-Studio.cfg で指定されたパスからファイル PVS-Studio.log を取得しました。そこから有用な情報を得るために、PVS-Studio 配布キットの一部である plog-converter ユーティリティを使用しました。

アナライザー レポートを表示するために、次の方法で plog-converter を実行しました。

$ plog-converter -a GA:1,2 -t tasklist -o /path/to/codelite.tasks 
/path/to/PVS-Studio.log

このコマンドの後、指定されたディレクトリに codelite.tasks を取得しました。これを Qt Creator で開きました。

ポインタ処理

警告 V595 'pResult' ポインターは、nullptr に対して検証される前に使用されました。チェック行:522, 526.SqliteDatabaseLayer.cpp 522

bool CodeBlocksImporter::isSupportedWorkspace()
{
  ....
  wxXmlNode* root = codeBlocksProject.GetRoot();
  wxString nodeName = root->GetName();                // <=
  
  if(root &&                                          // <=
    (nodeName == wxT("CodeBlocks_workspace_file") || 
     nodeName == wxT("CodeBlocks_project_file")))
      return true;
  }
  return false;
}

上記のコードで、アナライザーは root の潜在的な逆参照に関連するバグを検出しました。 ポインター。ポインターが null になることは決してない可能性があり、プログラマーはこれを確信していますが、なぜ再び null に対して検証するのでしょうか?これは、コードを読むときに混乱を招くだけです。私の意見では、これは本当のバグであり、コードを変更する必要があります.

同様のアナライザー警告:

  • V595 'pResult' ポインターは、nullptr に対して検証される前に使用されました。チェック行:522, 526.SqliteDatabaseLayer.cpp 522
  • V595 'ms_instance' ポインターは、nullptr に対して検証される前に使用されました。チェック行:24, 25. php_parser_thread.cpp 24

警告 V512 「memset」関数を呼び出すと、バッファー「EndTimestampListHandles」のアンダーフローが発生します。 md5.cpp 243

class MD5
{
  ....
  // assumes char is 1 word long
  typedef unsigned      char uint1; 
  // next, the private data:
  ....
  uint1 buffer[64];   // input buffer
  ....
  static void memset(uint1 *start, uint1 val, uint4 length);
  ....
};

void MD5::finalize ()
{
  ....
  // Zeroize sensitive information
  memset (buffer, 0, sizeof(*buffer));        // <=
  finalized=1;
}

このバグは、memset に渡される 3 番目の引数の値が正しくないことに関連しています。 関数。 sizeof(*buffer) 演算子は、バッファーの実際のサイズではなく、エラーである最初の要素のサイズを返します。この特定の例では、1 バイトのみが memset に渡されます。 64 の代わりに。

注意 ここでは、プログラマーが「カスタム」memset を使用していることに注意してください。 関数。アナライザーは、それが正しく使用されていないことをどのように認識しますか?この関数と他のいくつかの関数の名前は基本的に似ているため、同じように使用されます。そのため、この関数やその他の関数については、アナライザーは、それらが宣言されている名前空間またはクラスを認識しません。最も重要なことは、引数の数と型が一致することです。ご覧のとおり、そのようなアクションはエラーを見つけるのに役立ちます。

警告 V668 メモリーは「new」演算子を使用して割り当てられているため、null に対して「buffer」ポインターをテストしても意味がありません。メモリ割り当てエラーの場合、例外が発生します。 ShapeDataObject.cpp 65

wxString wxSFShapeDataObject::SerializeSelectedShapes(....)
{
  ....
  char *buffer = new char [outstream.GetSize()];

  if(buffer)        // <=
  {
    memset(buffer, 0, outstream.GetSize());
    outstream.CopyTo(buffer, outstream.GetSize()-1);
    wxString output(buffer, wxConvUTF8);
    delete [] buffer;
    return output;
  }
  else
    return wxT(....);
}

ここでは、無意味なポインタ検証があります。 C++ 言語標準によると、new を介してメモリを割り当てる場合、null に対してポインタを検証しても意味がありません。例外 std::bad_alloc() が発生する可能性があるためです。 メモリの割り当てに失敗した場合にスローされます。このような場合、try... catch を使用する必要があります。 ブロックして、これらの重大な状況を処理します。例外の使用を避けたい場合は、new があります 例外をスローしません。例:

char *buffer = new char (std::nothrow) [outstream.GetSize()];

もちろん、try..catch を使用して または std::nothrow これらは適切な解決策の例ではなく、ここでは迅速かつ大まかな修正のバリエーションとしてのみ提供されています。

他にも同様の状況がいくつか見つかりました (ここではメッセージの一部のみを示します。合計 19 です):

  • V668 メモリは「new」演算子を使用して割り当てられているため、null に対して「pResultSet」ポインタをテストしても意味がありません。メモリ割り当てエラーの場合、例外が生成されます。 SqliteDatabaseLayer.cpp 199
  • V668 'pReturnStatement' ポインタを null に対してテストしても意味がありません。メモリは 'new' 演算子を使用して割り当てられたからです。メモリ割り当てエラーの場合、例外が生成されます。 SqliteDatabaseLayer.cpp 223
  • V668 メモリは「new」演算子を使用して割り当てられているため、null に対して「m_proc」ポインタをテストしても意味がありません。メモリ割り当てエラーの場合、例外が生成されます。 async_executable_cmd.cpp 182
  • など...

この不注意…

警告 V519 'm_commentEndLine' 変数に 2 回連続して値が割り当てられています。おそらくこれは間違いです。チェック行:175, 176.PhpLexerAPI.h 176

struct WXDLLIMPEXP_CL phpLexerUserData {
    ....
    int m_commentStartLine;
    int m_commentEndLine;
    ....
    void ClearComment()
    {
        m_comment.clear();
        m_commentEndLine = wxNOT_FOUND;     // <=
        m_commentEndLine = wxNOT_FOUND;
    }
};

明らかなコピペミス。クラス phpLexerUserData 変数 commentStartLine があります 変数のほかに commentEndLine。 実際、ClearComment メソッドは次のようになります:

void ClearComment()
{
  m_comment.clear();
  m_commentStartLine = wxNOT_FOUND;
  m_commentEndLine = wxNOT_FOUND;
}

同じエラーがさらにいくつかの場所で見つかりました:

  • V519 'm_commentEndLine' 変数には、連続して 2 回値が割り当てられます。おそらくこれは間違いです。チェック行:171, 172.CxxLexerAPI.h 172
  • V519 'm_commentEndLine' 変数には、連続して 2 回値が割り当てられます。おそらくこれは間違いです。行を確認してください:143, 144.JSLexerAPI.h 144

警告 V547 式 'type.Lower() =="Array"' は常に false です。 NodeJSOuptutParser.h 61

struct NodeJSHandle {
  wxString type;
  ....
  bool IsString() const {return type.Lower() == "string";}
  bool IsArray() const {return type.Lower() == "Array"; }  // <=
};

IsArray メソッドは常に false を返します ちょっとした誤字脱字のため。これを修正するには、「Array」を「array」に置き換えるだけで、すべてが正常に機能します。

警告 V517 'if (A) {...} else if (A) {...}' パターンの使用が検出されました。論理エラーが存在する可能性があります。チェック行:383, 386. MainFrame.cpp 383

void MainFrame::OnSignal(wxCommandEvent& e)
{
  if(m_process) {
    int sigid = e.GetId();
    if(sigid == ID_SIGHUP)
        wxKill(m_process->GetPid(), wxSIGHUP);

    else if(sigid == ID_SIGINT)
        wxKill(m_process->GetPid(), wxSIGINT);

    else if(sigid == ID_SIGKILL)
        wxKill(m_process->GetPid(), wxSIGKILL);

    else if(sigid == ID_SIGKILL)        // <=
        wxKill(m_process->GetPid(), wxSIGTERM);        
  }
}

プログラマーは、前の文字列をコピーしてこのメ​​ソッドの記述を高速化することを決定したが、定数を変更するのを忘れていたと思います。もちろん、生産性の向上は素晴らしいことですが、注意を払うことを忘れてはなりません。正しいバージョンは次のとおりです:

void MainFrame::OnSignal(wxCommandEvent& e)
{
    ....
    else if(sigid == ID_SIGKILL)
        wxKill(m_process->GetPid(), wxSIGKILL);

    else if(sigid == ID_SIGTERM)        
        wxKill(m_process->GetPid(), wxSIGTERM);        
  }
}

もう 1 つのアナライザー警告:

  • V517 「if (A) {...} else if (A) {...}」パターンの使用が検出されました。論理エラーが存在する可能性があります。チェック行:212, 222. new_quick_watch_dlg.cpp 212

警告 V530 関数 'empty' の戻り値を使用する必要があります。アクター_ネットワーク.cpp 56

StringTokenizer::StringTokenizer(const wxString& str,
                const wxString& strDelimiter,
                const bool &bAllowEmptyTokens /* false */)
{
  ....
  wxString token;
  while( nEnd != -1 )
  {
    if( nEnd != nStart)
      token = str.substr(nStart, nEnd-nStart);
    else
      token.empty();        // <=

    if(!token.empty())
      m_tokensArr.push_back(token);
    ....
  }
}

empty() 関数はオブジェクトを変更せず、ブール値の結果のみを返します。つまり、else ブランチは何もしていません。 token.empty() の代わりに t プログラマは token.Empty() と書くべきだった 文字列、またはおそらく他の何かをゼロにします。

おっと!忘れ物

警告 V729 関数本体に、どの 'goto' ステートメントでも使用されていない 'find_rule' ラベルが含まれています。 include_finder.cpp 716

....
#define YY_DECL int yylex YY_PROTO(( void ))
....
YY_DECL
  {
    ....
    yy_find_action:
      yy_current_state = *--yy_state_ptr;
      yy_lp = yy_accept[yy_current_state];

      /* we branch to this label when backing up */
    find_rule:         // <= 
    
    for ( ; ; ) /* until we find what rule we matched */
    ....
  }

ここでのエラーは、多数のコード行の中に find_rule があることです。 ラベル、goto のどれも 演算子を参照してください。これは、コードのリファクタリングまたはその他の理由で発生する可能性があります。今のところ、この孤独なラベルには意味的な負荷はありません。何かがどこかに忘れられているというヒントを与えるだけです。

このような警告は、他のいくつかの場所で見つかりました:

  • V729 関数本体に、「goto」ステートメントで使用されていない「find_rule」ラベルが含まれています。 comment_parser.cpp 672
  • V729 関数本体に、「goto」ステートメントで使用されていない「find_rule」ラベルが含まれています。 cpp_expr_lexer.cpp 1090
  • V729 関数本体に、「goto」ステートメントで使用されていない「find_rule」ラベルが含まれています。 cpp_lexer.cpp 1138

警告 V523 'then' ステートメントは 'else' ステートメントと同等です。 art_metro.cpp 402

void wxRibbonMetroArtProvider::DrawTab(
                 wxDC& dc,
                 wxWindow* WXUNUSED(wnd),
                 const wxRibbonPageTabInfo& tab)
{
    ....
    if (tab.active)
      dc.SetPen(m_tab_border_pen);
    else
      // TODO: introduce hover border pen colour
      dc.SetPen(m_tab_border_pen);              // <=
     
    ....
 }

上記のコード フラグメントでは、プログラマーが何らかのアイデアに取り組み始めましたが、メモを書き留めて停止しました。 else に繰り返しコード文字列があってはならないことを推測するのは難しくありません -ブランチ。ただし、コメントから判断すると、おそらく一時的な決定です。

同様のアナライザー警告:

  • V523 'then' ステートメントは 'else' ステートメントと同等です。 art_metro.cpp 402
  • V523 'then' ステートメントは 'else' ステートメントと同等です。 php_workspace_view.cpp 948

警告 V560 条件式の一部が常に false:0. entry.c 397

extern void openTagFile (void)
{
  ....
  boolean fileExists;
  setDefaultTagFileName ();
  TagFile.name = eStrdup (Option.tagFileName);
  fileExists = doesFileExist (TagFile.name);

  /* allways override old files */
  if (fileExists  &&  /*! isTagFile (TagFile.name)*/ 0) // <= 
    error (FATAL,
      "\"%s\" doesn't look like a tag file; ....",
        TagFile.name);

  if (Option.etags)
   {
  ....
}

ここで条件 (fileExists && /*! isTagFile (TagFile.name)*/ 0) は 0 のため常に false です。おそらく、このようにするつもりでしたが、これはエラーである可能性が最も高いです。プログラマーがデバッグを行って条件を変更したときにコードに侵入した可能性がありますが、作業を終了した後、条件を元に戻すのを忘れていました。

余計な比較

警告 V728 過剰チェックを簡略化できます。 「||」演算子は、反対の式 '!found' と 'found' で囲まれています。 editor_config.cpp 120

bool EditorConfig::Load()
  {
  ....
  if(userSettingsLoaded) {
      if(!found || (found && version != this->m_version)) { // <=
          if(DoLoadDefaultSettings() == false) {
              return false;
          }
      }
  }
  ....
}

ここにはエラーはありませんが、このような状態は読みにくいです。次のように短縮できます:

if(!found || version != this->m_version)

警告 V571 定期チェック。 'isInStatement' 条件は 2292 行で既に検証されています。ASBeautifier.cpp 2293

void ASBeautifier::parseCurrentLine(const string& line)
{
....
    if(isInStatement && !inStatementIndentStack->empty()) {
      if(prevNonSpaceCh == '=' &&
         isInStatement && !inStatementIndentStack->empty()) // <=
          inStatementIndentStack->back() = 0;
    }
  }
....
}

連続して実行される 2 つのチェックに同じ部分式が記述されています。おそらく、このエラーはコピー アンド ペーストによってここに到達したか、おそらくこの条件を編集する必要がありますが、いずれにしても確認する価値があります。

結論

CodeLite プロジェクトには、C および C++ で記述された約 60 万行のコードがあります。もちろん、ほとんどのプロジェクトで発生するように、不注意やポインターの処理によるエラーもいくつかありました。合計で、アナライザーは 360 の第 1 および第 2 レベルの警告を発行しました。それらの約 40 は、レビューが必要であり、おそらく修正が必要なものです。

コードにエラーが蓄積しないようにするには、静的コード アナライザーを定期的に使用することが重要です。結果が示すように、アナライザーの優れたバリアントは PVS-Studio です。

Linux 用の PVS-Studio を使用して、自分のプロジェクトや興味のあるプロジェクトを確認したい場合は、ここからダウンロードできます。