本文是對於南京大學李樾和譚添老師開設的《軟件分析》課程視頻的筆記總結。相對應的視頻在可以再B站上觀看。
1.Motivation
上節回顧
在筆記3里我學習了CHA有關的概念和用法,用一個例子來復習一下:
如上圖所示,定義了一個接口Number,然后有三個類繼承了該接口,實例化了一個Number對象n,那么利用CHA可以找到3個調用目標(因為n是Number類型,所以要去Number和所有Number的子類中尋找對應的調用目標),對於常量傳輸,x的值是NAC,因為調用了三個方法,返回的值可能是{0,1,2},很明顯這個結果是不准的因為實際上只會調用One中的方法,如果使用指針分析的方法則可以得到更精確的結果。
2.Introduction to pointer analysis
指針分析是最基礎的靜態分析,它最早的研究是從40多年之前開始的,它回答了程序中的指針指向哪個內存的問題,Java語言中的指針分析指的是一個指針指向程序中的哪個對象(Object)的問題.通常指針分析是一個may-analysis,分析的結果通常是一個指針可能指向哪些對象。
我們用一個實例來解釋指針分析的內容:
如上圖所示,對於左邊的程序代碼,通過分析前兩句代碼我們可以得到變量a指向new A(),變量x指向new B(),對於a.setB()方法很明顯是指向A類中的對應方法,而對於setB()方法,方法中的this會指向new A()因為foo()中是a調用的setB()方法,而方法中的形參b則是由實參x傳給他的,指向new B();因此對於setB方法中的“this.b = b;”,是new A.b指向了new B。
指針分析的輸入就是一個程序代碼,然后輸出一個指向關系。
注意
指針分析和別名分析(aliases)有很多相似之處,但是指針分析並不等於別名分析,二者區別如下:
-
指針分析解答的是一個指針可能指向哪個對象的問題
-
別名分析解答的是兩個指針是否能指向同一個對象的問題,如果是就認為二者互為別名。如下圖所示:
- 別名分析可以通過指針分析推導出來。
指針分析的應用
- 可以用來計算其他基本信息(別名分析,調用圖...)
- 編譯優化
- 找Bug
- 安全性分析
- 等等......
指針分析是最基礎的靜態分析之一,也是很多其他分析的基礎。
3.Key factors of pointer analysis
指針分析是一個非常復雜的系統,指針分析有兩個指標即精度(precision)和速度(efficiency),我們需要根據實際情況在這兩者中進行取舍。為了方便做分析,我們需要了解一些影響指針分析的關鍵要素。有代表性的關鍵要素的簡圖如下:
3.1Heap Abstraction堆抽象
解決在指針分析中對內存建模的問題,在程序動態執行時,堆當中對象的數量理論上是無窮無盡的,但是靜態分析沒辦法處理這個問題,所以為了保證指針分析可以終止,我們采用了堆抽象技術,將無窮的具體對象抽象成有限的抽象對象。
如上圖所示,左邊是動態的執行過程,右邊使我們的靜態分析,我們將有共性的部分動態對象抽象成一個靜態對象,這樣就可以限制靜態分析對象的個數。
堆抽象有很多復雜的分支,如下圖所示:
從圖上可知堆抽象大概有兩個流派,我們只學習Allocation-Site技術,因為它最常見也最常被使用。
Allocation-Site的思想就是將動態對象抽象成它們的創建點(Allocation-Site),它為程序中沒一個創建點抽象出一個抽象點來表示在這個點創建的所有動態對象。一個簡單示例如下:
如圖所示,我們用了一個for循環創建了3個動態對象,在這里為了方便靜態分析,我們用創建點O2來抽象代表這三個動態對象。
3.2Context sensitivity上下文敏感
解答了指針分析中如何調用上下文進行建模的問題。分為兩種就是上下文敏感和上下文不敏感。
如上圖所示,上下文敏感的思想就是在靜態分析中會模擬動態分析中上下文的變化,對於一個方法不同的上下文分別分析。下圖中因為兩種方法是由不同的對象調用的(a和b),因此我們將它們區分為兩種,即圖中的Context1和Context2,靜態分析中也會分別分析,
上下文不敏感就是不會將不同上下文區分開,如下圖所示就是將不同上下文都采用同一個方法,這樣會丟失精度。
我們先從上下文不敏感開始分析。
3.3Flow Sensitivity流敏感
即解決對控制流分建模問題,跟上下文敏感一樣,流敏感也分為流敏感和流不敏感兩種。
流敏感就是對於程序中語句的順序是敏感的,目前我們學到的所有數據流分析都是流敏感的。如下圖所示,流敏感會記錄每一步各個值的變化。
流非敏感就是會忽略掉語句之間的順序(控制流順序),一個map就記錄了程序中所有的可能信息,對於上圖中的代碼,分析結果為:
因此流不敏感會丟失精度,要注意流敏感對不同的編程語言重要程度是不一樣的,C中很有效,JAVA等則相對不明顯,因此我們分析中多實用流不敏感的方式。
3.4Analysis Scope
即解決要分析程序哪個部分的問題,分為whole-program和demand-driven兩種。
-
whole-program就是分析出程序中所有指針的指向
-
demand-driven就是分析出程序中特定某些部分指針的指向,舉個例子就是在上面的程序中,如果我們要特定的分析第五行的代碼,那么我們只需要z的信息就夠了。通常來說驅動分析工作量相對於全程序分析要小,但是實際應用不一定更好。
-
我們優先學習全程序分析
4.Concerned Statements
指針分析的目標只關注能直接影響到指針指向的語句。
Java中的指針:
- Local variable:x
- Static field:C.f(有時候被稱為全局變量)
- instance field:x.f(對象的field)
- Array element:array[i] (靜態分析往往沒辦法確定下標,一般是將整個array存到一個field中)如下圖,就是創建了一個數組對象,存儲和讀取都是對這個對象進行的。
直接影響到指針指向的語句
一般分為以下五種語句:
- New: x = new T()
- Assign: x = y
- Store: x.f = y
- Load: y = x.f
- Call: r = x.k(a,...)
- Static call C.get()
- Special call super.foo()
- Virtual call x.get()
5.指針分析的規則
為了循序漸進,在指針分析中,我們會先分析上面提到的五種語句中的前四種。
5.1指針分析的域和相對應的記法
如上圖所示,用pt來表示程序中的指向關系(映射),由指針指向相應的指針集。#08 8530
5.2規則
如上圖,該圖采用了推導式的形式,橫線上面的為條件,橫線下面的為結論,橫線上為空則代表無條件。
New
對於創建對象語句,我們就將new T()對應的對象oi加入到x的指針集中,如下圖所示:
Assign
經過上圖中語句,就是將y對應的指針集加入到x對應的指針集中。
Store
就是讓OI的field指向Oj,實際上跟Assign很像。
Load
實際上就是Store的反操作。