发明内容
本发明的目的,在于提供一种Java API程序异常与文档的描述不一致自动检测方法,其可提高Java API文档对抛出异常描述的准确性,进而提高软件质量。
为了达成上述目的,本发明的解决方案是:
一种Java API程序异常与文档的描述不一致自动检测方法,包括如下步骤:
(1)提取源代码中每个方法的执行代码部分以及注释文档部分,分别进行分析;
(2)遍历当前分析目标(通常将整个工程项目的源代码作为输入)的所有方法,提取每个方法的抛出异常类型及其触发条件,并建立目标工程源代码各方法之间的调用关系库;
(3)再次分析目标工程通过步骤(2)中提取出的数据,对于每一个方法,首先分析当前的异常触发条件,然后根据调用关系,递归分析调用方法中的异常触发条件;
(4)对于目标工程中的每个方法,利用启发式方法分析其注释文档;
(5)对于目标工程中的每个方法,将步骤(3)中提取出的异常信息与步骤(4)中提取出的文档描述信息进行比对,进而检测出两者不一致的问题。
上述步骤(1)中,注释文档指每个Java API方法对应的Javadoc Annotation注释文档,这是一种具有半结构特征的文档。
上述步骤(1)中,执行代码指执行API功能的程序代码。
上述步骤(2)中,抛出异常类型指在throw语句中被抛出的异常对象所属类型,句式为
throw new Exception(Description)
其中,Exception泛指所有抛出异常的类型,Description泛指代码中抛出异常时的提示信息。
上述步骤(3)中,异常触发条件指代码执行到抛出异常的throw语句时参数的设置条件,故该条件中必须有至少一个当前方法的参数参与。
上述步骤(2)中,调用关系库利用开源技术,获取方法之间存在直接参数传递现象的调用关系。
上述存在直接参数传递现象的调用关系是指,某个方法将自己的某个参数作为形参传递给其调用的另一个方法,则称这两个方法之间是一种存在直接参数传递的调用关系。
上述步骤(3)中,递归分析的具体步骤还包括,提前设置调用深度阈值、每层方法刷新异常信息的触发条件和涉及参数信息。
上述步骤(4)中,启发式方法指对于某个异常信息,逐个查看注释文档中的特定条目,针对当前异常条件中所涉及到的参数,检查这些文档条目是否将所有参数名称描述完整,从而判断文档中是否对当前异常进行描述。
上述启发式方法基于这样的假设,即如果文档对某项约束条件进行了描述,则该描述是正确的。
采用上述方案后,本发明主要利用静态代码分析的手段,对Java API中程序异常与相应文档描述不一致问题进行自动化检测,分别提取Java API注释文档和执行代码中的异常信息及其触发条件,并与文档的相应描述进行比对,从而检测出文档中可能存在的异常描述不一致问题,以提高Java API文档对抛出异常描述的准确性,进而提高软件质量。本发明主要以Java语言的源代码为适用对象。
具体实施方式
以下将结合附图,对本发明的技术方案进行详细说明。
参照图1所示,本发明提供一种Java API程序异常与文档的描述不一致自动检测方法,包括如下内容:
(1)核心数据结构
基于本发明的检测策略,我们定义一个基本的元数据结构,用于存储代码中可能产生的异常及其相关信息。我们将其命名为InfoBox。每个InfoBox需要存储异常类型、异常触发条件、所涉及的参数以及该异常所属的方法信息等。本文之后提到的代码分析都是基于这样的元数据结构。
由于每个InfoBox只记录一条异常相关信息,所以对于每个API方法,我们可能提取出多个InfoBox数据,而这些异常信息都应该在该方法对应的文档中得到表述。若当前API不存在文档-代码不一致性,则它们之间的关系应满足如下公式:
公式中,ci代表该API方法中第i个InfoBox所包含的参数约束条件信息,cdoc表示该API方法对应的文档中表述的所有约束条件信息。如果发现某个InfoBox所包含参数约束信息不满足上述公式,则认为该API文档对异常描述存在不一致。
我们分析的文档具体是指Java源代码中存在的Javadoc Annotation信息。这一类文档具有一定的结构特征。基本的,每个Java方法对应一块Javadoc信息块,而每一个信息块中存在多个条目(directive),每个条目主要分为3个部分:类型描述、关键词、描述语句。其中类型描述一般以@开头,常见的有@param、@throws以及@exception等,表示该条目主要的内容。关键词随条目类型的不同而具有不同的意义,如@param中关键词是相关的参数名,而@throws和@exception中关键词是抛出异常类型。描述语句是一段自然语言文字信息。
(2)调用关系库的构建
该步骤将针对源代码项目中各方法之间存在的调用关系进行分析。同时,由于我们的研究对象为程序异常,并且主要针对其触发条件中相关参数需要满足的约束条件,所以我们仅关注一种存在直接参数传递的调用关系。
所谓直接参数传递的调用关系是指,如果某方法A中调用了方法B,并将该方法A的某个参数p(或者某些参数,这里仅举一个参数进行说明)作为形参直接传递给方法B的这样一种调用关系。例如,javax.swing.JTabbedPane类中的insertTab(String title,Iconicon,Component component,String tip,int index)方法调用了java.awt.Container中的addImpl(Component comp,Object constraints,int index)方法,并将自己的component参数作为形参直接传递给后者相应的参数位。我们称insertTab方法与addImpl方法之间存在直接参数传递的调用关系。
这种参数传递的特征在于,对于参与调用关系的两个方法,它们之间传递的参数需要满足同样的约束条件。如果在被调方法中由于参数不满足约束条件而导致异常抛出,那么在主调方法对应的文档中也同样应该对这个异常进行相应的描述。
我们利用静态分析的手段构建调用关系库。具体的,在实践中,我们采用的是eclipse中的call hierarchy开源模块作为具体的技术实施手段来获取调用关系,由于是静态分析工具,我们不考虑运行时由方法重载引起的动态调用情况。
(3)执行代码分析模块
我们采用一种二阶段的处理方式对执行代码进行分析,提取其中的异常信息及其触发条件。其中,第一阶段逐个分析每个方法中存在的异常信息,即提取InfoBox。第二阶段加入调用关系的分析,获取该方法在递归调用其他方法中还可能抛出的异常信息,即递归提取InfoBox。
在第一阶段中,我们利用eclipse提供的JDT-AST工具进行语法树分析,进而抽取出InfoBox信息元数据。其算法思想伪代码如下:
表1单方法语法树分析及异常信息抽取
伪代码中,我们用元组(m,c,p)来表示InfoBox元数据,其中,m代表异常类型,c代表其触发条件的集合,p代表触发条件中所涉及该方法的参数集合。
具体的,针对每一条子句,我们根据其不同的类型,进行不同的操作。大概分为3类:
a)可展开语句,如子句段落组合(在JDT中具体为一个Block对象)。对于这样的简单可展开段落,我们再次针对每条子句逐个进行递归搜索,体现深度优先的搜索方式。
b)异常触发语句,如throws语句(在JDT中具体为一个ThrowStatement对象)。对于这样的语句处理方式是,在返回的信息集合中新建一个InfoBox,将当前的错误触发类型以及一些基本信息记录下来,然后将信息集合返回,待上层的条件语句对它的触发条件进行补充。
c)条件语句,如if语句(在JDT中具体为一个IfStatement对象)。对这类的处理方式,是先获取其子句的信息,递归分析并提取每个子句中存在的异常信息,组建成InfoBox集合,然后将本句的条件添加到该集合的每个信息元组上,将更新后的InfoBox集合返回。
在得到了整个信息集合之后,对于集合中每个元组,分析其触发条件,找到所有参与触发条件判断的参数,补充在元组的p部分。如此,我们得到了这个信息集合。此阶段最后一步,就是将获取的信息存在本地。
第二阶段将根据第一阶段获取的数据进行进一步分析。对于每一个待分析的API方法,其具体分析步骤伪代码如表2所示:
表2结合调用关系的异常信息抽取
我们的分析策略如下:
a)首先,提取当前方法的所有InfoBox异常信息;
b)查看调用关系库,获取当前方法的所有调用关系;
c)对于每个调用关系,递归分析它们的异常信息,并将其返回,形成当前方法的一个异常信息集合;
d)对于每个异常信息,我们会根据主调、被调方法之间对应的参数名,更新其中的触发条件以及参数信息,使之和上一层的参数名匹配。
为了避免调用关系形成闭环,在分析开始时,我们需要设置一个递归深度的阈值。当达到预计的分析深度时,则停止向下的递归分析。
(4)比对方案及匹配过程
我们关注文档中对参数约束条件的正确描述。针对文档中对程序异常描述不一致的情况,我们提出这样的假设:
假设如果文档对某项约束条件进行了描述,则该描述是正确的。
基于这个假设,我们提出一种启发式的检测方法,对Java API程序异常与文档的描述不一致问题进行检测,将代码中提取的异常信息与API文档进行比对。具体的,从文档描述中查询相关约束的关键词以及参数名等信息,如果描述中存在这些信息,我们认为该文档对程序异常的描述是一致的,反之则不一致。具体的,对于从执行代码中提取出的每一条异常信息InfoBox,对应该方法的API文档,我们进行这样的匹配:
1)从InfoBox的触发条件集合中获取当前异常信息所涉及到的API参数;
2)针对每一条文档条目,判断是否提到上一步骤中提取到的所有参数名称;
3)如果该条目提到了所有参数,则认为该条目对这个异常信息进行了正确描述;
4)否则,查看下一条目,直到找到符合步骤3)的条目,或所有条目搜寻完毕;
5)如果所有条目搜寻完毕,仍然找不到符合该异常信息的正确描述,则认为当前文档存在对该异常描述存在不一致性。
综上所述,本发明通过上述几个模块,利用静态分析的手段,对Java API程序异常与文档的描述不一致进行检测。其中主要通过模块3对源代码中的执行代码进行分析和异常信息提取,而模块3中会使用模块2中对整个项目的调用关系分析结果,再在模块4中结合上述模块分析结果,对相应API的文档进行分析比对,检测出两者不一致的情况。
参照表3,其为本发明的准确度验证的数据集,如下:
表3
表4为本发明对文档中异常描述不一致性检测结果的准确度评估,如下:
表4
表5为随机分配结果的对比实验准确度,如下:
表5
|
TP |
FP |
FN |
TN |
Prec(%) |
Rec(%) |
F-mea(%) |
空值约束 |
77 |
124 |
58 |
103 |
38.3 |
57.0 |
45.8 |
取值约束 |
125 |
200 |
128 |
176 |
38.5 |
49.4 |
43.3 |
类型约束 |
62 |
31 |
61 |
23 |
66.7 |
50.4 |
57.4 |
Total |
264 |
355 |
247 |
302 |
42.6 |
51.7 |
46.7 |
我们将被测API按照约束类型划分成3类:空值约束、取值约束以及类型约束。
1、空值约束指API对于参数值为null情况下的一些约束条件。通常分为空值允许和空值不允许两种。所谓空值允许,指代码中有考虑到参数值为空时进行相应处理而使得程序不会发生错误或抛出异常,而空值不允许是指如果参数为空值则会发生错误或抛出异常。
2、取值约束指参数需要满足一些取值条件,如java.awt.Component中createBufferStrategy(int numBuffers,BufferCapabilities caps)方法中要求,numBuffers取值需大于或等于1,否则将抛出异常。
3、参数类型约束指参数必须是某种类型,如java.awt.Container中add(Component comp)方法中要求,如果comp是java.awt.Window的子类,则会抛出异常。
根据不同的约束类型,我们对本方法的准确性进行评估。总体而言,本方法的准确率为71.5%,召回率85.9%,对比表3中的随机选择结果,可以看出,本方法具有显著的效果。其中,对于类型约束的检测效果最好,达到77.4%的准确率以及97.6%的召回率。对于取值约束的检测效果不是很好,但是其准确率和召回率分别是67.3%77.5%,同样优于随机选择。
表6为对不同的不一致类型,本方法的检测效果,如下:
表6
对于异常描述不一致的种类,我们进行分类,其中:
1.正确是指代码和文档内容一致;
2.模糊是指代码行为在文档中没有具体描述,如java.awt.Container中的add(Component comp,int index)方法,对应的注释文档中提到,@IllegalArgumentExceptionif index is invalid,这里没有说index具体应该满足什么要求;
3.未提及是指文档对于应该给出的参数约束没有相关描述,如示例中javax.swing.JTabbedPane的addTab(String title,Component component)方法,对应文档没有提到component不能为Window子类这一约束条件;
4.错误是指相关约束条件描述错误,如java.awt.event.InputEvent中getMaskForButton(int button)方法,其对应文档中指出@throwsIllegalArgumentException if button is less than zero or greater than thenumber of button masks reserved for buttons,而从代码中可以看出,当button小于0时抛出异常,此时文档中的描述和执行代码的行为是不一致的。
这几类中“正确”和“模糊”算作对异常描述一致,而“未提及”和“错误”为不一致。在不一致的情况中,“错误”占比仅不到1%,证明我们的假设正确,即如果文档对某项约束条件进行了描述,则该描述是正确的。同时,在针对“未提及”这类不一致情况的检测评估中,其准确判断的占89.9%,同时证明我们比对方法的正确。
表7为本发明普适度验证的数据集,如下:
表7
表8为本发明普适度验证的检测结果准确度评估,如下:
表8
可以看到,本发明依然能保持较好的检测效果。其中,java.util.*包的数据规模最大,分析结果也最好,分别能达到73.3%的准确率以及92.6%的召回率。对另外几个项目而言,召回率表现都不错,其中java.security.*能达到80%以上。
以上实施例仅为说明本发明的技术思想,不能以此限定本发明的保护范围,凡是按照本发明提出的技术思想,在技术方案基础上所做的任何改动,均落入本发明保护范围之内。