胡营营,赵逢禹
(上海理工大学 光电信息与计算机工程学院,上海 200093)
为了快速开发Web应用系统,开发人员常常复用已有系统的框架或成熟项目中现有的代码,然后在此基础上进行完善与修改。这种代码与框架复用能够提高开发效率,节省开发时间,但同时也带来了诸多的副作用。一是代码冗余增加,即项目中存在冗余的页面代码、JavaScript代码、CSS代码、处理方法、控制类、支持类等;二是导致源代码臃肿,可读性差,增加了维护难度。在某些情况下,Web应用系统中的冗余代码还会隐藏软件缺陷与安全漏洞[1-3],最近一项对9 300名开发人员进行的调查结果显示将冗余代码检测和代码拆分评为最高级别功能请求。
冗余检测一直受到国内外学者的关注。王伟使用Lex和YACC分别对C语言代码进行词法和语法分析,通过语法树检测代码中的幂等冗余、变量冗余和死代码冗余[4];苏小红等人利用TOKEN序列建立复合语句控制结构信息表,设计了基于控制结构的冗余代码检测模型,能对C语言中幂等、隐幂等、私有变量、条件冗余、死代码和冗余传参进行检测[5-6];寿能为了揭示冗余与软件缺陷的关系,在冗余分类的基础上,研究了冗余特征与软件缺陷的关联关系,使用NRefactory设计了冗余检测算法,实现了一个冗余检测与缺陷提示的检测器[7];Wang Xing结合静态切片和动态切片分析方法提出了一种基于程序切片的死代码检测方法,并在LLVM基础上设计了死代码检测框架[8];Leitao通过对C语言代码构建抽象语法树,设计了Retargetable Redundancy and Deceit Detector (R2D2),通过分析语法树中的子节点来检测冗余代码[9]。
Niels针对Web中代码复用带来浏览器解析多余JavaScript死代码造成Web应用系统的整体性能下降问题,提出了一种基于静态和动态分析的JavaScript死代码消除方法Lacuna,在执行时间和分析精度方面取得了可喜的成果[10]。虽然Niels针对JavaScript中的冗余代码进行了检测,但Web应用系统中还包括多个层面的代码如浏览器端的表现层代码HTML和CSS、服务器端处理与控制类代码、数据库操作与服务支持代码等[11],Niels的研究尚无法推广到对整个Web应用系统的冗余检测。而实际上,前后台交互的业务处理类与处理方法是Web应用系统的核心逻辑,对该类代码进行冗余检测在提高系统的可读性、可维护性以及性能上有重要作用,因此文中把研究的重点集中在前后台交互的业务处理类与处理方法冗余检测方面。
Web应用系统开发中常用前端开发语言有HTML、Javascript、CSS、C#、Jquery和Bootstrap等;常用后端开发语言为Java、ASP.NET、PHP、Python和Ruby等。尽管一个Web应用系统可能由不同语言甚至多种语言组合开发,但前后台仍然是通过业务处理类与处理方法进行交互,对该类冗余的检测仍然适用于所有Web应用系统,因此文中以广泛使用的HTML、Javascript、CSS和Java语言为例,给出了Web应用系统中基于源代码分析的冗余代码检测(redundant code detection for web application,RCDWA)方法。
在RCDWA方法中,需要获取Web应用中前后台交互的业务处理类与处理方法,即表现层和业务逻辑层功能节点业务跳转的关联关系,这需要用到抽象语法树以实现对相关节点的提取和源代码的解析。
抽象语法树[12](abstract syntax tree,AST)是源代码的抽象语法结构的树状表现形式。树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节,一些语句被隐含在树的结构中,并没有以节点的形式呈现。
采用Eclipse JDT中的静态解析技术将源代码文件转化为抽象语法树,通过操纵抽象语法树,一方面可以精确地获得Web应用源代码中的相关节点,进而实现对代码的解析和利用;另一方面在语法分析过程中编译器会对文法进行等价的转换如消除左递归、回溯、二义性等[13],这样会给文法引入多余的成分,抽象语法树的结构采用的是上下文无关文法即不依赖于源语言的文法,因此选用抽象语法树可以避免干扰因素,更好地对处理类与处理方法节点进行提取。
一个Web应用系统是由页面、后台处理逻辑、数据库系统组成的有联系的代码集合。Web应用从入口页面即主页面开始执行并根据用户交互情况动态生成不同的显示层页面[14]。代码1和代码2中的代码分别为Web应用系统前台HTML、Javascript、CSS代码和后台的Java代码举例。代码1中超链接的href会链接到页面next.jsp,Javascript代码的myfunction函数调用代码2中的业务处理方法function1,代码2中的Controller和Redundant为业务处理类。从程序入口开始遍历所有源代码文件,遍历时被调用到的页面、被调用的处理类与方法就是Web应用中有效的代码集。遍历完整个Web应用系统,如果某些页面、类与方法都没有被调用,那它们就是冗余页面、冗余业务处理类与处理方法。在代码1和代码2给出的例子中,代码2中Redundant、function2和function3都没有被调用,就是冗余类或冗余方法。
代码1:Web应用系统的入口文件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应用系统中业务处理类与处理方法的冗余问题,需要分析前后台交互的业务处理类与处理方法。图1给出了该冗余检测问题的整体流程框架。
图1 RCDWA方法流程框架
(1)提取Web应用中的页面文件名,得到页面文件名集合PageSet。在应用系统页面文件目录下新建一个.bat文件,并且以编辑形式打开并输入代码@ECHO OFF回车>tree /F >页面文件名集合.txt,即可得到PageSet集合。
(2)提取后台文件中类和方法得到节点集合AllSet={AST1,AST2,…,ASTi…},其中节点ASTi=
(3)搜索Web应用入口页面中对其他页面的调用或对后台类中方法的调用,递归构建Web应用调用树(application call tree,ACT)。算法2给出了构建ACT的详细过程。
(4)冗余检测。将集合PageSet、AllSet集合和Web应用调用树ACT作为输入进行冗余代码检测,输出冗余页面、冗余处理类和处理方法。算法3给出了冗余检测的具体过程。
在RCDWA方法中核心算法有节点构建算法、Web应用调用树构建算法和冗余检测算法。节点构建算法利用源代码的抽象语法树查找Classi中的方法,得到节点ASTi;调用树构建算法是为了构造出Web应用系统页面间、方法间、页面与方法间的调用关系;冗余检测算法利用Web应用调用树得到Web应用中有效的页面、处理类和处理方法节点集ActivePage和ActiveSet,将有效节点集与Web应用中总的节点集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为抽象语法树的根节点,TYPES(2)表示该属性下有两个TypeDeclaration子节点,TypeDeclaration表示类声明或接口声明,在该树中表示类Controller和Redundant;类声明的子节点BodyDeclaration表示类主体,即类大括号中的内容;BodyDeclaration的子节点MethodDeclaration表示方法声明或构造器声明,在该树中代表方法function1、function2和function3。将程序源码解析为抽象语法树AST,生成的语法树可以方便地查找类与类中的方法,下面给出类与方法节点ASTi构建算法。
算法1:节点构建算法。
输入:源码code;
输出:AllSet。
处理:
(1)获取Web应用所有后台源代码文件。
(2)利用Eclipse JDT中的静态解析技术构建源代码文件类的抽象语法树Treei。
(3)根据抽象语法树Treei,查找类中的方法,并形成节点ASTi=
(4)返回节点ASTi,并将节点添加到节点集合AllSet中。
利用节点构建算法处理后台源代码文件Controller.java可以得到集合AST1=
为了检测Web系统中从未调用的页面、处理类与处理方法,需要建立代码之间的逻辑调用关系,并基于代码间的调用关系构建Web应用调用树ACT。Web应用的主页面为ACT的根节点,主页面调用的其他页面page(页面文件名)、调用的后台类中方法Class.Method都是该根节点的子节点。从程序入口页面开始将节点按调用关系逐步构建Web应用调用树,以便后文利用Web应用调用树来识别有效的页面、处理类与处理方法。
算法2:Web应用调用树构建算法。
输入:Web应用入口页面文件;
输出:Web应用调用树ACT。
处理:
(1)将Web应用入口页面文件名作为树的根节点Root,并标记该节点为“未访问”,得到初始Web应用调用树ACT(每个Web应用系统有唯一入口页面)。
(2)采用广度优先算法,从ACT中获取第一个未访问的节点Node,如果该节点是一个页面文件名,转步骤3,如果该节点是一个方法,转步骤4,如果该节点为Null,则转步骤5。
(3)搜索该页面中标签属性为href和action的节点作为Node节点的孩子节点,并将该Node节点标记为“已访问”,然后转步骤2。
(4)利用抽象语法树查找Node节点调用的页面page(页面文件名)与调用的方法Class.Method,把这些节点作为该Node节点的孩子节点,并将该Node节点标记为“已访问”,然后转步骤2。
(5)返回构建好的ACT,结束算法。
Web应用调用树是由href链接到的page和action逻辑跳转到的Class.Method 2种节点组成,如图2所示为前文代码1和代码2的Web应用调用树,根节点为入口文件index.jsp,页面index.jsp中标签属性为href和action的节点分别调用页面next.jsp和后台方法function1,依次递归直至构建为完整的Web应用调用树。
图2 代码示例的Web应用调用树
将源程序中所有页面文件名存入集合PageSet中,到目前为止,已经获取集合PageSet、集合AllSet和Web应用调用树ACT,集合PageSet和AllSet中包含Web应用所有的页面文件、类和方法,ACT中的节点为有效的页面文件、处理类和处理方法,通过计算可以得出冗余页面、冗余处理类与处理方法。
算法3:冗余代码检测算法。
输入:PageSet、AllSet、ACT;
输出:冗余代码。
处理:
(1)设置有效的页面集合ActivePage与有效的方法集合ActiveSet为Null。
(2)获取ACT中未访问过的节点Node,如果Node为空,则转步骤4。
(3)如果该节点是一个页面文件则存入集合ActivePage中,如果该节点是一个方法则存入集合ActiveSet中,然后把Node节点标记为“已访问”。转步骤2。
(4)计算冗余页面集合RedundantPage=PageSet- ActivePage,集合RedundantPage中节点对应的页面就是冗余页面。
(5)计算冗余方法集合RedundantSet=AllSet-ActiveSet,集合RedundantSet中节点对应的方法就是冗余方法。
(6)如果一个类中的所有方法都是冗余方法,则该类记为冗余类
(7)输出代码冗余结果。
为了评估RCDWA方法的有效性,包括误检率和漏检率,对两个Web应用系统进行了冗余检测,并对两个程序集分别进行不同数量的人工注入冗余实验。
采用UsstMarket和MovieBoot两个Web应用进行实验分析,其中UsstMarket为笔者所在实验室开发的大型模拟创业网站,网站总共包括6个季度,模拟了现实世界创业中可能遇到的各种流程,目的是给各大高校学生当作一门创业课。MovieBoot为github上的开源项目,是一个集电影、音乐和书籍于一体的JavaWeb应用,github上显示最近一次代码更新是1个月前。两个Web应用都是由HTML、JavaScript、CSS和Java语言开发。表1为两个应用程序的基本信息。
表1 程序集信息
实验1:误检率检测。
利用RCDWA方法对UsstMarket和MovieBoot进行冗余检测,获取Web应用中的页面文件名集合,获取后台源代码文件并解析为抽象语法树得到类与方法节点集,然后构建Web应用调用树,进行冗余检测。
实验过程中两个程序集前台页面文件数、生成的抽象语法树AST个数、处理类与处理方法个数如表2所示。对检测出的页面冗余、类冗余和方法冗余进行了人工审查,发现确实是冗余代码,其中UsstMarket中的49个方法冗余是因为复用其它功能代码后需求改变所导致的冗余,2个页面冗余是开发新页面时复用其它页面的代码导致的。
表2 误检率检测结果
实验2:漏检率检测。
为了统计漏检率,本文对两个Web应用进行了人工注入冗余实验,人工注入冗余时将冗余类和方法尽量分散到了不同文件里,表3列出了人工注入冗余页面、冗余处理类与处理方法的数量,注入冗余后利用RCDWA方法和文献[6][8]中的冗余代码检测方法对两个应用进行冗余检测,检测结果如表3所示,对RCDWA方法检测出的冗余进行人工审查,发现检测出总的冗余是人工注入冗余和实验1检测冗余结果之和。
表3 漏检率检测结果
通过表2和表3可以看出,RCDWA方法对两个应用程序冗余检测的误检率为0%,对人工注入冗余的漏检率为0%。针对实验2进行了多次试验,每次注入若干个页面、处理类与处理方法冗余,漏检率和误检率都为0%。RC-Finder和DCDPS方法对页面冗余检测数为0是因为文献[6]和文献[8]并未对页面冗余进行研究;对冗余类和冗余方法漏检率较高是因为Web应用使用面向对象语言开发,引入了封装、继承、多态等特性,文献[6]中RC-Finder方法不能完全适用于面向对象语言,文献[8]中DCDPS方法添加了动态分析技术,但由于动态程序切片的效率低导致只能分析较小的程序。从两个实验结果来看,文中提出的冗余代码检测方法针对Web应用系统中的页面冗余、处理类与处理方法冗余可以达到较高的检测效率。
冗余检测对Web应用系统的缺陷排除、系统维护有重要意义[15]。提出的基于源代码分析的冗余代码检测方法,从应用程序入口开始,根据代码之间的逻辑调用关系构建Web应用调用树,进而得到有效页面、类与方法节点集;然后将有效节点集与Web应用总的节点集根据冗余检测算法检测出冗余页面、冗余业务处理类与处理方法。基于该方法,对UsstMarket和MovieBoot两个中型Web应用进行了冗余检测实验,具有一定的代表性。尽管实验是针对JavaWeb应用系统进行的,但RCDWA方法完全适用于其他语言开发的Web应用。
文中重点对Web应用系统中基于前后台交互的业务处理类、处理方法和页面进行冗余检测,对于Web应用系统中的其他冗余,如数据库操作冗余、CSS冗余、Javascript冗余等,并没有进行深入的研究,因此在后续工作中将进一步针对Web应用中的其他冗余进行研究。