360Qtest团队 静态代码扫描 (三)——FindBugs 自定义规则入门

丁老九 · July 08, 2016 · Last by simple replied at April 25, 2017 · 4891 hits

这是静态代码扫描系列文章的第三篇,前两篇文章介绍了如何使用 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. 明确要定义的规则。
  2. 分析样例代码的字节码内容。
  3. 编写探测器。
  4. 将规则加入规则文件中。

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 <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;

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 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);
                }
            }
        }
    }
}

4. 将规则加入规则文件中

我们刚才在编写探测器的时候,已经给定了规则的名称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 检测器

360Qtest 团队公众号

关注公众号,第一时间收到我们推送的新文章~

共收到 2 条回复 时间 点赞

😀 学习了

静态代码扫描,99% 的测试同学都止步于使用工具阶段了😩

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up