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