【转】静态代码扫描 (三)——FindBugs 自定义规则入门

这是静态代码扫描系列文章的第三篇,前两篇文章介绍了如何使用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 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:

<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

image

打包成功后,在可运行版本的findbugs中替换原来的/lib/findbugs.jar
执行findbugs命令,扫描样例文件的class文件,查看运行结果:

image
image

在扫描结果中,可以看出确实扫描到了我们设定的问题语句。
使用FindBugs自定义规则成功!

参考文献

IBM.FindBugs,第 1 部分: 提高代码质量
IBM.FindBugs,第 2 部分: 编写自定义检测器
百度文库.用Eclipse自带插件创建自定义findbugs检测器

作者:Qtest
链接:https://www.jianshu.com/p/dc04ce83159e
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • 在我们平时项目开发中,经常会写一些不严谨的代码或者一些比较低级的错误代码,但是这些错误往往很难被发现,这样就导致了...
    青果果阅读 1,478评论 0 7
  • Android静态代码分析 [TOC] 最佳项目里面来了很多新的小伙伴,然后每个人的代码风格还不一样,虽然有代码风...
    流水不腐小夏阅读 2,270评论 0 5
  • 第九章:如何更快速的创造成果? 你不是因为神性需要而对神性说,请原谅你是给说给自己听。让爱流动从自己的内在做起,对...
    成长的路上有您结伴而行阅读 96评论 0 0
  • 想到我还有那么多事情没做,在不做就没有机会做了,我就很难安。
    游学者Joey阅读 94评论 0 0
  • 也没什么 一切如旧 除了你 生活还是它原来的样子 波峰低谷,过山车罢了 起初的兴奋,后来的习惯 最后的厌倦 但总要...
    小黄日记阅读 580评论 0 4