![]() |
|
IBM Checking Tool for Bugs Errors and Mistakes(本文后面將采用其文字縮寫(xiě) BEAM)是IBM 開(kāi)發(fā)的一個(gè)靜態(tài)分析工具,可以用于分析并查找出 C, C++ 和 Java代碼中的一些不容易發(fā)現的潛在錯誤,從而達到提高代碼質(zhì)量的目的。同動(dòng)態(tài)分析工具和其它靜態(tài)分析工具相比,它擁有一些可貴的特性。
首先,BEAM可以直接對代碼進(jìn)行分析,不需要運行代碼,也不需要對代碼編譯鏈接,所以相對容易。比如,運行它不需要為代碼編寫(xiě)任何測試用例,而動(dòng)態(tài)分析僅在單元測試時(shí)就需要大量測試用例,而且這些測試用例只能測試單個(gè)代碼片段,重用性很低,基本上每個(gè)類(lèi)都需要不同的測試用例,因此只有編寫(xiě)足夠多的單元測試用例才能測試大型的軟件產(chǎn)品,耗時(shí)且代價(jià)高昂。
其次,這個(gè)工具可以查找出單元測試和專(zhuān)業(yè)代碼審查所可能錯過(guò)的代碼缺陷和安全弱點(diǎn),比如內存泄漏,非法的數據庫訪(fǎng)問(wèn)和非法內存訪(fǎng)問(wèn)等等,據統計,BEAM 可以在平均每 1000 行已經(jīng)經(jīng)過(guò)測試的代碼中找出一個(gè)錯誤。
再次,在開(kāi)發(fā)早期就可以運行其對代碼進(jìn)行檢查,從而在產(chǎn)品開(kāi)發(fā)早期發(fā)現缺陷,有助于降低開(kāi)發(fā)成本。同時(shí),它還有助于開(kāi)發(fā)人員在產(chǎn)品開(kāi)發(fā)早期發(fā)現自己編碼風(fēng)格的缺點(diǎn),及早做出改進(jìn),防止工程后期重復發(fā)生相同類(lèi)型的錯誤。
同其它靜態(tài)分析工具一樣,BEAM 也是對代碼進(jìn)行語(yǔ)法掃描,通過(guò)算法對代碼進(jìn)行檢查分析,并和一些 bug 模式進(jìn)行比較,最終標明問(wèn)題區域,輸出分析結果。但是相對于其他靜態(tài)分析工具,它有一些獨到的優(yōu)點(diǎn)。
首先,它模仿 javac 的使用,語(yǔ)法和 javac 相似,支持許多 javac 的常用命令參數,而且具有相同的語(yǔ)義,比如 -classpath,-source,-d 等等;不僅如此,它還模仿 javac 接受相同的源文件,只不過(guò)不是編譯,而是分析檢查這些源文件。這樣習慣于 javac 的開(kāi)發(fā)人員就可以很輕松的學(xué)會(huì )使用。
其次,許多靜態(tài)分析工具報錯的準確性較低,很多被這些工具標記為錯誤的代碼事實(shí)上是正確的,這樣會(huì )增加程序員工作量,并有可能掩蓋真正的錯誤。而B(niǎo)EAM 使用了額外的定理證明(theoremproving)技術(shù)來(lái)判斷一個(gè)潛在的錯誤是否是真正的錯誤,從而減輕了程序員判斷錯誤真偽所需的工作量。例如:
int Average(int Sum, int N) { return Sum / N; } |
很多靜態(tài)分析工具遇到清單 1 的代碼會(huì )報除數為 0 的錯誤,而事實(shí)上如果清單 1 所在的整個(gè)代碼工程中,進(jìn)入 Average 的參數 N 永遠都不可能為 0 的話(huà),這個(gè)錯誤就不能算是真正的錯誤。而 BEAM 只有在發(fā)現了確鑿的證據證明除數 N 可能為 0 時(shí)才會(huì )報錯。例如清單 2 和清單 3,都會(huì )報除數為 0 的錯誤。
int Average(int Sum, int N) { if ( N == 0 ) // 這里證明 N 有可能等于 0 X = 1; return Sum / N; } |
int Average(int Sum, int N) { if ( N != 0 ) // 這里證明 N 有可能允許等于 0 X = 1; return Sum / N; } |
再次,這個(gè)工具使用了符號執行(symbolic execution)技術(shù)指出導致錯誤發(fā)生的條件,并在輸出結果中打印出整個(gè)出錯路徑供程序員分析。例如清單 4:
BEAM 目前多用于 Linux/AIX 平臺上對 C 和 C++ 語(yǔ)言的檢查分析,而在最常用的 Windows 平臺上使用其對Java 進(jìn)行靜態(tài)分析的人不多,因此經(jīng)驗不足,文檔匱乏。本文接下來(lái)就介紹如何在 Windows 平臺上運行這個(gè)工具對 Java 代碼進(jìn)行分析。
與大多數工具一樣,BEAM 可以以多種方式運行 —— 命令行、使用 Ant 或作為 Eclipse 插件程序。本文將只介紹使用 Ant 運行。
![]() |
|
BEAM 當前最新版本是 3.4.2,而且支持 Windows 2000 及其以上版本。本文假設您將其安裝到 C:\BEAM-3.4.2 下。
在這個(gè)工具的 bin 目錄下有些 Perl 腳本(比如 beam_configure),所以需要 5.004 或以上版本的 Perl 解釋器去解釋?zhuān)ㄕ垍㈤?參考資料)。本文假設您把 ActivePerl 安裝到 C:\Perl 目錄下。
運行 beam_configure 腳本生成 Java 編譯器配置文件
這個(gè)工具是為了盡可能多地匹配本地 Java 編譯器 javac 的行為,需要通過(guò)一個(gè) TCL 格式的編譯器配置文件去了解本地 javac 的環(huán)境。(請參閱 參考資料 獲得 TCL 文件相關(guān)知識)這個(gè)編譯器配置文件中包括當前 Java 語(yǔ)言的版本(如 1.4),默認的根類(lèi)路徑(如 jre\lib\core.jar)和默認的 Classpath 等信息。
TCL 格式的編譯器配置文件是通過(guò) Perl 腳本 beam_configure 檢查本地 Java 編譯器 javac 后,使用檢查結果自動(dòng)生成的,如清單 5:
C:\Perl\bin\perl "C:\BEAM-3.4.2\bin\beam_configure" --java D:\Tools\Work\ibm-sdk\bin\javac -o my_config.tcl |
C:\Perl\bin\perl:Perl 解釋器的絕對路徑。 C:\BEAM-3.4.2\bin\beam_configure:Perl 腳本 beam_configure 的絕對路徑。 --java:表明接下來(lái)的編譯器是 Java 編譯器。 D:\Tools\Work\ibm-sdk\bin\javac:本地 Java 編譯器 javac 的絕對路徑。 -o:是 output 的意思,表明將配置信息輸出到接下來(lái)指定的文件中。 my_config.tcl 就是最終生成的 Java 編譯器配置文件,名字可以任意,但是由于接下來(lái)要使用,所以需要記住這個(gè)名字。運行完此命令,當前目錄下就生成了 my_config.tcl。本文假設您把 my_config.tcl 放在 D:\Work\Beam\Java\ 下。 注意:需要修改 beam_configure 腳本中的一行代碼才能成功運行生成 TCL 文件。
將
my $tmpdir = "/tmp/beam_config.$$." . int(rand 1000000); |
my $tmpdir = "c:/temp/beam_config.$$." . int(rand 1000000); |
因為 bin 目錄下的 beam_configure 是針對 Linux 和 AIX 平臺的,所以臨時(shí)目錄是 /tmp,而 Windows 平臺的臨時(shí)目錄是 c:/temp,所以需要修改臨時(shí)目錄。
這個(gè)工具支持 Ant 運行,可以把其當作 Ant 的一個(gè)任務(wù)來(lái)執行。(請參閱 參考資料 獲得 Ant)。本文假設您把 Ant 安裝到 C:\apache-ant-1.7.0 下。
創(chuàng )建支持 BEAM 的 build.xml 文件
在 Java 源文件的根目錄下為 Ant 創(chuàng )建 build.xml(請參閱 參考資料 獲得 XML 相關(guān)知識)。
1. 指定 XML 版本
所有的 XML 文件的第一行都必須是一個(gè) XML 聲明,指定將要使用的 XML 版本,本文使用 XML 1.0 版本。如清單 6
<?xml version="1.0"?> |
2. 指定根目錄
定義屬性 beam.install,指定根目錄,如清單 7 。通常,beam_compile 程序應該位于 ${beam.install}/bin 下。
<property name="beam.install" value="C:\BEAM-3.4.2"/> |
3. 指定任務(wù)名
因為要將其作為 Ant 的一個(gè)任務(wù)來(lái)運行,所以接下來(lái)需要指定任務(wù) taskdef,只有定義過(guò) taskdef,才可以在運行 Ant 時(shí)通過(guò)指定任務(wù)名來(lái)運行指定的任務(wù)。
<taskdef name="beam" classname="com.ibm.beam.ant.BeamTask" classpath="${beam.install}/jar/ant-beam.jar" /> |
taskdef name="beam":指定任務(wù)名為 beam,接下來(lái) Ant 運行時(shí)可以通過(guò)指定 beam 來(lái)運行指定的任務(wù)。 classname="com.ibm.beam.ant.BeamTask":指定接下來(lái)要執行 beam 任務(wù)類(lèi)的全限定名,告訴 Ant 運行 beam 任務(wù)要裝載哪一個(gè)類(lèi)。 classpath="${beam.install}/jar/ant-beam.jar":指定 classpath,供尋找 classname 時(shí)使用。 4. 指定 Java 源代碼路徑
<property name="code.dir" value="./java"/> |
指定 code.dir 屬性,表明當前路徑的 java 子目錄下存放的所有 Java 源文件代碼都需要這個(gè)工具進(jìn)行分析。
5. 指定輸出結果路徑
<property name="beam.classes" value="./class/beam"/> |
如清單 10 所示,beam.classes 存放分析輸出結果(如:BEAM-messages 文件)。
6. 定義 path
BEAM 需要 javac 編譯代碼時(shí)使用的 classpath,目的是檢查源代碼所調用的類(lèi)庫文件是否在 classpath 內。這里定義具有 id 屬性的 path 元素,供接下來(lái)其 運行時(shí)指定 classpath 用。
<path id="classpath"> <fileset dir="./lib"> <include name="**/*.jar"/> </fileset> </path> |
如清單 11 所示,path 包括當前路徑下 lib 子目錄下的所有 jar 包文件。
7. 定義 prepare 任務(wù)
在運行這個(gè)工具前,需要創(chuàng )建輸出結果目錄,為接下來(lái)的運行做準備。創(chuàng )建目錄的動(dòng)作必須在一個(gè)任務(wù)中完成,所以這里定義 prepare 任務(wù)以達到此目的。
<target name="prepare"> <mkdir dir="${beam.classes}"/> </target> |
在 Ant 的元素中,標簽 target 專(zhuān)門(mén)用來(lái)定義新任務(wù),定義的新任務(wù) prepare 的內容是創(chuàng )建屬性 beam.classes 的值所代表的目錄,即輸出結果目錄。
8. 定義任務(wù)
前面已經(jīng)通過(guò) taskdef 指定了任務(wù)名為 beam,接下來(lái)定義任務(wù)的具體內容。
讓我們更詳細地分析這段代碼。
第 1 行:標簽 target 定義 beam 任務(wù),標簽 depends 說(shuō)明 beam 任務(wù)依賴(lài)于 prepare 任務(wù),即運行 beam 任務(wù)前必須先運行 prepare 任務(wù)。
第 2 行:定義對該 beam 任務(wù)的描述。
第 3 行:定義元素 beam,并指定所要分析的 Java 源代碼的路徑 srcdir,本文是 code.dir。
第 4 行: source 為這個(gè)工具支持的 javac 的編譯器參數,指定所要分析的 Java 源代碼的版本,本文是 1.4。
第 5 行: destdir 指定這個(gè)工具輸出結果的位置,本文是 beam.classes。
第 6 行: classpath 指定 Java 源文件所引用的一些類(lèi)庫的位置,refid 表明引用之前定義的 path 元素的 id 值。(請參閱 參考資料,獲得關(guān)于 refid 的詳細信息)
第 7 行 - 第 13 行 都是專(zhuān)門(mén)用來(lái)控制這個(gè)工具行為的特殊參數,定義特殊參數需要通過(guò)定義 option 元素來(lái)實(shí)現,option 元素的起始標簽后不能再定義任何屬性,這個(gè)工具的特殊參數通過(guò)在 option 的正文中定義。這個(gè)工具所有的參數都以 --beam開(kāi)頭,使用這種不太尋常的前綴是為了盡量不與別的編譯器參數相沖突。
第 7 行: --beam::compiler:指定之前利用 beam_configure 腳本生成 的 Java 編譯器配置文件 my_config.tcl 的位置。
第 8 行: --beam::root:指定其輸出結果的根路徑。
第 9 行: --beam::data:指定其輸出結果中分析數據的路徑,如果該目錄不存在,這個(gè)工具會(huì )在運行時(shí)創(chuàng )建該目錄。
第 10 行: --beam::display_analyzed_files:指定該參數,會(huì )在分析 Java 源文件的同時(shí)打印出源文件的全路徑。
第 11 行: --beam::parser_file:這個(gè)工具自帶一個(gè)解析器 parser,運行結束后,會(huì )將 parser 消息寫(xiě)入這里指定的文件。
第 12 行: --beam::complaint_file:這是使用這個(gè)工具時(shí)最重要的參數,工具運行結束后,會(huì )將分析出的所有 ERROR,MISTAKE 和 WARNING 都寫(xiě)入這里指定的文件中,程序員通過(guò)此輸出文件來(lái)分析代碼中的錯誤。
第 13 行: --beam::stats_file:一些統計數據會(huì )存放在這里指定的文件中。
進(jìn)入 Java 源文件的根目錄下,即 build.xml 所在的目錄,通過(guò)命令行調用 Ant 運行這個(gè)工具,如清單 14:
C:\apache-ant-1.7.0\bin\ant beam |
C:\apache-ant-1.7.0\bin\ant:Ant 批處理文件的絕對路徑。 beam:build.xml 中定義的任務(wù)名。 在命令行上輸入命令后,就會(huì )得到類(lèi)似清單 15 所示的運行結果。
Buildfile: build.xml prepare: beam: [beam] BEAM: Analyzing `D:\Work\java\com\ibm\config\CliVersionHandler.java' [beam] BEAM: Analyzing `D:\Work\java\com\ibm\config\CliHandler.java' [beam] BEAM: Analyzing `D:\Work\java\com\ibm\config\EssMetaClassHandler.java' [beam] BEAM: Analyzing `D:\Work\java\com\ibm\config\CliResourceID.java' [beam] BEAM: Analyzing `D:\Work\java\com\ibm\config\CliIOPortHandler.java' [beam] BEAM: Analyzing `D:\Work\java\com\ibm\config\CliRankHandler.java' BUILD SUCCESSFUL Total time: 1 minutes 11 seconds |
成功運行完后,即可以在其輸出結果路徑 beam.classes 中發(fā)現生成了 build.xml 中定義的 BEAM-messages,它記錄著(zhù)這個(gè)工具報出的所有代碼缺陷(ERROR,MISTAKE 和 WARNING),通過(guò)分析并相應修改這些錯誤,從而達到提高代碼質(zhì)量的目的。
BEAM-messages 中的這個(gè)工具的輸出結果,一般如清單 16 所示:
輸出結果中首先列出了這個(gè)工具的版本和輸出結果根目錄等相關(guān)配置信息,“--”之后是缺陷類(lèi)型名,缺陷類(lèi)型有 ERROR,MISTAKE 和 WARNING,嚴重程度依次遞減。緊接著(zhù)是代碼出錯的行數和解釋?zhuān)⒏接性敿毜某鲥e路徑和在這個(gè)出錯路徑結束時(shí)變量最終的取值。
BEAM報出假錯誤的概率很低,但是也會(huì )有偶爾出錯的時(shí)候,如果發(fā)現這個(gè)工具報的缺陷是“無(wú)辜”的,則可以將缺陷類(lèi)型名后的注釋粘貼在源代碼中其所報錯的那一行后(如將清單 16 中 ERROR2 后的注釋 /*operating on NULL*/ 粘貼在報錯的第 11行后),以后再運行時(shí)將不會(huì )再對此行代碼的這個(gè)錯誤進(jìn)行報錯。
本文介紹了如何在 Windows 平臺上運行靜態(tài)分析工具 BEAM 來(lái)檢查 Java 代碼的缺陷,讀者通過(guò)本文介紹的詳細步驟,可以自行在 Windows 上運行 BEAM ,找出代碼隱患,達到提高 Java 代碼質(zhì)量的目的。
聯(lián)系客服