今天分享一篇2020年在PLDI上發表的一篇關於Java企業應用靜態分析的論文——「Static Analysis of Java Enterprise Applications: Frameworks and Caches, the Elephants in the Room」
論文題目中」 the Elephants in the Room」翻譯為「房間裡的大象」,這是英語中的一個隱喻,形容一個明明存在的問題,卻被人刻意的迴避及無視的情形。這也體現了本篇論文關注的是那些被現有靜態分析框架所忽視的,但是在企業應用程式中大量存在的「Frameworks and Caches」(框架和緩存)的靜態分析方法。
1.什麼是靜態分析
在介紹論文之前,我先來介紹一下什麼是靜態分析:靜態分析就是在不運行程序的情況下對源碼進行詞法分析、語法分析、控制流、數據流、指針分析等,來檢測程序是否滿足規範性和安全性等。
靜態分析不同於動態分析,動態分析通常是通過執行程序,根據程序運行返回結果和程序狀態去分析。
靜態分析中有兩個常見的名詞:
soundness:對程序進行了over-approximate過擬合,不會漏報(有false positives誤報)。
completeness:對程序進行了under-approximate欠擬合,不會誤報(有false negatives漏報)。
從圖1中可以清晰的理解這兩個名詞,其中綠色區域表示所有的Truth,也是理想狀態下靜態分析應該找出的所有情況,如果對程序進行了過擬合分析也就是圖中紅色區域(Soundness),它雖然包含了所有Truth不會產生漏報,但是會有誤報存在;同理Completeness沒有包含所有的Truth,但是它不會產生誤報,存在一定的漏報。
圖1 Soundness & Completeness
靜態分析存在的一大問題
靜態分析面臨的一個很大的問題就是很難處理程式語言帶有的動態特性。例如Java中的反射。反射技術就存在很強的動態性,只有在程序運行時才能知道內存中的哪個字節碼文件被反射使用;現有的靜態分析框架對Java中的反射就沒有較好的解決方法。
2.本文所求解的問題
本文主要求解兩個問題:
1.企業應用程式在開發中大量使用框架,框架為了儘可能通用,採取了許多動態技術,例如依賴注入,對於應用程式中的邏輯,框架通常採用註解或xml等配置文檔來實現;這加大了靜態分析的難度,該如何對企業應用程式中使用的框架進行靜態分析。
2.在企業應用程式中會經常用到緩存這一技術,從緩存中讀取數據的操作是很難靜態分析的。例如Spring的視圖對象做緩存,有利於前端解析處理。該如何分析通用、異構的數據結構的緩存。
企業應用程式靜態分析面臨的可擴展性和精確性挑戰:
1.靜態分析的完整性會影響分析的質量,由於框架的高度抽象和可配置性,嚴重影響了靜態分析的完整性。
2.靜態分析的可擴展性和精確性的權衡,如果可擴展性很好那必然會喪失精確性,如果精確性很好,那就相當於是某個具體應用程式的定製產品,可擴展性必然下降。
3.本文貢獻與創新
本文提出了JackEE,一個企業應用程式靜態分析框架,表明對現實企業應用程式進行可擴展、高完整性、高精度的靜態分析是可能的。
本文介紹了用於識別和建模企業應用程式入口點的技術和一般概念。JackEE定義了一個詞彙表,來表達企業框架的行為。
構建sound-modulo-analysis,一種對Java數據結構分析的模型,不像其他模型一個直接丟棄信息,而是通過替換保留了數據結構的全部動態行為但是簡化了靜態分析。
4.企業中常用開發框架的考察
為著眼於如何靜態的對程序進行建模,論文中主要對這些框架進行三個方面的考察。
1.What:應用功能單元之間交互的 關鍵概念,實體和 數據/對象的種類。
2.Where:應用功能的入口點或者內部的連接點。
3.How:用來描述「What」或「Where」的方式,例如:子類型,XML屬性,Java註解等。
Servlet:
Servlet是常用的Web應用程式開發的技術。有Generic Servlets和Http Servlets.等
What:不同類型的servlet,servlet中的request/response對象,filters
Where:生命周期方法如init,destroy等,service,doFilter, doGet, doPost。
How:繼承實現抽象類。
Enterprise Java Beans(EJB):
可以簡化應用開發的一項技術。
What:不同類型的beans,bean客戶端類。
Where:生命周期方法和bean的方法。
How:Java註解或XML配置。
Spring體系:
Spring是Java開發中使用較多的框架。
What:controllers,interceptors,authentication managers,providers,and beans
Where:controller的方法和其他handler類 例如:preHandle,postHandle,authenticate等。
How:Java註解,XML配置,子類繼承。
5.框架建模方法和舉例
在介紹論文中建模方法之前,我先對Datalog語言進行一個簡單的介紹,只有了解了Datalog的一些基本語法,才能理解之後論文中的建模方法。
Datalog簡介:
Datalog語言是一種聲明式程式語言,在靜態分析領域使用非常廣泛,我們常見的SQL語言就是一種聲明式語言。聲明式語言更偏向於去描述「需要做什麼(What to do)」,而不是「怎麼做(How to do)」,它更像是一份規範,更適合人來理解。
Datalog這門語言關心的對象有兩部分,data + logic,其中data是一些列的謂詞(predicate),而logic則是很多規則(rule)。謂詞是陳述(statement)的集合,也可以理解成SQL中的表或者關係(relation);原子(atoms)是Datalog語言中的基本元素,包括關係原子和算術原子,其中關係原子形式記作P(x1, x2, x3),其中P為謂詞名,xn為參數;算術原子attr >= var,其中attr為謂詞中的一個列,var為一個變量或者常量。
規則是邏輯的表達,形式為H <- B1, B2,...,Bn,H為規則的頭部,是規則的結論(consequent),B為規則體,也是規則的前因(antecedent),Bi也被叫作子目標(subgoal),若且唯若前因為真時,結論為真。「,」在規則中充當「and」的作用,「;」在規則中充當「or」的作用,而「!」充當取反的作用。
建模:
首先構造詞彙表,這個詞彙表就是一些關係原子,詞彙表如下:
圖2 基礎關係,框架在此基礎上建模
圖2展示了框架建模詞彙表的一個關鍵概念的例子,既與程序文本和配置相關(例如,Class_Annotation, XMLNode),也與程序語義相關(例如,Interceptor, Servlet),或者與分析語義相關(例如,GeneratedObject, EntryPointClass)。如果碰到一個EntryPointClass或者Controller等,都會觸發進一步的推理。
圖3 程序相關信息,用於框架規範(和進一步分析)。
模擬對象創建規則:
一旦入口點被識別,就需要創建入口點使用的模擬對象。
創建模擬對象的規則如下:
1.給定一個入口點方法m和它的聲明類型C,則為入口點方法m創建一個C類型的接收器模擬對象,靜態分析認為this指向這個模擬對象。
2.給定一個入口點方法m及其帶有索引i和類型T的參數
a)如果應用程式中有T類型的具體子類型,就為程序中每個子類型創建模擬對象。
b)如果應用程式中沒有T的子類型,就找入口點方法中T類型的所有強制類型轉換S,為每個S創建一個模擬對象。
c)遵循每種類型一個模擬對象的原則,確保無論入口點數量如何,分析都保證可伸縮性。
d)靜態分析認為參數i指向上述模擬對象。
建模舉例:
利用圖2的基礎關係和圖3的程序相關信息來對一些程序框架進行建模。
Servlet
首先對Servlet進行建模分析,在Java Servlet API中,所有的Servlet都是javax.servlet.GenericServlet的一個子類型, GenericServlet類位於Servlet類層次結構的頂部,因此可以進行如下建模。
圖4 Servlet建模
另外也認為接收ServletRequest或ServletResponse任意一個對象的也是入口點。
圖5 Servlet入口方法
過濾器是對資源請求和資源響應進行過濾的類,通過實現Filter接口,重寫doFilter方法,過濾器類也是一個通用的入口點。
圖6 Servlet入口類
Spring
將帶有@Controller註解的類作為入口類,帶有@RequestMapping的標記為入口點方法。
圖7 SpringMVC入口類
SpringMvc中攔截器是通過實現HandleInterceptor接口或繼承HandleInterceptorAdapter類實現的。
圖8 攔截器入口類
Spring Security中自定義的身份認證服務通過XML文檔進行配置。
圖9 權限XML配置
圖10 攔截器建模
Bean
在框架中bean是最常出現的,企業應用程式依賴beans實現可重用代碼。因此靜態分析要想保證完整性,對於bean的分析是不可忽視的。完整的bean對象以及它們的可傳遞依賴關係是很重要的,這樣分析可以完全分析bean代碼。
對註解標記的bean進行識別:
圖11 Bean註解建模
對XML文檔配置的bean進行識別:
圖12 BeanXML建模
6.緩存的處理
2-object-sensitive analysis 這種分析方法在對java.util包中的這些數據結構類的分析有很好的效果,但是通過分析前人的工作發現,用這個方法對一個小應用程式進行分析,其中對java.util的分析佔了整個靜態分析耗時的三分之二。
產生這種現象的原因是應用程式中有大量高度通用且異構的數據緩存,由於應用程式的分布式特性和水平擴展需求,資料庫相關對象,身份信息映射,視圖對象,業務邏輯相關對象都常常會被緩存。
在Spring中使用@EnableCaching and @Cacheable註解來實現任意bean的緩存。
提出一個sound-modulo-analysis的分析模式,通過替換修改一些原來的代碼,在不破壞原有一些行為特徵的情況下,簡化靜態分析。
通過分析發現,程序分支的條件,分析語句的順序,和數組內容的精確索引都不會影響靜態分析,根據這個特性,實現了數據結構的一個簡化版本。
sound-modulo-analysis儘可能的保證Java庫的原始行為。
對HashMap進行簡化,代碼如下(左側為原始代碼右側是簡化後代碼)
圖13 HashMap重構
7.JackEE評估
左側是評估程序時所分析的所有企業應用,和Doop進行完整性對比,可以看到JackEE在所有分析的企業應用程式中方法的可達率都超過了Doop框架,JackEE的分析平均應用內可達性為58.04%,在alfresco也降至不低於43.48%。相比之下,Doop的平均覆蓋率為14.48%,其中在alfresco和pybbs的覆蓋率分別降至約1.8%和0.0%,alfresco和pybbs都通過框架特定的機制定義了入口點和進一步的功能的。但是Doop卻檢測不到,alfresco既有XML配置的入口點,也有一個自定義的基於Spring的RESTful風格的API。pybbs的入口點是用Spring注釋給出的。然後,兩個應用程式都使用注釋進行依賴注入。可見其分析的完整性。
圖14 JackEE評估
從這張圖可以看出JackEE採用的mod-2objH方法比傳統方法分析效率上有明顯提升,有的程序執行分析時所消耗的時間幾乎和上下文無關(ci)分析所用時間相近,可見JackEE有較高的分析效率。
本文提出的對Java企業應用程式中框架和緩存的靜態分析方法相比現有靜態分析方法有較好的準確性和可擴展性。
論文下載連結:https://pdfs.semanticscholar.org/0f24/74c24741b34ddffdd512ce2d556dd57afe9a.pdf
Youtube講解地址:https://www.youtube.com/watch?v=kADROzL8fJU
參考:https://www.jianshu.com/p/8d06766d232c,https://zhuanlan.zhihu.com/p/161700173