【南大软件分析-学习笔记】Lecture 8-Point Analysis

0. 前言

接下来几节开始指针分析的学习。

1. Motivation

首先介绍一下指针分析的动机,我们在上节课的学习中已经知道了 CHA 和 Point Analysis 是两种常见的过程间分析方法,CHA 相比 Point Analysis 具有更快的速度,但是精度更低,这是怎么回事呢?我们结合下面这个简单的例子来展示一下。

在上图的程序中,我们要寻找 n 的方法在哪里定义的,进而确定 x ,采用 CHA 算法时,它会从对象 n 对应的接口 Number 中寻找 get() 的定义,找不到后会从接口 Number 的子类中寻找,遵循 safe-approximation 原则,CHA 分析的结果应该是 {0, 1, 2} ,如果我们分析的是常量传播的话,我们最终得到的分析结果就是 NAC,包含两个 false positives。倘若我们知道 n 是 One 类型的创建的话,就可以很容易的分析出返回值应该是 1,这就需要用到指针分析了。

2. Intorduction to Pointer Analysis

2.1 What is Pointer Analysis?

首先还是先简单介绍一下指针分析的几个特征吧:

  1. A fundamental static analysis:计算一个指针能指向哪个内存位置;
  2. For OO programs (focus on Java):计算一个指针(variable or field)指向哪个对象;
  3. Regarded as may analysis:计算指针指向的过拟合的对象集,也就是说这个指针可能会指向哪些对象?

需要注意的一点是,不同于 C ,在 Java 中,我们不能显式的对指针进行操作,Java 的指针被封装在代码底层,在 Java 中我们分析的是局部变量、字段等保存的引用可能指向哪些对象,它们存储的不是数据而是地址的变量,例如 T x = new T() 中的 x 存储了新创建的一个类型为 T 的对象的地址,就可以认为是一个指针。“指针分析”是程序分析领域的通用名称,并不要求语言提供 C / C++ 风格的指针语法。

下面我们还是用一个简单的程序来分析一下在这个程序中的变量(variable)和对象(object)的指向关系:

void foo() {
   A a = new A();
   B x = new B();
   a.setB(x);
   B y = a.getB();
}

class A {
   B b;
   void setB(B, b) {
       this.b = b;
   }
   B getB() {
       return this.b;
   }
}

各变量以及字段的指向关系(points-to relations)是这样的:

2.2 Pointer Analysis & Alias Analysis

接下来分析两个比较容易混淆的分析方法:

  • Pointer analysis(指针分析):一个指针指向哪些对象;
  • Aias analysis(别名分析):哪两个指针指向同一个对象。

其中,别名的信息可以通过 points-to relations 得到。

2.3 Applications

指针分析的应用也是十分的广泛:

  1. 辅助 call graph / aliases 等的建立;
  2. 编译器的优化;
  3. 查找 bug;
  4. 安全性分析 ...

3. Key Factors of Pointer Analysis

指针分析是一个复杂的系统,存在许多会影响其精确度(precision)和效率(efficiency)的因素,如下图所示,我们会依次进行介绍:

3.1 Heap abstraction

Java 在运行时会创建很多的对象放在堆中以供调用,堆抽象就是对其进行建模。

在程序的动态执行中,由于循环(loops)和递归(recursion)操作的存在,短时间内来看堆对象的数量可能是无上限的,为了确保终止,堆抽象会将动态执行时无限的域映射到静态分析时有限的域,再进行静态分析。

学术界将堆抽象方法按照以下方法进行分类:

这里我们介绍最常用的一种堆抽象方法:allocation-site abstraction

具体而言,会将具体对象根据它们的分配点(allocation sites)进行抽象,每个分配点对应一个抽象对象,这个 abstrac object 会用来表示被分配的所有具体对象。

比如说在下图的程序中,我们就将 for 循环中循环创造的几个对象抽象为一个整体的对象。

3.2 Context sensitivity

接下来是上下文敏感性,上下文敏感性关注的是如何对函数调用上下文进行建模,分为:

  1. context-sensitive
  2. context-insensitive

在程序中一个方法可能会被多次调用,上下文敏感性对于每次调用方法的时候都会分别记录那次方法的上下文;而上下文不敏感则会归并的处理同一方法的多次调用的上下文,每个方法只分析一次,如下图所展示。

很显然,前者比后者的精确度更高。

3.3 Flow sensitivity

接下来是流敏感性。同样也是分为 flow-sensitive 和 flow-insensitive,前者会关注程序中语句的执行顺序,在每个程序点处维护一个指针指向关系映射;后者会忽略控制流顺序,将程序视作无序的语句集合,为整个程序维护一个指针指向关系映射。

我们之前所学习的几种数据流分析都是流敏感分析,在流敏感分析中很多时候域中的指向对象不可替换,因此开销很大,流不敏感的分析在此时就可以发挥用处,下面简单展示了一小段程序在 flow-sensitive 和 flow-insensitive 中的分析结果。

3.4 Analysis scope

最后是分析范围,分为:

  • whole-program:整体分析,顾名思义,分析整段程序,计算出程序中的所有指向关系;
  • demand-driven:按需分析,只计算影响到特定区域(sites of interest)的指针的指向关系。

最终,在教学中,我们选择的情况如下:

4. Concerned Statements

最后来看我们需要分析哪些语句,顾名思义,既然是指针分析,那么我们肯定只需要关注会影响指针的语言(pointer-affecting statements),像 if-else、for / while 这样的循环语句、分支语句自然也就不用分析了。

在 Java 中,常见的指针有四种类型:

  • Local variable:x,本地变量,也叫局部变量;
  • Static field:C.f,静态域,也叫全局变量;
  • Instance field:x.f,实例域;
  • Array element:array[i],数组元素。

其中,对于数组元素而言,在做指针分析会忽略它的索引,将全部数组元素当作数组对象的单一域 arr,如下图所示:

具体的,影响指针的 statements 应该是以下几个:

  1. New:如 x = new T( );
  2. Assign:如 x = y;
  3. Store / Load:如 x.f = y, y = x.f;
  4. Call:如 r = x.k(a, ...),又分为 static call, special call 和 virtual call(重点关注)。
posted @ 2026-10-21 18:54  乙烯酷  阅读(7)  评论(0)    收藏  举报