Navigating the call graph
内容提要
本文介绍CodeQL中用于分析Java程序调用图的类:Callable(可被调用的方法或构造器)和Call(调用点),以及导航谓词getCallee、getCaller和polyCalls。通过示例展示如何编写查询查找未使用的方法,并逐步优化查询,排除库代码、类初始化器、finalize方法、公共方法、默认构造器及测试类,以减少误报。
延伸解读
静态解析与动态分派的差异
文章指出,getCallee返回的是静态解析的调用目标,而实际运行时可能因方法覆盖而调用不同的方法。例如,Client.main中对getX的调用,getCallee返回Super.getX,但实际执行的是Sub.getX。理解这一差异对于分析多态行为至关重要,尤其是在查找未使用的方法时,需使用polyCalls来覆盖动态分派的情况。
排除隐式调用与特殊模式
查询未使用的方法时,需排除类初始化器(<clinit>)和finalize方法,因为它们由JVM隐式调用。此外,非公共默认构造函数在单例模式中故意不被调用,也应排除。这些特殊模式若不处理,会产生大量误报,影响查询结果的准确性。
反射与测试框架的挑战
反射调用难以静态分析,但JUnit等测试方法由测试运行器反射调用,CodeQL通过识别TestClass来过滤。这提示在分析未使用代码时,需考虑框架的隐式调用机制,否则会误报。实际项目中,反射和框架的使用广泛,需结合具体上下文调整查询。
Q&A
CodeQL中如何表示Java程序的调用图?
CodeQL的Java库提供了两个抽象类:Callable和Call。Callable是可被调用的方法或构造器,Call是调用点,包括方法访问、类实例表达式、this/super构造器调用等。
CodeQL中Call类的getCallee和getCaller谓词有什么作用?
getCallee返回调用点静态解析到的Callable,getCaller返回包含该调用点的Callable。注意getCallee对于实例方法可能返回静态类型的方法,而运行时可能调用覆盖版本。
CodeQL中calls和polyCalls谓词有什么区别?
calls检查是否包含直接调用目标方法的调用,polyCalls检查是否可能调用目标方法,包括通过覆盖间接调用。polyCalls更全面,用于判断方法是否可能被调用。
如何用CodeQL查找未使用的方法?
使用查询:import java; from Callable callee where not exists(Caller caller | caller.polyCalls(callee)) select callee。该查询找出没有被任何其他方法直接或间接调用的方法。
在查找未使用方法时,为什么要排除类初始化器和finalize方法?
类初始化器(<clinit>)在类加载时隐式调用,finalize方法在对象被垃圾回收时隐式调用,它们都没有显式调用点,因此应排除以避免误报。
在查找未使用方法时,为什么要排除公共方法和默认构造器?
公共方法可能是外部API入口点,默认构造器(尤其是私有)常用于单例模式等,它们不被调用是设计意图,因此应排除以减少误报。
如何排除JUnit测试类中的方法,以避免误报?
使用CodeQL的TestClass类型,通过条件 not callee.getDeclaringType() instanceof TestClass 来排除测试类中定义的方法,因为测试方法通常由测试运行器反射调用。