深入源码:解析SpotBugs (7)写一个 Detector要如何入手 ?


前面的几篇文章介绍了 Spotbugs 的流程,如何进行 Bug 处理的,编写 plugin 的方式,以及字节码相关知识的简介。本篇开始,我们来着重从源码看一下,如何表达我们的检测意愿。

材料来源于 SpotBugs 的测试用例 spotbugs-test 工程。

Row Detector

行的检测是最多但是相对来说比较简单的部分。 以下面这个 String.indexOf 检测为例,推荐使用 int 参数,也就是单字符参数。

  • Use whenever possible String.indexOf(int) instead of String.indexOf(String),or String.lastIndexOf(int) instead of String.lastIndexOf(String).

该检测的原因是要测试
在这里插入图片描述
相比较而言, indexof(int) 会更高效一点。
在这里插入图片描述
目标字节码:
在这里插入图片描述
getClassConstantOperand 调用类 “java/lang/String”
getNameConstantOperand 调用方法 “lastIndexOf” “indexOf”
getSigConstantOperand 方法参数 “(Ljava/lang/String;)I” 参数String,返回int; (“(Ljava/lang/String;I)I” 参数 String,int 返回 int。
seen 在这里是操作码的意思,可以参考 Const 中定义的常量值。

@Override
    public void sawOpcode(int seen) {
        if (seen == Const.INVOKEVIRTUAL && stack.getStackDepth() > 0 && "java/lang/String".equals(getClassConstantOperand())) {

            boolean lastIndexOf = "lastIndexOf".equals(getNameConstantOperand());
            if (lastIndexOf || "indexOf".equals(getNameConstantOperand())) {

                int stackOff = -1;
                if ("(Ljava/lang/String;)I".equals(getSigConstantOperand())) { // sig: String
                    stackOff = 0;
                } else if ("(Ljava/lang/String;I)I".equals(getSigConstantOperand())) { // sig: String, int
                    stackOff = 1;
                }
                if (stackOff > -1) {
                    OpcodeStack.Item item = stack.getStackItem(stackOff);
                    Object o = item.getConstant();
                    if (o != null && ((String) o).length() == 1) {
                        bugReporter.reportBug(new BugInstance(this, lastIndexOf ? "IIO_INEFFICIENT_LAST_INDEX_OF" : "IIO_INEFFICIENT_INDEX_OF",
                                LOW_PRIORITY).addClassAndMethod(this)
                                .describe(StringAnnotation.STRING_MESSAGE).addCalledMethod(this).addSourceLine(this));
                    }
                }
            }
        }
    }

整个实现是很简单的检测调用堆栈栈深 > 0 的调用位置,符合匹配方法的,抛出 bug。

Method Detector

以 SynchronizeAndNullCheckField 为例。传入synchronized块的锁引用如果是null的话则执行到这个synchronized块时会立即抛出NPE。也就是说同步和空值检测发生在同一个属性上,判断没有必要。
目标代码:
在这里插入图片描述
Detector:

@Override
    public void sawOpcode(int seen) {
        switch (currState) {
        case 0:
            if (seen == Const.GETFIELD || seen == Const.GETSTATIC) {
                syncField = FieldAnnotation.fromReferencedField(this);
                currState = 1;
            }
            break;
        case 1:
            if (seen == Const.DUP) {
                currState = 2;
            } else {
                currState = 0;
            }
            break;
        case 2:
            if (seen == Const.ASTORE || seen == Const.ASTORE_0 || seen == Const.ASTORE_1 || seen == Const.ASTORE_2 || seen == Const.ASTORE_3) {
                currState = 3;
            } else {
                currState = 0;
            }
            break;
        case 3:
            if (seen == Const.MONITORENTER) {
                currState = 4;
            } else {
                currState = 0;
            }
            break;
        case 4:
            if (seen == Const.GETFIELD || seen == Const.GETSTATIC) {
                gottenField = FieldAnnotation.fromReferencedField(this);
                currState = 5;
            } else {
                currState = 0;
            }
            break;
        case 5:
            if ((seen == Const.IFNONNULL || seen == Const.IFNULL) && gottenField.equals(syncField)) {
                BugInstance bug = new BugInstance(this, "NP_SYNC_AND_NULL_CHECK_FIELD", NORMAL_PRIORITY).addClass(this)
                        .addMethod(this).addField(syncField).addSourceLine(this);
                bugReporter.reportBug(bug);
            } else {
                currState = 0;
            }
            break;
        default:
            currState = 0;
        }
    }

如果注意到,就能发现,switch 是对currState判断的,而且顺着case依次递增加1,或者清空为0. 这种技巧在 Spotbugs 的 Detector 文件夹里有非常广泛的应用,通过状态层层递进来实现逻辑判断。
定位逻辑: monitorEntor 之后 调用了获取 Field ,且对 Field 判空,抛出 Bug。

对于面向 Class 的 Detector 范围更大,但是基础是一样的,可以参考官方代码,本系列不再赘述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值