![]() |
|
IBM Checking Tool for Bugs Errors and Mistakes(本文后面将采用其文字缩写 BEAM)是 IBM 开发的一个静态分析工具,可以用于分析并查找出 C, C++ 和 Java 代码中的一些不容易发现的潜在错误,从而达到提高代码质量的目的。同动态分析工具和其它静态分析工具相比,它拥有一些可贵的特性。
首先,BEAM 可以直接对代码进行分析,不需要运行代码,也不需要对代码编译链接,所以相对容易。比如,运行它不需要为代码编写任何测试用例,而动态分析仅在单元测试时就需要大量测试用例,而且这些测试用例只能测试单个代码片段,重用性很低,基本上每个类都需要不同的测试用例,因此只有编写足够多的单元测试用例才能测试大型的软件产品,耗时且代价高昂。
其次,这个工具可以查找出单元测试和专业代码审查所可能错过的代码缺陷和安全弱点,比如内存泄漏,非法的数据库访问和非法内存访问等等,据统计,BEAM 可以在平均每 1000 行已经经过测试的代码中找出一个错误。
再次,在开发早期就可以运行其对代码进行检查,从而在产品开发早期发现缺陷,有助于降低开发成本。同时,它还有助于开发人员在产品开发早期发现自己编码风格的缺点,及早做出改进,防止工程后期重复发生相同类型的错误。
同其它静态分析工具一样,BEAM 也是对代码进行语法扫描,通过算法对代码进行检查分析,并和一些 bug 模式进行比较,最终标明问题区域,输出分析结果。但是相对于其他静态分析工具,它有一些独到的优点。
首先,它模仿 javac 的使用,语法和 javac 相似,支持许多 javac 的常用命令参数,而且具有相同的语义,比如 -classpath
,-source
,-d
等等;不仅如此,它还模仿 javac 接受相同的源文件,只不过不是编译,而是分析检查这些源文件。这样习惯于 javac 的开发人员就可以很轻松的学会使用。
其次,许多静态分析工具报错的准确性较低,很多被这些工具标记为错误的代码事实上是正确的,这样会增加程序员工作量,并有可能掩盖真正的错误。而 BEAM 使用了额外的定理证明(theorem proving)技术来判断一个潜在的错误是否是真正的错误,从而减轻了程序员判断错误真伪所需的工作量。例如:
int Average(int Sum, int N) { return Sum / N; } |
很多静态分析工具遇到清单 1 的代码会报除数为 0 的错误,而事实上如果清单 1 所在的整个代码工程中,进入 Average
的参数 N 永远都不可能为 0 的话,这个错误就不能算是真正的错误。而 BEAM 只有在发现了确凿的证据证明除数 N 可能为 0 时才会报错。例如清单 2 和清单 3,都会报除数为 0 的错误。
int Average(int Sum, int N) { if ( N == 0 ) // 这里证明 N 有可能等于 0 X = 1; return Sum / N; } |
清单 3. 报错代码示例 2
int Average(int Sum, int N) { if ( N != 0 ) // 这里证明 N 有可能允许等于 0 X = 1; return Sum / N; } |
再次,这个工具使用了符号执行(symbolic execution)技术指出导致错误发生的条件,并在输出结果中打印出整个出错路径供程序员分析。例如清单 4:
- BEAM_VERSION=3.4.0
- BEAM_ROOT=D:/Work/class/beam
- BEAM_DIRECTORY_WRITE_INNOCENTS=D:/Work/class/beam/beam_data/innocent1
- BEAM_DIRECTORY_WRITE_ERRORS=D:/Work/class/beam/beam_data/errors1
- -- ERROR2 /*operating on NULL*/ >>>ERROR2_foo_55479ec82b606
- "Example.java", line 11: invalid operation involving NULL pointer
- ONE POSSIBLE PATH LEADING TO THE ERROR:
- "Example.java", line 2: loop entry condition is true
- "Example.java", line 5: the if-condition is true
- "Example.java",line 8: the if-condition is true(used as evidence that error is possible)
- "Example.java", line 11: using operation `[]' to dereference NULL pointer `p'
- VALUES AT THE END OF THE PATH:
- p = 0
BEAM 目前多用于 Linux/AIX 平台上对 C 和 C++ 语言的检查分析,而在最常用的 Windows 平台上使用其对 Java 进行静态分析的人不多,因此经验不足,文档匮乏。本文接下来就介绍如何在 Windows 平台上运行这个工具对 Java 代码进行分析。
与大多数工具一样,BEAM 可以以多种方式运行 —— 命令行、使用 Ant 或作为 Eclipse 插件程序。本文将只介绍使用 Ant 运行。
![]() |
|
BEAM 当前最新版本是 3.4.2,而且支持 Windows 2000 及其以上版本。本文假设您将其安装到 C:/BEAM-3.4.2
下。
在这个工具的 bin 目录下有些 Perl 脚本(比如 beam_configure),所以需要 5.004 或以上版本的 Perl 解释器去解释(请参阅 参考资料)。本文假设您把 ActivePerl 安装到 C:/Perl 目录下。
运行 beam_configure 脚本生成 Java 编译器配置文件
这个工具是为了尽可能多地匹配本地 Java 编译器 javac 的行为,需要通过一个 TCL 格式的编译器配置文件去了解本地 javac 的环境。(请参阅 参考资料 获得 TCL 文件相关知识)这个编译器配置文件中包括当前 Java 语言的版本(如 1.4),默认的根类路径(如 jre/lib/core.jar)和默认的 Classpath 等信息。
TCL 格式的编译器配置文件是通过 Perl 脚本 beam_configure 检查本地 Java 编译器 javac 后,使用检查结果自动生成的,如清单 5:
清单 5. 生成 Java 编译器配置文件
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
:表明接下来的编译器是 Java 编译器。D:/Tools/Work/ibm-sdk/bin/javac
:本地 Java 编译器 javac 的绝对路径。-o
:是 output 的意思,表明将配置信息输出到接下来指定的文件中。my_config.tcl
就是最终生成的 Java 编译器配置文件,名字可以任意,但是由于接下来要使用,所以需要记住这个名字。运行完此命令,当前目录下就生成了 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 平台的,所以临时目录是 /tmp,而 Windows 平台的临时目录是 c:/temp,所以需要修改临时目录。
这个工具支持 Ant 运行,可以把其当作 Ant 的一个任务来执行。(请参阅 参考资料 获得 Ant)。本文假设您把 Ant 安装到 C:/apache-ant-1.7.0 下。
在 Java 源文件的根目录下为 Ant 创建 build.xml(请参阅 参考资料 获得 XML 相关知识)。
1. 指定 XML 版本
所有的 XML 文件的第一行都必须是一个 XML 声明,指定将要使用的 XML 版本,本文使用 XML 1.0 版本。如清单 6
清单 6. 指定 XML 版本
<?xml version="1.0"?> |
2. 指定根目录
定义属性 beam.install,指定根目录,如清单 7 。通常,beam_compile 程序应该位于 ${beam.install}/bin
下。
清单 7. 指定根目录
<property name="beam.install" value="C:/BEAM-3.4.2"/> |
3. 指定任务名
因为要将其作为 Ant 的一个任务来运行,所以接下来需要指定任务 taskdef
,只有定义过 taskdef
,才可以在运行 Ant 时通过指定任务名来运行指定的任务。
清单 8. 指定任务名
<taskdef name="beam" classname="com.ibm.beam.ant.BeamTask" classpath="${beam.install}/jar/ant-beam.jar" /> |
taskdef name="beam"
:指定任务名为beam
,接下来 Ant 运行时可以通过指定beam
来运行指定的任务。classname="com.ibm.beam.ant.BeamTask"
:指定接下来要执行beam
任务类的全限定名,告诉 Ant 运行beam
任务要装载哪一个类。classpath="${beam.install}/jar/ant-beam.jar"
:指定classpath
,供寻找classname
时使用。
4. 指定 Java 源代码路径
清单 9. 指定 Java 源代码路径
<property name="code.dir" value="./java"/> |
指定 code.dir
属性,表明当前路径的 java
子目录下存放的所有 Java 源文件代码都需要这个工具进行分析。
5. 指定输出结果路径
清单 10. 指定输出结果路径
<property name="beam.classes" value="./class/beam"/> |
如清单 10 所示,beam.classes
存放分析输出结果(如:BEAM-messages 文件)。
6. 定义 path
BEAM 需要 javac 编译代码时使用的 classpath
,目的是检查源代码所调用的类库文件是否在 classpath
内。这里定义具有 id
属性的 path
元素,供接下来其 运行时指定 classpath
用。
清单 11. 定义 path
<path id="classpath"> <fileset dir="./lib"> <include name="**/*.jar"/> </fileset> </path> |
如清单 11 所示,path
包括当前路径下 lib
子目录下的所有 jar 包文件。
7. 定义 prepare 任务
在运行这个工具前,需要创建输出结果目录,为接下来的运行做准备。创建目录的动作必须在一个任务中完成,所以这里定义 prepare
任务以达到此目的。
清单 12. 定义 prepare 任务
<target name="prepare"> <mkdir dir="${beam.classes}"/> </target> |
在 Ant 的元素中,标签 target
专门用来定义新任务,定义的新任务 prepare
的内容是创建属性 beam.classes
的值所代表的目录,即输出结果目录。
8. 定义任务
前面已经通过 taskdef
指定了任务名为 beam,接下来定义任务的具体内容。
清单 13. 定义任务
- 1 <target name="beam" depends="prepare"
- 2 description="runs all code through beam.">
- 3 <beam srcdir="${code.dir}"
- 4 source="1.4"
- 5 destdir="${beam.classes}">
- 6 <classpath refid="classpath"/>
- 7 <option>--beam::compiler=D:/Work/Beam/Java/my_config.tcl</option>
- 8 <option>--beam::root=./class/beam</option>
- 9 <option>--beam::data=./class/beam/beam_data</option>
- 10 <option>--beam::display_analyzed_files</option>
- 11 <option>--beam::parser_file=./class/beam/BEAM-parseErrors</option>
- 12 <option>--beam::complaint_file=./class/beam/BEAM-messages</option>
- 13 <option>--beam::stats_file=./class/beam/BEAM-functions</option>
- 14 </beam>
- 15 </target>
让我们更详细地分析这段代码。
第 1 行:标签 target
定义 beam 任务,标签 depends
说明 beam 任务依赖于 prepare 任务,即运行 beam 任务前必须先运行 prepare 任务。
第 2 行:定义对该 beam
任务的描述。
第 3 行:定义元素 beam
,并指定所要分析的 Java 源代码的路径 srcdir
,本文是 code.dir
。
第 4 行: source
为这个工具支持的 javac 的编译器参数,指定所要分析的 Java 源代码的版本,本文是 1.4。
第 5 行: destdir
指定这个工具输出结果的位置,本文是 beam.classes
。
第 6 行: classpath
指定 Java 源文件所引用的一些类库的位置,refid
表明引用之前定义的 path 元素的 id 值。(请参阅 参考资料,获得关于 refid 的详细信息)
第 7 行 - 第 13 行 都是专门用来控制这个工具行为的特殊参数,定义特殊参数需要通过定义 option
元素来实现,option
元素的起始标签后不能再定义任何属性,这个工具的特殊参数通过在 option
的正文中定义。这个工具所有的参数都以 --beam
开头,使用这种不太寻常的前缀是为了尽量不与别的编译器参数相冲突。
第 7 行: --beam::compiler
:指定之前利用 beam_configure 脚本生成 的 Java 编译器配置文件 my_config.tcl 的位置。
第 8 行: --beam::root
:指定其输出结果的根路径。
第 9 行: --beam::data
:指定其输出结果中分析数据的路径,如果该目录不存在,这个工具会在运行时创建该目录。
第 10 行: --beam::display_analyzed_files
:指定该参数,会在分析 Java 源文件的同时打印出源文件的全路径。
第 11 行: --beam::parser_file
:这个工具自带一个解析器 parser,运行结束后,会将 parser 消息写入这里指定的文件。
第 12 行: --beam::complaint_file
:这是使用这个工具时最重要的参数,工具运行结束后,会将分析出的所有 ERROR
,MISTAKE
和 WARNING
都写入这里指定的文件中,程序员通过此输出文件来分析代码中的错误。
第 13 行: --beam::stats_file
:一些统计数据会存放在这里指定的文件中。
进入 Java 源文件的根目录下,即 build.xml 所在的目录,通过命令行调用 Ant 运行这个工具,如清单 14:
清单 14. 运行
C:/apache-ant-1.7.0/bin/ant beam |
C:/apache-ant-1.7.0/bin/ant
:Ant 批处理文件的绝对路径。beam
:build.xml 中定义的任务名。
在命令行上输入命令后,就会得到类似清单 15 所示的运行结果。
清单 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
中发现生成了 build.xml 中定义的 BEAM-messages
,它记录着这个工具报出的所有代码缺陷(ERROR
,MISTAKE
和 WARNING
),通过分析并相应修改这些错误,从而达到提高代码质量的目的。
BEAM-messages
中的这个工具的输出结果,一般如清单 16 所示:
- BEAM_VERSION=3.4.0
- BEAM_ROOT=D:/Work/class/beam
- BEAM_DIRECTORY_WRITE_INNOCENTS=D:/Work/class/beam/beam_data/innocent1
- BEAM_DIRECTORY_WRITE_ERRORS=D:/Work/class/beam/beam_data/errors1
- -- ERROR2 /*operating on NULL*/ >>>ERROR2_foo_55479ec82b606
- "Example.java", line 11: invalid operation involving NULL pointer
- ONE POSSIBLE PATH LEADING TO THE ERROR:
- "Example.java",line 8: the if-condition is true(used as evidence that error is possible)
- "Example.java", line 11: using operation `[]' to dereference NULL pointer `p'
- VALUES AT THE END OF THE PATH:
- p = 0
输出结果中首先列出了这个工具的版本和输出结果根目录等相关配置信息,“--
”之后是缺陷类型名,缺陷类型有 ERROR
,MISTAKE
和 WARNING
,严重程度依次递减。紧接着是代码出错的行数和解释,并附有详细的出错路径和在这个出错路径结束时变量最终的取值。
BEAM 报出假错误的概率很低,但是也会有偶尔出错的时候,如果发现这个工具报的缺陷是“无辜”的,则可以将缺陷类型名后的注释粘贴在源代码中其所报错的那一行后(如将清单 16 中 ERROR2 后的注释 /*operating on NULL*/ 粘贴在报错的第 11 行后),以后再运行时将不会再对此行代码的这个错误进行报错。
本文介绍了如何在 Windows 平台上运行静态分析工具 BEAM 来检查 Java 代码的缺陷,读者通过本文介绍的详细步骤,可以自行在 Windows 上运行 BEAM ,找出代码隐患,达到提高 Java 代码质量的目的。