这是静态代码扫描系列文章的第三篇,前两篇文章介绍了如何使用PMD自定义规则。
- 静态代码扫描 (一)——PMD 自定义规则入门
- 静态代码扫描 (二)——PMD 自定义规则实践
阅读本文前,建议先了解一下FindBugs的介绍和使用方法。
准备工作
由于FindBugs是分析编译后的class文件,也就是字节码文件。我们需要了解FindBugs底层的处理机制。根据FindBugs官网文档描述,FindBugs使用了BCEL来分析Java字节码文件。从1.1版本开始,FindBugs也支持使用ASM字节码框架来编写bug探测器。
我们需要下载FindBugs源码版用来新增自定义探测器:findbugs-3.0.1-source.zip
也需要下载FindBugs标准版:findbugs-3.0.1.zip,将findbugs.jar替换为我们的自定义版本后,运行查看结果。
自定义规则
自定义规则思路:
- 明确要定义的规则。
- 分析样例代码的字节码内容。
- 编写探测器。
- 将规则加入规则文件中。
1. 明确要定义的规则
我将以一个非常简单的规则举例:代码中避免使用有类似System.out的输出语句。
package main;
public class TestFindBugs {
public static void main(String[] args) {
System.out.println("123"); //bug
System.err.println("123"); //bug
}
}
2. 分析样例代码的字节码内容
为了更方便的分析样例代码的字节码内容,这里推荐一个Eclipse上用来查看java文件字节码内容的插件:
Bytecode Outline
官网地址:http://andrei.gmxhome.de/bytecode/index.html
在安装完成后,通过Bytecode工具编译后的字节码文件内容:
// class version 51.0 (51)
// access flags 0x21
public class main/TestFindBugs {
// compiled from: TestFindBugs.java
// access flags 0x1
public ()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object. ()V
RETURN
L1
LOCALVARIABLE this Lmain/TestFindBugs; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 5 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "123"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 6 L1
GETSTATIC java/lang/System.err : Ljava/io/PrintStream;
LDC "123"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L2
LINENUMBER 7 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
MAXSTACK = 2
MAXLOCALS = 1
}
通过查看字节码文件分析,我们找到了一些关键语句:
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
GETSTATIC java/lang/System.err : Ljava/io/PrintStream;
3. 编写探测器
我们通过刚才找到的关键语句,结合我们的逻辑,进行探测器编写:
package edu.umd.cs.findbugs.detect;
import org.apache.bcel.classfile.Code;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
/**
* @author yuanwei3-iri
* @category 代码中避免使用有类似System.out的输出语句
*/
public class ForbiddenSystemClass extends OpcodeStackDetector {
BugReporter bugReporter;
public ForbiddenSystemClass(BugReporter bugReporter) {
this.bugReporter = bugReporter;
}
/**
* visit方法,在每次进入字节码方法的时候调用 在每次进入新方法的时候清空标志位
*/
@Override
public void visit(Code obj) {
super.visit(obj);
}
/**
* 每扫描一条字节码就会进入sawOpcode方法
*
* @param seen
* 字节码的枚举值
*/
@Override
public void sawOpcode(int seen) {
if (seen == GETSTATIC) {
if (getClassConstantOperand().equals("java/lang/System")) {
if(getNameConstantOperand().equals("out") || getNameConstantOperand()
.equals("err")){
BugInstance bug = new BugInstance(this, "CJ_SYSTEMCLASS",
NORMAL_PRIORITY).addClassAndMethod(this).addSourceLine(
this, getPC());
bugReporter.reportBug(bug);
}
}
}
}
}
4. 将规则加入规则文件中
我们刚才在编写探测器的时候,已经给定了规则的名称CJ_SYSTEMCLASS
。现在我们需要将这个规则添加在配置文件中。
配置findbugs.xml:
配置message.xml:
Default FindBugs plugin
This plugin contains all of the standard FindBugs detectors.
]]>
代码不能出现System.out
请使用log日志形式打印
]]>
代码不能出现System.out
{1}代码不能出现System.out,请使用log形式输出
不能使用System.out和System.err,请使用log
]]>
影响性能的输出System.out
规则添加完成后,重新打包findbugs.jar:
mvn clean install -Dmaven.test.skip=true
打包成功后,在可运行版本的findbugs中替换原来的/lib/findbugs.jar
执行findbugs命令,扫描样例文件的class文件,查看运行结果:
在扫描结果中,可以看出确实扫描到了我们设定的问题语句。
使用FindBugs自定义规则成功!
参考文献
IBM.FindBugs,第 1 部分: 提高代码质量
IBM.FindBugs,第 2 部分: 编写自定义检测器
百度文库.用Eclipse自带插件创建自定义findbugs检测器
360Qtest团队公众号
关注公众号,第一时间收到我们推送的新文章~