近年来静态程序分析已成为保障软件可靠性、安全性和高效性的关键技术之一. 指针分析作为基 础程序分析技术为静态程序分析提供关于程序的一系列基础信息,例如程序任意变量的指向关系、变量 间的别名关系、程序调用图、堆对象的可达性等. 介绍了 Java 指针分析的重要内容:指针分析算法、上下文 敏感、堆对象抽象、复杂语言特性处理、非全程序指针分析,特别是对近年来指针分析的研究热点选择性 上下文敏感技术进行了梳理和讨论.
目录
3 堆对象抽象
4 复杂语言特性处理
4.1 反 射
4.2 本地代码
4.3 异 常
4.4 invokedynamic 与 Lambda 表达式
3 堆对象抽象
目前,Java 指针分析使用最广泛的堆抽象技术 是创建点抽象(allocation-site abstraction),它用程序中 的每个创建点 i(即对象创建语句,如 i: x = new T()) 表示动态运行时所有由 i 创建出来的对象 . 对 应 Heap()函数(见表 3 与表 5)的定义为 Heap(i) = i. 创 建点抽象具有良好的精度,几乎所有主流 Java 指针 分析框架都支持这种堆抽象.
虽然创建点抽象精度良好,但一些复杂的 Java 程序包含大量的创建点,使用创建点抽象会在指针 分析过程中产生大量对象,造成很大的开销. 主流的 指针分析框架都会提供对一些特定对象按类 型合并的功能,如对于 StringBuilder 或 StringBuffer 类 型的对象、字符串常量等对象,将所有同类型的对象 合并抽象成一个对象. 这些对象在程序中通常有许 多创建点,因此合并这些创建点对应的对象之后可 以对指针分析效率带来一定提升.
复杂的 Java 程序会包含数量巨大的访问路径. 此外,程序中的循环引用理论上可形成数量无限的 访问路径,如语句 x.f = x 可使得 x,x.f,x.f.f,x.f.f.f…都 是有效的访问路径,导致指针分析无法穷举. 因此, 实际使用访问路径时,通常也会采用类似上下文敏 感的 k-limiting 技术,即对访问路径中包含的字段数 量设置一个上限 k,长度超过 k 的访问路径则被合并 到相同前缀的访问路径,例如 k=1 时,x.f*表示所有 以 x.f 开头的访问路径,包括 x.f,x.f.g,x.f.h 等. 由于访 问路径可以在一些情况下方便地做强更新(strong update),因此目前主要是一些流敏感分析使用该技术.
4 复杂语言特性处理
4.1 反 射
Livshits 等人提出借助指针分析解析反射关 键 API(如 Class.forName()、 Class.getMethod())的 字 符串参数进而分析出反射调用的副作用. 具体而言, 该反射分析与指针分析同时运行并互相依赖,在此 过程中,反射关键 API 字符串参数(如图 3 中的 clsName 与 mtdName)的指针集发生变化时,指针分析会触发 反射分析,mtdName 根据指针集中的字符串常量来分 析反射调用的行为,并将其相应的副作用反馈给指 针分析. 然而该分析只能处理反射参数指向字符串 常量的情况,对于其它字符串非常量的情况均无法 分析(唯一例外是 Class.newInstance()的返回值被立即进行向下类型转换(downcasting)这一特定情形,该 技术可以利用类型转换的信息对 Class.newInstance() 的副作用进行推导).
在 Elf 的 基 础 上 , Li 等 人 提 出 集 体 推 导 (collective inference)与懒惰堆建模(lazy heap modeling) 的技术,并形成新的反射分析 Solar. 集体推导在 Elf 的基础上进一步增强了推导能力. 懒惰堆建模用于 分 析 由 反 射 调用 Class.newInstance()或 Constructor. newInstance()创建但具体类型在创建点未知的堆对 象. 对于这类对象,Solar 将其传播到程序中使用它们 的位置,如向下类型转换,或 Method.invoke()、Field. get()、Field.set()的反射调用点等,并更充分地利用 程序中这些位置的类型信息以分析反射创建对象的 具体类型. 此外,Solar 也能识别出分析不准确的反射调用点,因 此可以帮助用户添加轻量级注解就可以满足他们的 在可靠性、分析效率等方面的需求.
4.2 本地代码
Java 是一门运行在虚拟机(Java Virtual Machine) 之上的语言,虚拟机封装了不同平台的功能并支持 一套统一的 Java 语言规范,这使得 Java 程序具有良 好的平台无关性因而可以跨平台执行. 然而,在一些情况下,例如 Java 程序需要直接与底层平台交互或 实现性能攸关的功能时,Java 程序也需要调用能直 接运行在宿主平台上的代码,即本地代码(通常由 C/C++实现). Java 提供了 native 关键字,用于在 Java 程序中声明本地方法(native method),例如 java.lang.
System 中的 arraycopy()方法:
class System { …public static native void arraycopy(Object src,int srcPos, Object dest, int destPos, int length);… }
具体来说 , JNI[71] 提供了一系列用于回 调 Java 方 法 的 API, 并且与具体回调的 Java 方法的签名紧密相 关,进而在该工作中提出扫描本地代码中的字符串常量,记录 JNI 调用的参数与字符串常量的对应关系, 结合传统 Java 分析工具对方法签名的分析,推断该 JNI 调用回调的是哪个 Java 方法. 实验结果表明,该工作可以快速有效地推测出 XCorpus 基准程序库和 Chrome 等真实应用中本地代码的回调方法.
4.3 异 常
异常(exception)是 Java 程序中重要而不可忽视 的控制流 . 准确的异常分析需要得知程序的 throw 语句以及方法调用可能抛出异常的具体类型,这需 要依赖于指针分析的结果,反之,异常分析的结果 也能影响指针分析,下面用图 4 中的代码片段进行 说明.
针对指针分析与异常分析相互耦合的特点 , Bravenboer 等人提出将指针分析与异常分析结合 进行的技术. 具体而言,异常分析处理 throw e 语句时 使用指针分析对于变量 e 的结果;而异常分析处理 catch 语句时,会推断哪些异常对象被对应的 catch 语 句捕获,并将捕获的异常对象注入到对应 catch 语句 的异常形参变量的指针集中,反馈给指针分析. 此外, 指针分析在构建调用图时,也会触发异常分析,使其 能够沿着调用边向调用点传播目标方法内没有被捕 获的异常. 通过文献 的方式,实现了指针分析与 异常分析的协同式迭代计算. 实验显示,与以往不精 确的异常分析相比,该技术对于 try-catch 异常对象 流的分析精度有显著提升.
4.4 invokedynamic 与 Lambda 表达式
Soot是目前最流行的 Java 分析框架之一,它 选择在前端生成中间代码的过程中将程序内与 Lambda 表达式相关的 invokedynamic 指令转换为非 invokedynamic 的调用语句,从而避免分析invokedynamic 指令. 然而这种方式损失了一部分原程序中的语义, 并且 Soot 的指针分析也不支持对于 invokedynamic 指 令的分析.