Codan 是在 C/C++ 項目上執行代碼檢查的代碼分析框架。自 2011 年起,Codan 已成為 Eclipse CDT(C/C++ 開發工具 )一部分,它不僅提供執行靜態代碼分析所需的全部基礎架構,還提供了一些有用的、隨時可用的問題檢查器(參閱 參考 資料)。
Codan 於 2012 年 6 月隨 Eclipse Juno 進行了更新,支持開發人員在 Eclipse 中自動執行外部代碼分 析工具。對於 Eclipse CDT 和 C/C++ 開發人員來說,這是一個令人鼓舞的進步。盡管之前提供的問題檢查程序表現良好, 但仍然需要更多地提供與現有外部代碼分析工具看齊的 Codan 功能。現在,Codan 可輕松實現與成熟外部工具(比如 Cppcheck 和 clang_check)的集成。
與開發人員單獨使用 Codan 相比,將外部代碼分析工具與 Eclipse CDT 集 成能夠提供更多更好的代碼檢查,還會極大地改進綜合開發生產力。現在,我們可以從 Codan 的 Preferences 頁面對外部 代碼分析工具進行配置。一旦與 Codan 集成,就可以自動調用該工具,以編輯器標記的形式顯示其輸出。
在本文中 ,將向您展示如何使用 Java 代碼和少許 XML 將您最喜愛的代碼分析器集成到您的 Eclipse C/C++ 開發環境中。我的示例 基於 Cppcheck 和 Codan 的集成,但該過程應該同樣適用於您選擇的工具。
安裝 Eclipse Juno 和 CDT
要 跟隨本文中的示例進行學習,需要安裝 Eclipse Juno 和 CDT。如果您尚未安裝 Eclipse,可以安裝一個帶有 CDT 預安裝 程序的版本。為此,只需從 Eclipse downloads 頁面選擇 Eclipse IDE for C/C++ Developers 即可。
如果您已經 安裝了一個不包括 CDT 的 Eclipse,那麼請按照下列指令來更新您的開發環境:
在 Eclipse 中,選擇菜單 Help > Install New Software...。
在 Install 對話框中,從下拉列表中選擇 Juno。
在 Programming Languages 目錄中,選擇 C/C++ Development Tools SDK。
圖 1. 安裝 CDT
除了 CDT 之外,還需要安裝標准 GNU C/C++ 開發工具來編譯、構建和調試您的代碼。
啟動 Codan
大多數 Codan 代碼檢查器都是默認啟用的。您可在工 作區或項目層分別使用 Eclipse 的 Preferences 或 Project Property 頁面單獨配置 Codan 代碼檢查。
在 Codan 的 Preferences 頁面中,如 圖 2 所示,您可以看到提供的所有代碼檢查器以及每個檢查器上報告的代碼問題。
圖 2. Codan Preferences 頁面上的代碼檢查器
在此頁中,您可以啟用、禁用或修改一個問 題的嚴重程度。如果您想配置某個問題的其他屬性,可以選中該問題並單擊 Customize Selected... 按鈕。圖 3 顯示了問 題 “Name convention for function” 的配置選項。
圖 3. 配置一個問題
圖 3 中的第 3 個選項卡允許您指定問題檢 查的啟動形式:
Run as you type:當用戶在 CDT 編輯器中更改某個文件時。
Run on file open:當文件在 CDT 編輯器中打開時。
Run on file save:當 CDT 編輯器上未保存的更改被保存時。
Run on incremental build:當發布增量構建時(通常在保存一個文件並啟用應用程序級選項 “Build Automatically ” 時)。如果您同時啟用該選項和 “Run on file save”,那麼代碼檢查將會運行兩次。
Run on full build:當發布完全構建時(例如,當清理某個項目時)。
Run on demand:當用戶從上下文按鈕項 “Run C/C++ Code Analysis” 手動觸發代碼檢查時。
使用 Codan 進行代碼檢查
為了使您能看到運行的 Codan,我會創建一個 C++ 項目和一個小的 C++ 文件。在此 文件中,我將為文件本身分配一個變量。Codan 包括代碼檢查 “Assignment to itself”,這是默認啟動的,嚴重程度級 別為 “error”。該代碼檢查被配置為在您輸入時運行,因此錯誤會立即彈出。
圖 4. 執行一個代碼檢查的 Codan
在 圖 4 中,您會看到,Codan 已經發現了自賦值錯誤,並在我還沒有來得及保存該文件之前報告錯誤。
要學習關 於使用 Codan 的更多信息,請訪問項目的主頁。
將 Cppcheck 集成到 Eclipse CDT 中
要集成外部代碼分析 工具與 Codan,需要編寫一個知道如何調用此工具的特殊檢查器,一個檢查器 是 Codan 的 IChecker 接口的一個實現,在 給定的 IResource(通常是一個 IFile)上執行某種代碼檢查。
為了演示如何輕松創建一個基於外部工具的檢查器 ,我們會創建一個調用流行工具 Cppcheck 的檢查器。下面我們將執行以下操作:
創建一個 Eclipse 插件項目,並添加 Codan 作為一個依賴項。
創建一個錯誤分析器來分析 Cppcheck 輸出,並創建編輯器標記(如果需要的話)。
創建代碼檢查器,該檢查器是負責調用 Cppcheck 的類。
步驟 1. 創建一個 Eclipse 插件項目
要創建一個 Codan 檢查器,首先要創建一個新 Eclipse 插件項目:
選擇菜單 File > New > Project...。
在類別 Plug-in Development 中,選擇 Plug-in Project。
為項目輸入一個名稱(我使用的名稱是 “CppcheckChecker”)並單擊 Next。
接受默認值並單擊 Finish。
圖 5. 創建一個插件項目
創建新的插件項目之後,Eclipse 會自動打 開 MANIFEST.MF 文件,該文件就是我們即將添加 Codan 依賴項的文件。
在編輯器中,選擇 Dependencies 選項卡 ,並將以下內容添加到 Required Plug-ins 列表中:
org.eclipse.cdt.codan.core org.eclipse.cdt.codan.core.cxx org.eclipse.cdt.codan.ui org.eclipse.cdt.codan.ui.cxx org.eclipse.cdt.core org.eclipse.cdt.ui org.eclipse.core.resources org.eclipse.core.runtime org.eclipse.ui
步驟 2. 創建一個錯誤分析器
我們將需要使用一個錯誤分析器從 Cppcheck 輸出中創建編 輯器標記,因此,我們的下一步是使用一個插件來擴展 Eclipse 的 C/C++ 工具。為此,我們將使用 Java 代碼,因為 Eclipse 本身就是一個 Java 應用程序。
首先,我們將創建一個類 CppcheckErrorParser,該類實現了 org.eclipse.cdt.core.IErrorParser。我們從尋找報告代碼問題時 Cppcheck 所用的模式開始。錯誤分析器會使用這個模 式來識別代表錯誤報告的一行輸出,然後從輸出中提取所需的信息來創建一個編輯器標記。
清單 1. 與 Cppcheck 輸出匹配的模式
// sample line to parse: // // [/src/HelloWorld.cpp:19]: (style) The scope of the variable 'i' can be reduced // ----------1--------- -2 --3-- ------------------4------------------------- // // groups: // 1: file path and name // 2: line where problem was found // 3: problem severity // 4: problem description private static Pattern pattern = Pattern.compile("\\[(.*):(\\d+)\\]:\\s*\\((.*)\\)\\s*(.*)");
清單 2 顯示了錯誤分析器如何使用該模式來提取將要檢查的文件的路徑和名稱,以及位置、描述和錯誤嚴重程度。有了 這些信息,錯誤分析器就可以創建一個新的 ProblemMarkerInfo,並將它傳遞給給定的 ErrorParserManager。 ErrorParserManager 是負責創建編輯器標記的類。
清單 2. 處理 Cppcheck 的輸出
@Override public boolean processLine(String line, ErrorParserManager parserManager) { Matcher matcher = pattern.matcher(line); if (!matcher.matches()) { return false; } IFile fileName = parserManager.findFileName(matcher.group(1)); if (fileName != null) { int lineNumber = Integer.parseInt(matcher.group(2)); String description = matcher.group(4); int severity = findSeverityCode(matcher.group(3)); ProblemMarkerInfo info = new ProblemMarkerInfo(fileName, lineNumber, description, severity, null); parserManager.addProblemMarker(info); return true; } return false; } }
映射問題嚴重程度
Cppcheck 定義了自己的問題的嚴重程度,與編輯器標記使用的不一樣。例如, Cppcheck 嚴重程度 “style” 在 Eclipse 中並沒有與之相對應的項。為了解決這個問題,我們需要在這兩類問題嚴重程 度之間創建一個映射。方法 findSeverityCode(如 清單 3 所示)演示了一個實現該映射的簡單方法:
清單 3. 映 射問題嚴重程度
private static Map<String, Integer> SEVERITY_MAPPING = new HashMap<String, Integer>(); static { SEVERITY_MAPPING.put("error", IMarkerGenerator.SEVERITY_ERROR_RESOURCE); SEVERITY_MAPPING.put("warning", IMarkerGenerator.SEVERITY_WARNING); SEVERITY_MAPPING.put("style", IMarkerGenerator.SEVERITY_INFO); } private int findSeverityCode(String text) { Integer code = SEVERITY_MAPPING.get(text); if (code != null) { return code; } return IMarkerGenerator.SEVERITY_INFO; }
創建完映射之後,Cppcheck 報告的嚴重程度為 “style” 的問題在 Eclipse 中將使用嚴重程度 SEVERITY_INFO 顯示。該映射僅定義了問題嚴重程度的默認值。稍後您會看到如何從 Codan Preferences 頁面配置該映射 。
對於 Codan 認可的 CppcheckErrorParser,需要在 plugin.xml 文件中注冊,並使用擴展點 org.eclipse.cdt.core.ErrorParser:
清單 4. 注冊錯誤分析器
<extension id="com.developerworks.cdt.checkers" name="Cppcheck error parsers" point="org.eclipse.cdt.core.ErrorParser"> <errorparser class="cppcheckchecker.CppcheckErrorParser" id="com.dw.cdt.checkers.CppcheckErrorParser" name="Cppcheck"> <context type="codan" /> </errorparser> </extension>
注意,在 清單 4 中,創建 ErrorParser 擴展點最初是為了注冊用於 CDT 構建工具的分析 器。該擴展點並不是特定於 Codan 的。為了表明 CppcheckErrorParser 僅與 Codan 同時使用,我們添加了上下文 “codan”。
步驟 3. 創建代碼檢查器
AbstractExternalToolBasedChecker 是任何基於外部工具的 Codan 代碼檢查器的超類。它提供了調用外部代碼分析工具的大部分基礎架構。由於我們集成了 Cppcheck,所以將調用 CppcheckChecker 這個類。
我們所要做的第一件事就是為該信息指定默認值,此消息與外部工具有關,會顯示在 Codan Preferences 頁面中。
應該將該信息傳遞給檢查器構造器,此信息包含以下內容:
外 部代碼分析工具名稱,在本例中是 Cppcheck。
工具的可執行文件名,在這裡是 cppcheck。我們不需 要為可執行文件指定一個路徑,因為我們假設它位於系統的 PATH 中。
傳遞到可執行文件的參數包含 在單個 String 中。我們指定了 “--enable=all”,以支持所有 Cppcheck 檢查。
清單 5. 默認 Cppcheck 信息
public CppCheckChecker() { super(new ConfigurationSettings("Cppcheck", new File("cppcheck"), "--enable=all")); }
請注意,Codan Preferences 頁面允許我們修改可執行路徑和傳遞參數。
將問題嚴重程度映射到問題 ID
接下來,指定我們將要使用的錯誤分析器的 ID,如清單 6 所示。ID 必須與 plugin.xml 文件中所用的 ID 相同 。
清單 6. 指定將要使用的錯誤分析器的 ID
@Override protected String[] getParserIDs() { return new String[] { "com.dw.cdt.checkers.CppcheckErrorParser" }; }
回到 清單 2,我們創建了一個 ProblemMarkerInfo,並將它傳遞給給定 ErrorParserManager 來創建編輯器標 記。ErrorParserManager 將編輯器標記創建委托給我們最新創建的檢查器。
為了讓檢查器創建一個編輯器標記,我 們需要重寫方法 addMarker(ProblemMarkerInfo)(它的工作是發現另一種類型的不匹配)。Codan 檢查程序不能直接從 ProblemMarkerInfo 創建編輯器標記。它們有自己的機制,使用問題 ID 描述已創建的編輯器標記的相應嚴重程度。
問題 ID 是一個獨一無二的 ID,Codan 用它來識別編輯器檢查器報告的代碼問題。所有代碼問題都在 Codan Preferences 頁面顯示(參見 圖 2)。
清單 7. 創建錯誤標記
@Override public void addMarker(ProblemMarkerInfo info) { String problemId = PROBLEM_IDS.get(info.severity); String description = String.format("[cppcheck] %s", info.description); reportProblem(problemId, createProblemLocation(info), description); }
要找到符合 ProblemMarkerInfo 嚴重程度的問題 ID,需要在嚴重程度和問題 ID 之間創建一個映射。清單 8 顯示了該映射是如何實現的:
清單 8. 將問題嚴重程度映射到問題 ID
private static final String ERROR_PROBLEM_ID = "com.dw.cdt.checkers.cppcheck.error"; private static final Map<Integer, String> PROBLEM_IDS = new HashMap<Integer, String>(); static { PROBLEM_IDS.put( IMarkerGenerator.SEVERITY_ERROR_RESOURCE, ERROR_PROBLEM_ID); PROBLEM_IDS.put( IMarkerGenerator.SEVERITY_WARNING, "com.dw.cdt.checkers.cppcheck.warning"); PROBLEM_IDS.put( IMarkerGenerator.SEVERITY_INFO, "com.dw.cdt.checkers.cppcheck.style"); }
使用外部代碼分析工具的代碼檢查器需要指出使用哪個問題 ID 作為 “首選”。首選問題 ID 被用來獲取檢查 器的首選值(例如,外部工具名,如 清單 5 所示)。哪個問題 ID 是首選無關要緊,因為所有問題都將共享該首選。
清單 9. 指定首選問題 ID
@Override protected String getReferenceProblemId() { return ERROR_PROBLEM_ID; }
常量 ERROR_PROBLEM_ID 是在 清單 8 中定義的。
注冊檢查器
為了將代碼檢查器和它報告的所有 問題都顯示在 Codan Preferences 頁面上(從而可以讓用戶訪問它),我們需要向 Codan 的 plugin.xml 文件注冊該檢查 器。
因為我們不知道 Cppcheck 報告的所有問題,我們也不能阻止 Cppcheck 在未來的版本中添加或刪除代碼檢查 ,所以我們不能注冊每一個問題。相反,我們可以通過嚴重程度對問題進行分組,然後將每一組作為一個問題進行處理。在 清單 10 中,我們將注冊 errors、warnings 和 style violations 作為 3 個獨立問題:
清單 10. 注冊檢查器和 問題報告
<extension point="org.eclipse.cdt.codan.core.checkers"> <category id="cppcheckChecker.category" name="Cppcheck" /> <checker class="cppcheckchecker.CppcheckChecker" id="cppcheckChecker.cppChecker" name="CppcheckChecker"> <problem id="com.dw.cdt.checkers.cppcheck.error" name="Error" defaultEnabled="true" defaultSeverity="Error" messagePattern="{0}" category="cppcheckChecker.category"/> <problem id="com.dw.cdt.checkers.cppcheck.warning" name="Warning" defaultEnabled="true" defaultSeverity="Warning" messagePattern="{0}" category="cppcheckChecker.category"/> <problem id="com.dw.cdt.checkers.cppcheck.style" name="Style" defaultEnabled="true" defaultSeverity="Info" messagePattern="{0}" category="cppcheckChecker.category"/> </checker> </extension>
我們在 category 元素中指定了將向用戶顯示的檢查器名稱。問題 ID 應該與檢查器中所用 的 ID 相同(參見 清單 8)。
在 plugin.xml 文件中,關於所有這 3 類問題,我們進行了以下指定:
都是默認啟用的。
分別具有默認嚴重程度 “Error”、“Warning” 和 “Info”。
都具有以下消息模式:“{0},”,這表示強制 Codan 使用問題描述,因為它是由 Cppcheck 報告的。
在 Codan 中使用 Cppcheck
現在,在 Codan Preferences 頁面上我們可以看到 CppcheckChecker,如 圖 6 所 示:
圖 6. Codan Preferences 頁面上顯示的 Cppcheck
圖 7 顯示配置 Cppcheck 應如何報告代碼錯 誤的選項:
圖 7. 配置 Cppcheck 的錯誤報告
圖 8 顯示了 Cppcheck 如何報告代碼問題。 請注意,Cppcheck 是在保存文件後自動調用的。
圖 8. 一個 Cppcheck 報告
集成的缺點
集成 Codan 與外部代碼 分析工具的一個限制是:當用戶輸入時,基於外部工具的檢查器不能運行。這是因為外部工具無法看到文件的未保存的更改 。因此,外部檢查器僅在打開文件並且保存該該文件的時候才能運行。
然而,這一限制超過了使用成熟代碼分析工 具所帶來的優勢。與創建一個正規檢查器相比,集成外部工具和 Codan 更輕松、更簡單,創建一個正規檢查器需要深入了 解 C 或 C++ 語言。相比之下,我們僅使用 100 行簡單的 Java 代碼(包含在 2 個類中)和 30 行 XML 就可以編寫 CppcheckChecker。
結束語
在發布 Eclipse Juno 之前,為 Codan 創建自定義代碼檢查需要對 C/C++ 語言 和 CDT 的 AST 實現有一個很好的理解。Eclipse CDT 的 Juno 版本解決了讓開發人員創建 Codan 代碼檢查器的問題,然 後將這個重任委托給了外部代碼分析工具。
在本文中,我們僅使用少許 Java 代碼和 XML 就集成了 Cppcheck 和 Codan,將這個流行的 C/C++ 代碼分析工具和 Eclipse 中用於 C/C++ 程序的內置代碼分析框架結合在一起。正如我之前提 到的,您可以將本文演示的流程用於您最喜愛的代碼分析工具。