You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写检测对象未关闭场景的FindBugs自定义检测器

实现FindBugs自定义检测器检测未安全关闭的GlobalObject

嘿,作为折腾过不少FindBugs自定义检测器的老鸟,我来给你拆解怎么实现这个场景的检测~

首先咱们明确要抓的问题:当你用Global.open()拿到GlobalObject后,如果直接在计算代码后调用Global.close(),一旦计算过程抛出异常,close就会被跳过,导致资源泄漏。咱们要找出这种没有用try-finally或者try-with-resources保证关闭的代码。

核心思路参考

你提到的OS_OPEN_STREAM检测器(就是检测流没关闭的那个)逻辑完全可以复用!它的核心是跟踪资源的创建→检查资源是否在所有退出路径(包括异常)都被关闭,咱们只要把“流”换成GlobalObject,把“打开/关闭流的方法”换成Global.open()和Global.close()就行。

具体实现步骤

1. 识别Global.open()的调用

首先要在检测器里捕获Global.open(String)这个静态方法的调用:

  • 在sawOpcode方法里,判断当前 opcode 是否是INVOKESTATIC
  • 通过getClassConstantOperand()、getNameConstantOperand()和getSigConstantOperand()匹配方法的类名、方法名和签名,确认是Global.open(String)
  • 记录这个方法返回的GlobalObject实例(可以用FindBugs自带的InstanceTracker或者自己维护一个存储结构,跟踪每个实例的赋值变量和位置)

2. 跟踪实例的关闭行为

接下来要检查这个GlobalObject实例的后续使用:

  • 捕获Global.close(GlobalObject)的调用,判断传入的参数是不是咱们之前跟踪的实例
  • 关键:检查这个close调用是否处于finally块中,或者这个实例是否用了try-with-resources语法声明(也就是在try()括号里初始化)
  • 用控制流图(CFG)分析:如果存在执行路径(比如异常路径)会跳过close调用,那就标记为bug

3. 适配try-with-resources的情况

如果你的SimilarAutoCloseable实现了AutoCloseable接口,那还要识别try-with-resources的语法:

  • 检查try语句的资源声明部分,是否有SimilarAutoCloseable的实例初始化
  • 这种情况因为JVM会自动在finally里调用close(),所以不需要标记为bug

关键代码片段参考

这里给你贴点核心的检测器代码片段(基于FindBugs的框架):

@Override
public void sawOpcode(int seen) {
    // 捕获Global.open(String)的调用
    if (seen == INVOKESTATIC) {
        String className = getClassConstantOperand();
        String methodName = getNameConstantOperand();
        String methodSig = getSigConstantOperand();
        if ("Global".equals(className) && "open".equals(methodName) && "(Ljava/lang/String;)LGlobalObject;".equals(methodSig)) {
            // 跟踪返回的GlobalObject实例
            Object instance = getStackValue(getStackDepth() - 1);
            trackResourceInstance(instance, getPC());
        }
    }
    // 捕获Global.close(GlobalObject)的调用
    else if (seen == INVOKESTATIC && "Global".equals(getClassConstantOperand()) && "close".equals(getNameConstantOperand())) {
        Object instance = getStackValue(getStackDepth() - 1);
        if (isTrackedResource(instance)) {
            // 检查这个close是否在finally块中
            boolean inFinally = isInFinallyBlock();
            if (!inFinally) {
                // 检查是否是try-with-resources的情况,如果不是就报告bug
                if (!isInTryWithResources(instance)) {
                    reportBug(new BugInstance(this, "GLOBAL_OBJECT_NOT_SAFELY_CLOSED", HIGH_PRIORITY)
                            .addClass(this.getClass())
                            .addMethod(this.getMethod())
                            .addSourceLine(this));
                }
            }
            // 标记该实例已被安全关闭
            untrackResourceInstance(instance);
        }
    }
    // 方法结束时,检查是否有未安全关闭的跟踪实例
    else if (seen == RETURN || seen == ATHROW) {
        checkForUnclosedResources();
    }
}

测试用例一定要写

别忘写测试用例验证检测器的正确性:

  • 需要检测出bug的坏代码:
    public void badExample(String id) {
        GlobalObject ob = Global.open(id);
        // 这里的计算操作可能抛出异常
        doCalculation(ob);
        Global.close(ob); // 异常时不会执行
    }
    
  • 不会触发bug的好代码:
    public void goodExample1(String id) {
        GlobalObject ob = Global.open(id);
        try {
            doCalculation(ob);
        } finally {
            Global.close(ob); // 无论是否异常都会执行
        }
    }
    
    public void goodExample2(String id) {
        try (SimilarAutoCloseable ob = new SimilarAutoCloseable(id)) {
            doCalculation(ob); // JVM自动关闭资源
        }
    }
    

内容的提问来源于stack exchange,提问作者Amy Liu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:55:52