这是静态代码扫描系列文章的第三篇,前两篇文章介绍了如何使用 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 替换为我们的自定义版本后,运行查看结果。
自定义规则思路:
我将以一个非常简单的规则举例:代码中避免使用有类似 System.out 的输出语句。
package main;
public class TestFindBugs {
public static void main(String[] args) {
System.out.println("123"); //bug
System.err.println("123"); //bug
}
}
为了更方便的分析样例代码的字节码内容,这里推荐一个 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 <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()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;
我们通过刚才找到的关键语句,结合我们的逻辑,进行探测器编写:
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 yuanwei
* @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);
}
}
}
}
}
我们刚才在编写探测器的时候,已经给定了规则的名称CJ_SYSTEMCLASS
。现在我们需要将这个规则添加在配置文件中。
配置 findbugs.xml:
<FindbugsPlugin>
<Detector class="edu.umd.cs.findbugs.detect.ForbiddenSystemClass" speed="fast" reports="CJ_SYSTEMCLASS" hidden="false" />
<BugPattern abbrev="CJ_SYSTEMCLASS" type="CJ_SYSTEMCLASS" category="PERFORMANCE" />
</FindbugsPlugin>
配置 message.xml:
<?xml version="1.0" encoding="UTF-8"?>
<MessageCollection>
<Plugin>
<ShortDescription>Default FindBugs plugin</ShortDescription>
<Details>
<![CDATA[
<p>
This plugin contains all of the standard FindBugs detectors.
</p>
]]>
</Details>
</Plugin>
<Detector class="edu.umd.cs.findbugs.detect.ForbiddenSystemClass">
<Details>
<![CDATA[
<p>代码不能出现System.out
<p>请使用log日志形式打印
]]>
</Details>
</Detector>
<BugPattern type="CJ_SYSTEMCLASS">
<ShortDescription>代码不能出现System.out</ShortDescription>
<LongDescription>{1}代码不能出现System.out,请使用log形式输出</LongDescription>
<Details>
<![CDATA[
<p>不能使用System.out和System.err,请使用log</p>
]]>
</Details>
</BugPattern>
<BugCode abbrev="CJ_SYSTEMCLASS">影响性能的输出System.out</BugCode>
</MessageCollection>
规则添加完成后,重新打包 findbugs.jar:
mvn clean install -Dmaven.test.skip=true
打包成功后,在可运行版本的 findbugs 中替换原来的/lib/findbugs.jar
执行 findbugs 命令,扫描样例文件的 class 文件,查看运行结果:
在扫描结果中,可以看出确实扫描到了我们设定的问题语句。
使用 FindBugs 自定义规则成功!
IBM.FindBugs,第 1 部分: 提高代码质量
IBM.FindBugs,第 2 部分: 编写自定义检测器
百度文库.用 Eclipse 自带插件创建自定义 findbugs 检测器
关注公众号,第一时间收到我们推送的新文章~