胡營營,趙逢禹
(上海理工大學(xué) 光電信息與計算機(jī)工程學(xué)院,上海 200093)
為了快速開發(fā)Web應(yīng)用系統(tǒng),開發(fā)人員常常復(fù)用已有系統(tǒng)的框架或成熟項目中現(xiàn)有的代碼,然后在此基礎(chǔ)上進(jìn)行完善與修改。這種代碼與框架復(fù)用能夠提高開發(fā)效率,節(jié)省開發(fā)時間,但同時也帶來了諸多的副作用。一是代碼冗余增加,即項目中存在冗余的頁面代碼、JavaScript代碼、CSS代碼、處理方法、控制類、支持類等;二是導(dǎo)致源代碼臃腫,可讀性差,增加了維護(hù)難度。在某些情況下,Web應(yīng)用系統(tǒng)中的冗余代碼還會隱藏軟件缺陷與安全漏洞[1-3],最近一項對9 300名開發(fā)人員進(jìn)行的調(diào)查結(jié)果顯示將冗余代碼檢測和代碼拆分評為最高級別功能請求。
冗余檢測一直受到國內(nèi)外學(xué)者的關(guān)注。王偉使用Lex和YACC分別對C語言代碼進(jìn)行詞法和語法分析,通過語法樹檢測代碼中的冪等冗余、變量冗余和死代碼冗余[4];蘇小紅等人利用TOKEN序列建立復(fù)合語句控制結(jié)構(gòu)信息表,設(shè)計了基于控制結(jié)構(gòu)的冗余代碼檢測模型,能對C語言中冪等、隱冪等、私有變量、條件冗余、死代碼和冗余傳參進(jìn)行檢測[5-6];壽能為了揭示冗余與軟件缺陷的關(guān)系,在冗余分類的基礎(chǔ)上,研究了冗余特征與軟件缺陷的關(guān)聯(lián)關(guān)系,使用NRefactory設(shè)計了冗余檢測算法,實現(xiàn)了一個冗余檢測與缺陷提示的檢測器[7];Wang Xing結(jié)合靜態(tài)切片和動態(tài)切片分析方法提出了一種基于程序切片的死代碼檢測方法,并在LLVM基礎(chǔ)上設(shè)計了死代碼檢測框架[8];Leitao通過對C語言代碼構(gòu)建抽象語法樹,設(shè)計了Retargetable Redundancy and Deceit Detector (R2D2),通過分析語法樹中的子節(jié)點(diǎn)來檢測冗余代碼[9]。
Niels針對Web中代碼復(fù)用帶來瀏覽器解析多余JavaScript死代碼造成Web應(yīng)用系統(tǒng)的整體性能下降問題,提出了一種基于靜態(tài)和動態(tài)分析的JavaScript死代碼消除方法Lacuna,在執(zhí)行時間和分析精度方面取得了可喜的成果[10]。雖然Niels針對JavaScript中的冗余代碼進(jìn)行了檢測,但Web應(yīng)用系統(tǒng)中還包括多個層面的代碼如瀏覽器端的表現(xiàn)層代碼HTML和CSS、服務(wù)器端處理與控制類代碼、數(shù)據(jù)庫操作與服務(wù)支持代碼等[11],Niels的研究尚無法推廣到對整個Web應(yīng)用系統(tǒng)的冗余檢測。而實際上,前后臺交互的業(yè)務(wù)處理類與處理方法是Web應(yīng)用系統(tǒng)的核心邏輯,對該類代碼進(jìn)行冗余檢測在提高系統(tǒng)的可讀性、可維護(hù)性以及性能上有重要作用,因此文中把研究的重點(diǎn)集中在前后臺交互的業(yè)務(wù)處理類與處理方法冗余檢測方面。
Web應(yīng)用系統(tǒng)開發(fā)中常用前端開發(fā)語言有HTML、Javascript、CSS、C#、Jquery和Bootstrap等;常用后端開發(fā)語言為Java、ASP.NET、PHP、Python和Ruby等。盡管一個Web應(yīng)用系統(tǒng)可能由不同語言甚至多種語言組合開發(fā),但前后臺仍然是通過業(yè)務(wù)處理類與處理方法進(jìn)行交互,對該類冗余的檢測仍然適用于所有Web應(yīng)用系統(tǒng),因此文中以廣泛使用的HTML、Javascript、CSS和Java語言為例,給出了Web應(yīng)用系統(tǒng)中基于源代碼分析的冗余代碼檢測(redundant code detection for web application,RCDWA)方法。
在RCDWA方法中,需要獲取Web應(yīng)用中前后臺交互的業(yè)務(wù)處理類與處理方法,即表現(xiàn)層和業(yè)務(wù)邏輯層功能節(jié)點(diǎn)業(yè)務(wù)跳轉(zhuǎn)的關(guān)聯(lián)關(guān)系,這需要用到抽象語法樹以實現(xiàn)對相關(guān)節(jié)點(diǎn)的提取和源代碼的解析。
抽象語法樹[12](abstract syntax tree,AST)是源代碼的抽象語法結(jié)構(gòu)的樹狀表現(xiàn)形式。樹上的每個節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)。之所以說語法是“抽象”的,是因為這里的語法并不會表示出真實語法中出現(xiàn)的每個細(xì)節(jié),一些語句被隱含在樹的結(jié)構(gòu)中,并沒有以節(jié)點(diǎn)的形式呈現(xiàn)。
采用Eclipse JDT中的靜態(tài)解析技術(shù)將源代碼文件轉(zhuǎn)化為抽象語法樹,通過操縱抽象語法樹,一方面可以精確地獲得Web應(yīng)用源代碼中的相關(guān)節(jié)點(diǎn),進(jìn)而實現(xiàn)對代碼的解析和利用;另一方面在語法分析過程中編譯器會對文法進(jìn)行等價的轉(zhuǎn)換如消除左遞歸、回溯、二義性等[13],這樣會給文法引入多余的成分,抽象語法樹的結(jié)構(gòu)采用的是上下文無關(guān)文法即不依賴于源語言的文法,因此選用抽象語法樹可以避免干擾因素,更好地對處理類與處理方法節(jié)點(diǎn)進(jìn)行提取。
一個Web應(yīng)用系統(tǒng)是由頁面、后臺處理邏輯、數(shù)據(jù)庫系統(tǒng)組成的有聯(lián)系的代碼集合。Web應(yīng)用從入口頁面即主頁面開始執(zhí)行并根據(jù)用戶交互情況動態(tài)生成不同的顯示層頁面[14]。代碼1和代碼2中的代碼分別為Web應(yīng)用系統(tǒng)前臺HTML、Javascript、CSS代碼和后臺的Java代碼舉例。代碼1中超鏈接的href會鏈接到頁面next.jsp,Javascript代碼的myfunction函數(shù)調(diào)用代碼2中的業(yè)務(wù)處理方法function1,代碼2中的Controller和Redundant為業(yè)務(wù)處理類。從程序入口開始遍歷所有源代碼文件,遍歷時被調(diào)用到的頁面、被調(diào)用的處理類與方法就是Web應(yīng)用中有效的代碼集。遍歷完整個Web應(yīng)用系統(tǒng),如果某些頁面、類與方法都沒有被調(diào)用,那它們就是冗余頁面、冗余業(yè)務(wù)處理類與處理方法。在代碼1和代碼2給出的例子中,代碼2中Redundant、function2和function3都沒有被調(diào)用,就是冗余類或冗余方法。
代碼1:Web應(yīng)用系統(tǒng)的入口文件index.jsp。
1
2
5
6
7
8
9
10 function myfunction(){
11 formObject.action="URL";}
12
13
14
代碼2:Web后臺的Java代碼Controller.java。
1 public class Controller{
2 @RequestMapping(value ="/ URL")
3 public ModelAndView function1 (request){
4 …
5 }
6 public ModelAndView function2 (request){
7 …
8 }
9 }
10 class Redundant {
11 public ModelAndView function3 (request){
12 …
13 }
14 }
為了分析Web應(yīng)用系統(tǒng)中業(yè)務(wù)處理類與處理方法的冗余問題,需要分析前后臺交互的業(yè)務(wù)處理類與處理方法。圖1給出了該冗余檢測問題的整體流程框架。
圖1 RCDWA方法流程框架
(1)提取Web應(yīng)用中的頁面文件名,得到頁面文件名集合PageSet。在應(yīng)用系統(tǒng)頁面文件目錄下新建一個.bat文件,并且以編輯形式打開并輸入代碼@ECHO OFF回車>tree /F >頁面文件名集合.txt,即可得到PageSet集合。
(2)提取后臺文件中類和方法得到節(jié)點(diǎn)集合AllSet={AST1,AST2,…,ASTi…},其中節(jié)點(diǎn)ASTi=
(3)搜索Web應(yīng)用入口頁面中對其他頁面的調(diào)用或?qū)笈_類中方法的調(diào)用,遞歸構(gòu)建Web應(yīng)用調(diào)用樹(application call tree,ACT)。算法2給出了構(gòu)建ACT的詳細(xì)過程。
(4)冗余檢測。將集合PageSet、AllSet集合和Web應(yīng)用調(diào)用樹ACT作為輸入進(jìn)行冗余代碼檢測,輸出冗余頁面、冗余處理類和處理方法。算法3給出了冗余檢測的具體過程。
在RCDWA方法中核心算法有節(jié)點(diǎn)構(gòu)建算法、Web應(yīng)用調(diào)用樹構(gòu)建算法和冗余檢測算法。節(jié)點(diǎn)構(gòu)建算法利用源代碼的抽象語法樹查找Classi中的方法,得到節(jié)點(diǎn)ASTi;調(diào)用樹構(gòu)建算法是為了構(gòu)造出Web應(yīng)用系統(tǒng)頁面間、方法間、頁面與方法間的調(diào)用關(guān)系;冗余檢測算法利用Web應(yīng)用調(diào)用樹得到Web應(yīng)用中有效的頁面、處理類和處理方法節(jié)點(diǎn)集ActivePage和ActiveSet,將有效節(jié)點(diǎn)集與Web應(yīng)用中總的節(jié)點(diǎn)集AllSet對比來檢測冗余代碼。
>TYPES(2)
>TypeDeclaration[158+99]
>type binding:cn.usst.market.controller.Controller
>BODY_DECLARATIONS(2)
>MethodDeclaration[186+65]
>method binding:Controller.function1()
>MethodDeclaration[254+30]
>method binding:Controller.function2()
>TypeDeclaration[290+49]
>type binding:cn.usst.market.controller.Redundant
>BODY_DECLARATIONS(1)
>MethodDeclaration[310+26]
>method binding:Redundant.function3()
以上為Controller.java的抽象語法樹,TYPES為抽象語法樹的根節(jié)點(diǎn),TYPES(2)表示該屬性下有兩個TypeDeclaration子節(jié)點(diǎn),TypeDeclaration表示類聲明或接口聲明,在該樹中表示類Controller和Redundant;類聲明的子節(jié)點(diǎn)BodyDeclaration表示類主體,即類大括號中的內(nèi)容;BodyDeclaration的子節(jié)點(diǎn)MethodDeclaration表示方法聲明或構(gòu)造器聲明,在該樹中代表方法function1、function2和function3。將程序源碼解析為抽象語法樹AST,生成的語法樹可以方便地查找類與類中的方法,下面給出類與方法節(jié)點(diǎn)ASTi構(gòu)建算法。
算法1:節(jié)點(diǎn)構(gòu)建算法。
輸入:源碼code;
輸出:AllSet。
處理:
(1)獲取Web應(yīng)用所有后臺源代碼文件。
(2)利用Eclipse JDT中的靜態(tài)解析技術(shù)構(gòu)建源代碼文件類的抽象語法樹Treei。
(3)根據(jù)抽象語法樹Treei,查找類中的方法,并形成節(jié)點(diǎn)ASTi=
(4)返回節(jié)點(diǎn)ASTi,并將節(jié)點(diǎn)添加到節(jié)點(diǎn)集合AllSet中。
利用節(jié)點(diǎn)構(gòu)建算法處理后臺源代碼文件Controller.java可以得到集合AST1=
為了檢測Web系統(tǒng)中從未調(diào)用的頁面、處理類與處理方法,需要建立代碼之間的邏輯調(diào)用關(guān)系,并基于代碼間的調(diào)用關(guān)系構(gòu)建Web應(yīng)用調(diào)用樹ACT。Web應(yīng)用的主頁面為ACT的根節(jié)點(diǎn),主頁面調(diào)用的其他頁面page(頁面文件名)、調(diào)用的后臺類中方法Class.Method都是該根節(jié)點(diǎn)的子節(jié)點(diǎn)。從程序入口頁面開始將節(jié)點(diǎn)按調(diào)用關(guān)系逐步構(gòu)建Web應(yīng)用調(diào)用樹,以便后文利用Web應(yīng)用調(diào)用樹來識別有效的頁面、處理類與處理方法。
算法2:Web應(yīng)用調(diào)用樹構(gòu)建算法。
輸入:Web應(yīng)用入口頁面文件;
輸出:Web應(yīng)用調(diào)用樹ACT。
處理:
(1)將Web應(yīng)用入口頁面文件名作為樹的根節(jié)點(diǎn)Root,并標(biāo)記該節(jié)點(diǎn)為“未訪問”,得到初始Web應(yīng)用調(diào)用樹ACT(每個Web應(yīng)用系統(tǒng)有唯一入口頁面)。
(2)采用廣度優(yōu)先算法,從ACT中獲取第一個未訪問的節(jié)點(diǎn)Node,如果該節(jié)點(diǎn)是一個頁面文件名,轉(zhuǎn)步驟3,如果該節(jié)點(diǎn)是一個方法,轉(zhuǎn)步驟4,如果該節(jié)點(diǎn)為Null,則轉(zhuǎn)步驟5。
(3)搜索該頁面中標(biāo)簽屬性為href和action的節(jié)點(diǎn)作為Node節(jié)點(diǎn)的孩子節(jié)點(diǎn),并將該Node節(jié)點(diǎn)標(biāo)記為“已訪問”,然后轉(zhuǎn)步驟2。
(4)利用抽象語法樹查找Node節(jié)點(diǎn)調(diào)用的頁面page(頁面文件名)與調(diào)用的方法Class.Method,把這些節(jié)點(diǎn)作為該Node節(jié)點(diǎn)的孩子節(jié)點(diǎn),并將該Node節(jié)點(diǎn)標(biāo)記為“已訪問”,然后轉(zhuǎn)步驟2。
(5)返回構(gòu)建好的ACT,結(jié)束算法。
Web應(yīng)用調(diào)用樹是由href鏈接到的page和action邏輯跳轉(zhuǎn)到的Class.Method 2種節(jié)點(diǎn)組成,如圖2所示為前文代碼1和代碼2的Web應(yīng)用調(diào)用樹,根節(jié)點(diǎn)為入口文件index.jsp,頁面index.jsp中標(biāo)簽屬性為href和action的節(jié)點(diǎn)分別調(diào)用頁面next.jsp和后臺方法function1,依次遞歸直至構(gòu)建為完整的Web應(yīng)用調(diào)用樹。
圖2 代碼示例的Web應(yīng)用調(diào)用樹
將源程序中所有頁面文件名存入集合PageSet中,到目前為止,已經(jīng)獲取集合PageSet、集合AllSet和Web應(yīng)用調(diào)用樹ACT,集合PageSet和AllSet中包含Web應(yīng)用所有的頁面文件、類和方法,ACT中的節(jié)點(diǎn)為有效的頁面文件、處理類和處理方法,通過計算可以得出冗余頁面、冗余處理類與處理方法。
算法3:冗余代碼檢測算法。
輸入:PageSet、AllSet、ACT;
輸出:冗余代碼。
處理:
(1)設(shè)置有效的頁面集合ActivePage與有效的方法集合ActiveSet為Null。
(2)獲取ACT中未訪問過的節(jié)點(diǎn)Node,如果Node為空,則轉(zhuǎn)步驟4。
(3)如果該節(jié)點(diǎn)是一個頁面文件則存入集合ActivePage中,如果該節(jié)點(diǎn)是一個方法則存入集合ActiveSet中,然后把Node節(jié)點(diǎn)標(biāo)記為“已訪問”。轉(zhuǎn)步驟2。
(4)計算冗余頁面集合RedundantPage=PageSet- ActivePage,集合RedundantPage中節(jié)點(diǎn)對應(yīng)的頁面就是冗余頁面。
(5)計算冗余方法集合RedundantSet=AllSet-ActiveSet,集合RedundantSet中節(jié)點(diǎn)對應(yīng)的方法就是冗余方法。
(6)如果一個類中的所有方法都是冗余方法,則該類記為冗余類
(7)輸出代碼冗余結(jié)果。
為了評估RCDWA方法的有效性,包括誤檢率和漏檢率,對兩個Web應(yīng)用系統(tǒng)進(jìn)行了冗余檢測,并對兩個程序集分別進(jìn)行不同數(shù)量的人工注入冗余實驗。
采用UsstMarket和MovieBoot兩個Web應(yīng)用進(jìn)行實驗分析,其中UsstMarket為筆者所在實驗室開發(fā)的大型模擬創(chuàng)業(yè)網(wǎng)站,網(wǎng)站總共包括6個季度,模擬了現(xiàn)實世界創(chuàng)業(yè)中可能遇到的各種流程,目的是給各大高校學(xué)生當(dāng)作一門創(chuàng)業(yè)課。MovieBoot為github上的開源項目,是一個集電影、音樂和書籍于一體的JavaWeb應(yīng)用,github上顯示最近一次代碼更新是1個月前。兩個Web應(yīng)用都是由HTML、JavaScript、CSS和Java語言開發(fā)。表1為兩個應(yīng)用程序的基本信息。
表1 程序集信息
實驗1:誤檢率檢測。
利用RCDWA方法對UsstMarket和MovieBoot進(jìn)行冗余檢測,獲取Web應(yīng)用中的頁面文件名集合,獲取后臺源代碼文件并解析為抽象語法樹得到類與方法節(jié)點(diǎn)集,然后構(gòu)建Web應(yīng)用調(diào)用樹,進(jìn)行冗余檢測。
實驗過程中兩個程序集前臺頁面文件數(shù)、生成的抽象語法樹AST個數(shù)、處理類與處理方法個數(shù)如表2所示。對檢測出的頁面冗余、類冗余和方法冗余進(jìn)行了人工審查,發(fā)現(xiàn)確實是冗余代碼,其中UsstMarket中的49個方法冗余是因為復(fù)用其它功能代碼后需求改變所導(dǎo)致的冗余,2個頁面冗余是開發(fā)新頁面時復(fù)用其它頁面的代碼導(dǎo)致的。
表2 誤檢率檢測結(jié)果
實驗2:漏檢率檢測。
為了統(tǒng)計漏檢率,本文對兩個Web應(yīng)用進(jìn)行了人工注入冗余實驗,人工注入冗余時將冗余類和方法盡量分散到了不同文件里,表3列出了人工注入冗余頁面、冗余處理類與處理方法的數(shù)量,注入冗余后利用RCDWA方法和文獻(xiàn)[6][8]中的冗余代碼檢測方法對兩個應(yīng)用進(jìn)行冗余檢測,檢測結(jié)果如表3所示,對RCDWA方法檢測出的冗余進(jìn)行人工審查,發(fā)現(xiàn)檢測出總的冗余是人工注入冗余和實驗1檢測冗余結(jié)果之和。
表3 漏檢率檢測結(jié)果
通過表2和表3可以看出,RCDWA方法對兩個應(yīng)用程序冗余檢測的誤檢率為0%,對人工注入冗余的漏檢率為0%。針對實驗2進(jìn)行了多次試驗,每次注入若干個頁面、處理類與處理方法冗余,漏檢率和誤檢率都為0%。RC-Finder和DCDPS方法對頁面冗余檢測數(shù)為0是因為文獻(xiàn)[6]和文獻(xiàn)[8]并未對頁面冗余進(jìn)行研究;對冗余類和冗余方法漏檢率較高是因為Web應(yīng)用使用面向?qū)ο笳Z言開發(fā),引入了封裝、繼承、多態(tài)等特性,文獻(xiàn)[6]中RC-Finder方法不能完全適用于面向?qū)ο笳Z言,文獻(xiàn)[8]中DCDPS方法添加了動態(tài)分析技術(shù),但由于動態(tài)程序切片的效率低導(dǎo)致只能分析較小的程序。從兩個實驗結(jié)果來看,文中提出的冗余代碼檢測方法針對Web應(yīng)用系統(tǒng)中的頁面冗余、處理類與處理方法冗余可以達(dá)到較高的檢測效率。
冗余檢測對Web應(yīng)用系統(tǒng)的缺陷排除、系統(tǒng)維護(hù)有重要意義[15]。提出的基于源代碼分析的冗余代碼檢測方法,從應(yīng)用程序入口開始,根據(jù)代碼之間的邏輯調(diào)用關(guān)系構(gòu)建Web應(yīng)用調(diào)用樹,進(jìn)而得到有效頁面、類與方法節(jié)點(diǎn)集;然后將有效節(jié)點(diǎn)集與Web應(yīng)用總的節(jié)點(diǎn)集根據(jù)冗余檢測算法檢測出冗余頁面、冗余業(yè)務(wù)處理類與處理方法?;谠摲椒ǎ瑢sstMarket和MovieBoot兩個中型Web應(yīng)用進(jìn)行了冗余檢測實驗,具有一定的代表性。盡管實驗是針對JavaWeb應(yīng)用系統(tǒng)進(jìn)行的,但RCDWA方法完全適用于其他語言開發(fā)的Web應(yīng)用。
文中重點(diǎn)對Web應(yīng)用系統(tǒng)中基于前后臺交互的業(yè)務(wù)處理類、處理方法和頁面進(jìn)行冗余檢測,對于Web應(yīng)用系統(tǒng)中的其他冗余,如數(shù)據(jù)庫操作冗余、CSS冗余、Javascript冗余等,并沒有進(jìn)行深入的研究,因此在后續(xù)工作中將進(jìn)一步針對Web應(yīng)用中的其他冗余進(jìn)行研究。