Navigating the call graph

💡 原文中文,约8300字,阅读约需20分钟。
📝

内容提要

本文介绍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 来排除测试类中定义的方法,因为测试方法通常由测试运行器反射调用。

🏷️

标签

➡️

继续阅读