如何编写检测对象未关闭场景的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
相关产品推荐
相关产品推荐

