本发明属于软件工程软件测试技术领域,特别是涉及一种基于贝叶斯网络推理的软件错误分层诊断方法。
背景技术:
(1)软件测试技术
一个完整的软件开发周期分为六个阶段:问题的定义与规划、需求分析、软件设计、程序编码、软件测试与运行维护。其中,软件测试作为软件面向用户前的最后一个步骤,决定着软件的质量,在整个软件开发周期中显得尤为重要。伴随商业软件的不断发展,软件的复杂度越来越高,开发周期越来越短,用户对软件的要求也越来越严格,这无疑更进一步提升了软件测试的重要程度。软件测试包含三个主要任务:测试、调试和验证。其中调试工作最为困难,最为耗时,需要的代价也最高。因为,测试和验证过程通常采用黑盒方式进行,只需要考察软件在特定输入下是否产生了正确的输出结果,不需要涉及从输入到输出的复杂过程。然而,调试过程需要定位软件缺陷在代码中的具体位置,必须通过白盒方式完成。黑盒的测试和验证过程与代码量以及代码结构的复杂程度无关。然而,在白盒的调试过程中,代码量的增大会导致调试时间和空间复杂度的增高。并且,复杂的代码逻辑结构也会增加调试的时间复杂度和难度。即白盒调试的复杂度与代码量以及代码结构的复杂程度呈正相关关系。
(2)程序错误诊断技术
程序错误诊断主要包含错误发现和错误定位两个过程,程序错误的最终修正得益于之前对错误的准确发现和快速定位。因此,程序错误诊断大致包含两个步骤:一是通过分析程序的动态执行行为,鉴别程序是否存在错误;二是一旦明确程序存在错误后,定位错误代码在程序中的具体位置。最初,调试人员完全依靠人工的方式对程序进行查错,例如在源代码中添加print语句、添加断言以及设置断点。这种错误诊断方法需要调试人员对代码充分了解,并且,错误诊断所耗费的时间依赖于调试人员的个人经验。面对大型程序,即使是有经验的调试人员,也无法在短时间内完成调试过程。后来,研究者们开始探索自动化的程序错误诊断技术,试图将乏味的代码调试工作交给电脑自动完成。目前,众多相关研究成果不仅在理论上得到证明,也已经在工业上投入使用,并取得了非常好的效果。
常见的自动化错误定位方法主要分为如下两类:
1)基于程序执行切片的方法。现阶段有人提出,执行切片指在某个特定测试用例下被执行的代码集合,该集合占所有代码量的百分比被称为该测试用例的代码覆盖。针对某个存在bug的程序,bug所处的代码语句被称为bug语句。该程序的某些测试用例由于执行了bug语句,即bug语句包含在相应的执行切片之中,所以会得到错误的输出结果,这些测试用例被称为失败的测试用例。某些测试用例由于未执行bug语句,即bug语句不包含在相应的执行切片之中,所以会得到正确的输出结果,这些测试用例被称为成功的测试用例。分析可知,失败测试用例的执行切片一定包含bug语句。基于此,通过计算多个不同失败测试用例的执行切片交集,可以缩小程序错误可能存在的代码范围。但是,简单通过这种方法得到的最终可疑代码范围仍然较大,因为大多数测试用例的代码覆盖都比较高,不同测试用例的执行切片拥有较大的交集。
2)基于代码可疑度计算的错误诊断方法。现阶段有人提出,如果某条代码语句被越多成功测试用例的执行切片覆盖,那么它就越不可能是bug语句,相反则越有可能是bug语句。于是,他们创新地利用某条代码被成功测试用例覆盖的次数和失败测试用例覆盖的次数来综合衡量该条代码是bug语句的可疑度,取得了非常显著的错误诊断效果。代码的可疑度被认为是该条代码为bug语句的概率。如果一条代码语句的可疑度越高,那么它就越有可能是bug语句,应该优先被调试人员检查。这类错误诊断方法的一般步骤为:首先,计算每条代码语句的可疑度;然后,对程序中所有代码语句按照可疑度的倒序进行排序;最后,调试人员从排序的第一条代码语句开始查找,直到找到程序真正的bug位置。
该类方法的核心问题是如何计算代码的可疑度,常见的方法有:
(1)tarantula
tarantula方法由jones等人在2002年提出,并通过大量实验验证了其有效性,成为领域内著名的错误诊断技术。
jones认为,某条程序语句,如果被大多数失败测试用例执行,那么它就很有可能是bug语句。但是,如果它同样也被大多数成功测试用例执行,那么它是bug语句的可能性将降低。tarantula可疑度的计算如公式
(2)sober
代码谓词是指代码中返回布尔值的条件判断语句。程序bug往往与代码谓词息息相关。找到程序执行过程中与bug最相关的谓词,调试人员就很容易找到bug所在的具体位置。因此,很多程序错误诊断技术将寻找bug语句转换为寻找与bug最相关的谓词表达式。sober就是计算代码谓词可疑度的程序错误诊断方法。在sober方法中,谓词的可疑度被表示为谓词的错误相关性,而谓词的错误相关性用该谓词分别在成功和失败执行过程中的返回值差异来衡量。具体地,如果某代码谓词在成功和失败执行过程中取真值的概率相差较大,则说明其具有较大的错误相关性,也就是具有较大的可疑度。
(3)rankcp
程序以代码语句为基本组成单位。通过语义分析可知,代码语句之间并不是相互独立的,它们存在一定的依赖关系。ferrante等人就将代码语句之间的依赖关系定义为控制依赖和数据依赖,并提出了表征程序这两类依赖关系的图模型结构-程序依赖图。baah等人极具创新地扩展了程序依赖图结构,为之加上概率参数,建立了概率程序依赖图模型,并利用它完成了程序错误诊断工作。基于ppdg的程序错误诊断技术以模型中相邻语句节点之间的条件概率作为代码可疑度的衡量标准。baah认为,在程序某次失败的执行过程中,如果某节点与其父节点之间的条件概率越小,则该节点所对应的代码语句的可疑度就越高,该条代码语句就越有可能是bug语句。这种可疑度计算方法被称为rankcp。rankcp可疑度的计算方法为公式
baah的实验结果显示,rankcp方法在保证效率的前提下,准确率远远优于tarantula和sober,因为它不仅考虑了成功和失败测试用例的执行特点,还充分利用了代码本身的语义信息(控制依赖、数据依赖)。但是,仔细分析后不难发现,rankcp可疑度为“局部可疑度”,因为它仅仅衡量了相邻节点之间的条件概率。然而,某些程序bug,并不一定会造成相邻节点的执行异常。相反地,这些程序bug对程序执行过程的影响是全局的、累积的,并非突发的。针对这种情况,rankcp方法很难确定真正的bug位置。因此,如何计算“全局可疑度”来衡量程序执行的累积异常,成为程序错误诊断技术上的新思路。同时,rankcp方法需要构建包含所有程序语句的图模型,图模型的节点个数对应整个程序的代码语句条数。面对大规模的程序,该方法的复杂度较高,不具备现实可行性。如果能降低图模型的规模,将使得错误诊断技术更具普适性。
综上所述,关于程序错误诊断的研究由来已久,并且已经产生了很多有价值的技术成果。其中,提供代码可疑度排序的方法比缩小可疑代码范围的方法更加实用。而在代码可疑度的众多计算方法中,rankcp方法具有较高的准确率和效率,在一定程度上领先其它方法。但是,由于仅仅考虑了程序执行过程中发生的局部异常,并且需要构建规模较大的图结构,导致其在程序错误诊断中具有一定的局限性。因此需要解决如何计算代码语句在全局的可疑度,并且降低整个模型的规模,将会对错误诊断效果产生较大提升的问题,本技术领域尚未出现具有理想效果的技术方案。
技术实现要素:
针对现有的基于程序依赖图的错误诊断方法在程序错误诊断中只能诊断程序执行过程中发生的局部异常,并且需要构建规模较大的图结构的问题,本发明设计了分层错误诊断框架,首先在函数层进行错误诊断,帮助程序测试人员定位到bug所在的具体函数。当确定目标函数后,再对目标函数里的所有程序语句进行错误诊断,帮助调试人员定位到bug所在的具体程序语句,减少了错误诊断的空间消耗和时间消耗。在完成函数层或语句层的测试预言和错误定位任务时,本发明设计了基于贝叶斯网络推理的错误诊断技术,以代码元素为节点,以代码元素之间的依赖关系为边的贝叶斯网络程序依赖图,通过在贝叶斯网络程序依赖图上进行跨节点概率推理,计算得到某测试用例为成功测试用例的概率,完成测试预言工作,计算得到每个代码元素的全局可疑度,完成错误定位工作,解决了rankcp等技术中计算的局部可疑度存在的问题。本发明在贝叶斯网络程序依赖图的结构学习过程中将mic理论应用在依赖关系的学习中,提高了统计依赖的准确度,在贝叶斯网络程序依赖图参数学习过程中,改进并应用laplace平滑策略,解决测试数据稀松性问题。
本发明提供的技术方案是一种基于贝叶斯网络推理的软件错误分层诊断方法,包括以下步骤:
一种基于贝叶斯网络推理的软件错误分层诊断方法,其特征在于,包括以下步骤:
步骤1,以函数为代码元素,构建函数层贝叶斯网络程序依赖图,bnpdg是表示代码元素依赖关系的贝叶斯网络模型。函数层bnpdg表示为一个三元组(v,e,p)。v代表节点集合,每个节点代表一个代码元素,即一个自定义函数。函数节点,有其对应的状态空间,不同状态值代表该代码元素不同的执行过程。e代表有向边的集合,每条有向边都代表代码元素之间的依赖关系,可以是语义依赖,包括控制依赖和数据依赖,也可以是通过测试数据得到的统计依赖。p代表网络参数,即每个节点的条件概率分布。构建函数层bnpdg包括以下子步骤,
步骤1.1,将要测试的程序代码生成程序依赖图,确定函数层bnpdg语义依赖关系。
步骤1.2,利用程序执行数据生成统计依赖关系图,确定函数层bnpdg统计依赖关系。
步骤1.3,融合控制依赖边、数据依赖边和统计依赖边,形成基于贝叶斯网络的程序依赖图初始结构。
步骤1.4,去掉基于贝叶斯网络的程序依赖图初始结构中的环路。
步骤1.5,学习基于贝叶斯网络的程序依赖图中的参数信息。
步骤2,在函数层贝叶斯网络程序依赖图上进行测试预言的概率推理,判断某个测试用例对整个程序来说是否为失败的测试用例。当确定某测试用例为失败测试用例以后,在函数层贝叶斯网络程序依赖图上进行错误定位的概率推理,计算代码元素的可疑度并排序,得到按照可疑度排序的函数序列。
步骤3,按照函数的可疑度排序,以每一个函数内的语句为代码元素,构建对应函数的语句层贝叶斯网络程序依赖图。语句层bnpdg可以表示为一个三元组(v,e,p)。v代表节点集合,每个节点代表一个代码元素,即一条程序语句。语句节点,有其对应的状态空间,不同状态值代表该代码元素不同的执行过程。e代表有向边的集合,每条有向边都代表代码元素之间的依赖关系,可以是语义依赖,包括控制依赖和数据依赖,也可以是通过测试数据得到的统计依赖。p代表网络参数,即每个节点的条件概率分布。构建语句层bnpdg包括以下子步骤,
步骤3.1,将要测试的程序代码生成程序依赖图,确定语句层bnpdg语义依赖关系。
步骤3.2,利用程序执行数据生成统计依赖关系图,确定语句层bnpdg统计依赖关系。
步骤3.3,融合控制依赖边、数据依赖边和统计依赖边,形成基于贝叶斯网络的程序依赖图初始结构。
步骤3.4,去掉基于贝叶斯网络的程序依赖图初始结构中的环路。
步骤3.5,学习基于贝叶斯网络的程序依赖图中的参数信息。
步骤4,按照函数的可疑度排序对函数进行逐一检查。检查某函数时,在该函数的语句层bnpdg上进行测试预言的概率推理,找到bug函数。在该bug函数的语句层贝叶斯网络程序依赖图上进行错误定位的概率推理,得到函数中所有语句的可疑度排序。按照语句的可疑度排序对语句进行逐一检查,直到找到真正的bug语句。
在上述的一种基于贝叶斯网络推理的软件错误分层诊断方法,所述步骤1.2具体包括:每个节点对都计算一个统计因子,该统计因子用于衡量该节点对中两个节点之间是否存在依赖关系或者存在依赖关系的可能性大小,然后对得到的依赖关系进行剪枝,目的是去掉依赖关系中的间接依赖关系,最后,根据条件独立性测试确定依赖关系的方向,选择最大信息系数作为统计因子,所有的统计因子都可以通过调用mine工具箱计算得到。
在上述的一种基于贝叶斯网络推理的软件错误分层诊断方法,所述步骤1.5具体包括:基于laplace平滑原理,在贝叶斯估计时设置先验参数,然后调整先验参数,改进平滑公式。贝叶斯估计先假设待估参数满足某一先验分布,然后用当前训练样本修改先验分布,得到参数的后验分布。具体是:
改进的平滑策略学习bnpdg的条件概率参数,在先验参数α=(α1,α2,...,αn)的指定问题上充分考虑程序执行样本的特点
首先,结合代码元素状态分析与最大熵原理,确定va节点各个状态对应干预样本的占比。
然后,根据干预样本与当前样本的比例,确定干预样本的条数。结合条件概率参数
在上述的一种基于贝叶斯网络推理的软件错误分层诊断方法,所述步骤2具体包括:
步骤1,定义bnpdg中的节点,根据其语义依赖关系分成三类:输入节点、输出节点和逻辑节点。输入节点代表程序的输入环境,定义为不控制或数据依赖于其它任何节点的节点;输出节点代表程序的输出结果,定义为没有任何节点控制或数据依赖于它的节点;逻辑节点代表整个程序的逻辑处理过程,定义为bnpdg中除了输入、输出节点以外的其它节点。
步骤2,测试预言的概率推理,判断失败测试用例。
令bnpdg中的输入节点集合为
步骤3,错误定位的概率推理,计算函数可疑度。
bnpdg的输出节点集合为
用给定输出节点状态后该逻辑节点的条件概率进行衡量。分析发现,对于某些特殊的代码元素
在上述的一种基于贝叶斯网络推理的软件错误分层诊断方法,所述步骤1、2、3、4具体包括:
设计了分层错误诊断框架,首先在函数层进行错误诊断,再对目标函数里的所有程序语句进行错误诊断,减少了错误诊断的空间消耗和时间消耗。
在完成函数层或语句层的测试预言和错误定位任务时,本发明设计了基于贝叶斯网络推理的错误诊断技术,以代码元素为节点,以代码元素之间的依赖关系为边的贝叶斯网络程序依赖图,通过在贝叶斯网络程序依赖图上进行跨节点概率推理,计算得到某测试用例为成功测试用例的概率,完成测试预言工作,计算得到每个代码元素的全局可疑度,完成错误定位工作,解决了rankcp等技术中计算的局部可疑度存在的问题。
在上述的一种基于贝叶斯网络推理的软件错误分层诊断方法,所述步骤1.2具体包括:
步骤1,定义代码元素的状态。假设程序p包含n个代码元素,v={v1,v2,...,vn},m个测试用例对应的执行数据集为d={d1,d2,...,dm},每条执行数据
s(vp)根据代码元素的数据依赖关系确定,代表该代码元素中变量的不同来源情况。假设代码元素vp中使用了两个变量a和b,a变量数据依赖于代码元素vr或者vs,b变量数据依赖于vt,如附图4a所示。则在执行过程中,vp中变量的来源存在三种情况:一是a变量来源于vr,b变量来源于vt;二是a变量来源于vs,b变量来源于vt;三是该代码元素未执行。对应的三种执行状态表示为s(vp)={(da(vr),db(vt)),(da(vs),db(vt)),⊥},其中,dvariable(node)表示变量variable来源于节点node,“⊥”表示程序未执行。如果该代码元素不数据依赖于任何节点,则存在两种执行状态“丅”和“⊥”,分别表示程序执行和未执行。
步骤2,构建统计依赖关系无向图,旨在找出可能存在相互依赖关系的节点对。当节点对中两个节点之间的mic值(统计因子)大于一定阈值的时候,认为两节点之间存在依赖关系,在统计依赖关系无向图中添加一条对应的无向边;否则,不添加无向边。
步骤3,消除非直接依赖关系边。在统计依赖关系无向图,可能存在非直接依赖关系边。这是因为,两节点之间即使不存在直接依赖关系,仍然会因为存在间接依赖关系,或者共同依赖于同一节点,而导致其mic值较高。在贝叶斯网络的构建过程中必须去掉这些非直接依赖边。
步骤4,确定直接依赖边的方向。通过统计因子只能得到依赖关系是否存在,而不能得到依赖关系的具体方向。采用chenxw等人提出的方法确定统计依赖关系图中的依赖关系方向。具体分为四个步骤,第一步,三节点链上的条件独立性测试。第二步,有限节点环路评分。第三步,条件独立性测试。第四,有向图结构评分。经历了前面三个步骤,网络中剩余的无向边条数较少。则可以采用穷举法得出最优的方向赋值方案。
本发明具有如下优点:减少了错误诊断的空间消耗和时间消耗。如果不采用分层框架,错误诊断的计算量级为整个程序的代码语句量;采用分层框架,错误诊断的计算量级仅为程序内的函数个数和单个函数内的代码语句量。在完成函数层或语句层的测试预言和错误定位任务时,本发明设计了基于贝叶斯网络推理的错误诊断技术,通过在贝叶斯网络程序依赖图上进行跨节点概率推理,计算得到某测试用例为成功测试用例的概率,完成测试预言工作,计算得到每个代码元素的全局可疑度,更加准确地完成错误定位工作。本发明的技术方案具有简单、快速的特点,能够较好地提高对软件错误诊断的准确率和效率。
附图说明
图1为本发明实施例的流程图;
图2为本发明实施例的贝叶斯网络模型示例;
图3a为本发明实施例的函数层源程序;
图3b为本发明实施例的函数层源程序对应的函数层控制依赖示例;
图3c为本发明实施例的函数层源程序对应的函数层数据依赖示例;
图4a为本发明实施例的代码元素节点的状态列表示例;
图4b为本发明实施例的语句层源程序(图5a)代码元素节点的状态列表示例;
图4c为本发明实施例的函数层源程序(图3a)代码元素节点的状态列表示例;
图5a为本发明实施例的语句层源程序;
图5b为本发明实施例的语句层源程序对应的控制流图;
图5c为本发明实施例的语句层源程序对应的程序依赖图;
图6为本发明实施例基于mic的统计依赖关系学习过程;
图7a为本发明实施例统计依赖关系无向图中的三角形环路示例;
图7b为本发明实施例统计依赖关系无向图中造成三角形环路的三种情况示例;
图8为本发明实施例bnpdg结构学习过程;
图9a为本发明实施例语句层bnpdg网络结构生成过程的语义依赖关系(控制依赖、数据依赖)示例;
图9b为本发明实施例语句层bnpdg网络结构生成过程的统计依赖关系示例;
图9c为本发明实施例语句层bnpdg网络结构生成过程的bnpdg初始结构示例;
图9d为本发明实施例语句层bnpdg网络结构生成过程的语句层bnpdg实例;
图10为本发明实施例bnpdg模型的节点划分;
图11为本发明实施例基于bnpdg的测试预言技术;
图12为本发明实施例基于bnpdg的可疑度计算;
图13为本发明实施例基于bnpdg的错误定位过程;
图14为本发明实施例findmax函数通过frama-c生成的程序依赖图;
图15为本发明实施例schedule程序通过calltree生成的函数调用图;
图16为本发明实施例分层错误诊断技术示意图;
具体实施方式
以下结合附图和实施例详细说明本发明技术方案。
实施例以如附图3a和附图5a所示的源程序为例。
基于上述的源程序,本发明设计的基于贝叶斯网络推理的软件错误分层诊断方法流程见附图1,所有步骤可由本领域技术人员采用计算机软件技术实现流程自动运行。实施例具体实现过程如下:
步骤1,以函数为代码元素,构建函数层贝叶斯网络程序依赖图,(bnpdg,bayesiannetworkbasedprogramdependencegraph)bnpdg是表示代码元素依赖关系的贝叶斯网络模型。函数层bnpdg可以表示为一个三元组(v,e,p)。v代表节点集合,每个节点代表一个代码元素,即一个自定义函数。函数节点,有其对应的状态空间,不同状态值代表该代码元素不同的执行过程。e代表有向边的集合,每条有向边都代表代码元素之间的依赖关系,可以是语义依赖,包括控制依赖和数据依赖,也可以是通过测试数据得到的统计依赖。p代表网络参数,即每个节点的条件概率分布。以附图3a源程序为例,构建函数层bnpdg包括以下子步骤,
步骤1.1,将要测试的程序代码生成程序依赖图,确定函数层bnpdg语义依赖关系。
在函数层bnpdg,函数为代码元素也是基本执行单元。函数层语义依赖包含函数控制依赖和函数数据依赖,并将代码元素间的这两种依赖关系表示为有向图结构,即程序依赖图。本技术的函数控制依赖来源于函数之间的相互调用关系。函数控制依赖关系表示某个函数的执行与否取决于另一个函数,假设程序p中存在两个函数a和b,在a函数的函数体中调用了b函数。那么,b函数控制依赖于a函数。附图3a所示的源程序定义了一个全局变量,当输入数据为正数时,全局变量加1,否则全局变量减1。在该程序中,main函数先后调用了add、minus和print函数,因此,如附图3b所示,这三个函数分别控制依赖于main函数。本技术的函数数据依赖来源于不同函数对全局变量的共同作用。函数数据依赖关系表示某个函数中全局变量的取值来源于另一个函数对该全局变量的赋值。假设程序p中存在两个函数a和b以及全局变量v,a函数对v变量进行了赋值,b函数使用了v变量的值,并且存在这样的执行情况:a函数开始执行到b函数开始执行之间,除了a函数没有其它函数对v变量进行重新赋值。那么,b函数数据依赖于a函数。在附图3a所示的源程序中,add函数对全局变量count进行了赋值,print函数使用了count的值。并且,执行完add函数后,如果循环就此结束,则在调用print函数之前都不会对count再进行重新赋值。因此,如附图3c所示,print函数数据依赖于add函数。同理,print函数也数据依赖于minus函数。
如果两个函数之间存在控制依赖或者数据依赖,则认为它们之间存在语义依赖关系。代表这种依赖关系的有向边表示函数层bnpdg语义依赖关系。为了得到函数层的语义依赖关系,可以通过calltree生成对应程序的函数调用图,如附图15所示。
步骤1.2,利用程序执行数据生成统计依赖关系图,确定函数层bnpdg统计依赖关系。
生成统计依赖关系图,依然是以函数为代码元素,进行统计依赖关系学习,通过样本数据的统计分析,得到函数节点之间直接依赖关系的猜测。统计依赖关系的确定,本技术选择以下方法:每个节点对都计算一个统计因子,该统计因子用于衡量该节点对中两个节点之间是否存在依赖关系或者存在依赖关系的可能性大小,然后对得到的可能依赖关系进行剪枝,目的是去掉依赖关系中的间接依赖关系,最后,根据条件独立性测试确定依赖关系的方向。其中本技术选择mic(maximalinformationcoefficient,最大信息系数)作为统计因子,具体实施时,所有的mic都可以通过调用mine工具箱计算得到。
在统计依赖关系学习之前,定义代码元素的状态。统计依赖关系的学习依赖于代码的执行数据,即程序在不同测试用例的执行过程中各个代码元素所具有的状态。假设程序p包含n个代码元素,v={v1,v2,...,vn},m个测试用例对应的执行数据集为d={d1,d2,...,dm},每条执行数据
s(vp)根据代码元素的数据依赖关系确定,代表该代码元素中变量的不同来源情况。假设代码元素vp中使用了两个变量a和b,a变量数据依赖于代码元素vr或者vs,b变量数据依赖于vt,如附图4a所示。则在执行过程中,vp中变量的来源存在三种情况:一是a变量来源于vr,b变量来源于vt;二是a变量来源于vs,b变量来源于vt;三是该代码元素未执行。对应的三种执行状态表示为s(vp)={(da(vr),db(vt)),(da(vs),db(vt)),⊥},其中,dvariable(node)表示变量variable来源于节点node,“⊥”表示程序未执行。如果该代码元素不数据依赖于任何节点,则存在两种执行状态“丅”和“⊥”,分别表示程序执行和未执行。附图5a示例程序的各个语句节点状态列表如附图4b所示,附图3a示例程序的各个函数节点状态列表如附图4c所示。
基于mic的统计依赖关系的学习如附图6所示。
首先,构建统计依赖关系无向图,旨在找出可能存在相互依赖关系的节点对。当节点对中两个节点之间的mic值(统计因子)大于一定阈值的时候,认为两节点之间存在依赖关系,在统计依赖关系无向图中添加一条对应的无向边;否则,不添加无向边。如果设置固定的静态阈值,会导致生成的统计依赖关系无向图中可能有孤立节点存在。而存在孤立节点的统计依赖关系图不满足贝叶斯网络的结构要求并且孤立的代码元素不符合代码的实际特点。因此,本技术选择采用动态阈值设定方案。针对不同节点,采用不同的阈值,保证该节点与其它节点一定有依赖关系存在。具体方法是:针对节点对(vi,vj),计算出vi与其它节点之间的最大mic值,记为mmic(vi),vj与其它节点之间的最大mic值,记为mmic(vj)。如果vi与vj之间的mic值mic(vi,vj)大于等于α·mmic(vi)或者α·mmic(vj),则认为vi与vj之间存在依赖关系,否则认为它们之间不存在依赖关系。α为阈值参数,0≤α≤1,针对不同的程序可以设定不同的α。
其次,消除非直接依赖关系边。在统计依赖关系无向图,可能存在非直接依赖关系边。这是因为,两节点之间即使不存在直接依赖关系,仍然会因为存在间接依赖关系,或者共同依赖于同一节点,而导致其mic值较高。在贝叶斯网络的构建过程中必须去掉这些非直接依赖边。非直接依赖关系往往存在于统计依赖关系无向图中的三角形环路中,因此,消除非直接依赖关系边的问题,被转化为消除三角形环路的问题。如附图7a所示的三角形环路,可能由附图7b所示的三种不同情况导致。第一种情况,va依赖于vc,vc依赖于vb,因va间接依赖于vb而在统计依赖关系无向图中存在边(va,vb)。要判断这种情况是否存在,只需要判断va和vb是否关于vc条件独立,即p(va|vc)=p(va|vb,vc)是否成立。如果等式成立,去掉统计依赖关系无向图中的边(va,vb)即可。第二种情况,va同时依赖于vc和三角形环路外的另一节点v,vc和v都依赖于vb。这时,即使等式p(va|vc)=p(va|vb,vc)不成立,仍然会因va间接依赖于vb而在统计依赖关系无向图中存在边(va,vb)。要判断这种情况是否存在,关键是判断是否存在节点v,满足p(va|vc,v)=p(va|vb,vc,v)。如果存在,并且有多个,选择与va和vb依赖关系最大的v节点(即mic(va,v)+mic(vb,v)值最大),在统计依赖关系无向图中添加边(va,v)和(va,v),去掉边(va,vb)。第三种情况,在前面两种情况都不存在的情况下,说明va和vb同时依赖于vc,虽然va和vb没有直接或者间接依赖关系,但其mic值仍然会较高而使得统计依赖关系无向图中存在边(va,vb)。这个时候,去掉边(va,vb)即可。
最后,确定直接依赖边的方向。通过统计因子只能得到依赖关系是否存在,而不能得到依赖关系的具体方向。采用chenxw等人提出的方法确定统计依赖关系图中的依赖关系方向。具体分为四个步骤,第一步,三节点链上的条件独立性测试。假设统计依赖关系无向图中存在三节点链va-vc-vb,要求va和vb之间除了vc没有其它路径连通。在给定节点vc状态的情况下,如果p(va|vc)=p(va|vb,vc)不成立,则确定va-vc-vb之间的依赖关系方向为va→vc←vb。第二步,有限节点环路评分。对于一个包含有限节点环路(通常指四节点和五节点环路)的方向确定问题,穷举所有赋值方案,然后根据结构评分函数择优选择方向赋值方案。针对四节点无向环路的方向确定问题,总共有24=16种方向赋值方案。排除掉其中两种有向环路方案,最终还剩16-2=14种方向赋值方案。针对五节点无向环路的方案确定问题,总共有25-2=30种方向赋值方案。结构评分函数选择bde评分,对于某条边,如果两个方向的bde评分相等,则暂时不对这条边进行方向确定。第三步,条件独立性测试。条件独立性测试主要解决这样的情况:va→vb-vc,即vb有一条入度的有向边,并且与一条无向边相连。如果p(vc|vb)=p(vc|va,vb)成立,则说明在给定vb的条件下,vc和va条件独立,vb-vc的方向为vb→vc。第四,有向图结构评分。经历了前面三个步骤,网络中剩余的无向边条数较少。则可以采用穷举法得出最优的方向赋值方案。
在统计依赖关系图中,如果存在边va→vb,则认为代码元素va和vb之间存在统计依赖关系,vb统计依赖于va。
步骤1.3,融合控制依赖边、数据依赖边和统计依赖边,形成基于贝叶斯网络的程序依赖图初始结构。
将前两个步骤生成的程序依赖图和程序执行数据生成的统计依赖关系图进行融合。因为代码元素与函数一一对应,所以基于贝叶斯网络的程序依赖图节点不变。对与涉及的控制依赖边、数据依赖边和统计依赖边,如果两节点之间同一方向的有向边重复,只保留一条。
步骤1.4,去掉基于贝叶斯网络的程序依赖图初始结构中的环路。
首先,发现带环依赖图中的自循环结构,消除结构中的所有自循环。自循环指只有一个节点的环路结构。假设该节点为va,为了消除自循环结构,新增一个va节点的克隆节点v′a。v′a节点有着与原va节点相同的状态列表,和va节点代表相同的程序元素。添加一条边由新增节点v′a指向原节点va,并去掉之前的自循环边。
然后,找到结构中剩余的多节点循环结构,选择并断开首先要断开的环路。环路的长度用环路中边的条数来表示,如果带环结构中存在多条环路,去环时优先选择较短的环进行断开。这样做可以减少去环操作的平均次数。当选择好要断开的环路结构后,断开选择的环路。去环操作是为了使最终生成的网络结构满足贝叶斯网络的无环定义。
判断结构中是否还存在环路结构,如果存在,则继续遵循“短环优先”选择下一步要断开的环路。循环上述步骤,直到结构中不再存在任何环路结构。
步骤1.5,学习基于贝叶斯网络的程序依赖图中的参数信息。
贝叶斯网络的参数学习,指根据大量训练数据估计出网络结构中相邻节点之间的条件概率分布。对bnpdg的参数学习问题进行如下符号定义:假设bnpdg的网络结构中存在节点va,其父节点集合为pa(va)。va的状态列表里包含n个不同的状态值(a1,a2,...,an),pa(va)包含m个不同的联合状态(b1,b2,...,bm),每个联合状态bi为pa(va)中所有节点的状态有序对。参数学习的任务是根据训练数据集d=(d1,d2,...,dk),估计在pa(va)为某联合状态的条件下va的条件概率,即p(va=aj|pa(va)=bi),i=1,2,...,m,j=1,2,...,n。以附图9d所示的bnpdg结构为例,假设节点6代表va节点,则pa(va)={4,5}。节点6包含{(dmax(3),dv(5)),(dmax(7),dv(5)),⊥}三种状态,则n=3,对应(a1=(dmax(3),dv(5)),a2=(dmax(7),dv(5)),a3=⊥)。节点4包含{(di(1),dn(2)),(di(8),dn(2)),⊥}三种状态,节点5包含{t,⊥}两种状态,则pa(va)包含3×2=6种联合状态,对应m=6。相应待估参数如表1所示。
在bnpdg的参数学习中,需要估计的是在给定pa(va)联合状态为pva情况下节点va的条件概率p(va|pa(va)=pva),记为
为解决样本稀疏性问题,本技术使用的方法是laplace平滑。为了使的laplace平滑算法适用代码执行数据的自身规律,本技术将laplace平滑看作是贝叶斯估计的特例,相当于在贝叶斯估计时设置特殊的先验参数,然后利用调整先验参数的思想,改进平滑公式。贝叶斯估计先假设待估参数满足某一先验分布,然后用当前训练样本修改先验分布,得到参数的后验分布,使之更逼近真实分布。laplace平滑就是为参数添加先验分布来解决样本稀疏性问题。
根据laplace平滑贝叶斯估计的特例,在进行待估参数的后验分布计算之前,假设θ服从参数为α=(α1,α2,...,αn)的狄利克雷先验分布,条件概率参数
改进的平滑策略学习bnpdg的条件概率参数,在先验参数α=(α1,α2,...,αn)的指定问题上充分考虑程序执行样本的特点,做出以下两个方面的改进。
一方面。结合代码元素状态分析与最大熵原理,确定va节点各个状态对应干预样本的占比。
laplace平滑算法运用最大熵原理,令α1=α2=...=αn。这种平滑方法适合于对变量各个状态可能性大小完全未知的情况。但是,在本技术中特定的应用环境中,代码元素的不同状态值代表了不同的具体含义,通过对这些具体含义的语义分析,可以得到在程序执行过程中,不同状态值之间发生概率的粗略大小关系。将这种先验的大小关系作为确定va节点各个状态对应干预样本占比的参考依据,制定出更加准确的先验参数。
根据在步骤1.2中代码元素的状态定义,代码元素的状态分为两部分:一部分是执行状态,代表该代码元素执行时的不同数据依赖情况;另一部分是未执行状态,代表该代码元素未被执行。任何代码元素都有一个未执行状态“⊥”,一个或者多个执行状态。针对节点va,其状态列表为(a1,a2,...,an),其中,an=⊥,代表未执行状态。如果va不控制依赖于任何节点,除非出现代码执行异常,该代码元素都会被执行。这种情况下,执行状态所对应的干预样本条数应该远多于⊥状态对应的干预样本条数。如果va控制依赖于某节点,表示判断条件成立,则va执行,判断条件不成立,则va不执行。这种情况下,执行情况与不执行情况发生的概率大小无法预估。
令
另一方面,根据干预样本与当前样本的比例,确定干预样本的条数。
最大熵原理确定了干预样本中各状态的占比,接下来需要确定干预样本的总条数。干预样本的总条数决定了在参数学习过程中先验概率对后验概率的影响程度。在laplace平滑算法中,统一将干预样本的总条数设置为变量的状态个数n,这将导致先验概率对后验概率的影响程度随着当前样本量和变量状态个数的变化而不断波动。然而,在对
所以,结合条件概率参数
步骤2,在函数层贝叶斯网络程序依赖图上进行测试预言的概率推理,判断某个测试用例对整个程序来说是否为失败的测试用例。当确定某测试用例为失败测试用例以后,在函数层贝叶斯网络程序依赖图上进行错误定位的概率推理,计算代码元素的可疑度并排序,得到按照可疑度排序的函数序列。
判断某一个具体测试用例是否是失败测试用例的工作,被称为测试预言。给定一个失败的测试用例后,分析该测试用例的执行过程,定位出造成该测试用例执行失败的具体bug代码位置,被称为错误定位。测试预言和错误定位的过程中,在具体实施时,贝叶斯网络的概率推理工作由matlab贝叶斯工具箱(bayesiannetworkstoolbox,bnt)完成。本技术将bnpdg中的节点根据其语义依赖关系分成三类:输入节点、输出节点和逻辑节点。输入节点代表程序的输入环境,定义为不控制或数据依赖于其它任何节点的节点;输出节点代表程序的输出结果,定义为没有任何节点控制或数据依赖于它的节点;逻辑节点代表整个程序的逻辑处理过程,定义为bnpdg中除了输入、输出节点以外的其它节点。
测试预言的概率推理,判断失败测试用例。给定输入数据,如果程序的执行过程满足大部分测试用例的执行规律,得到的高概率的输出结果,则该测试用例很有可能是成功的测试用例。相反,如果程序的执行过程不满足大部分测试用例的执行规律,得到了小概率的输出结果,则该测试用例很有可能是失败的测试用例。
令bnpdg中的输入节点集合为
错误定位的概率推理,计算函数可疑度。当确定某测试用例为失败测试用例以后,在函数层bnpdg上进行错误定位的概率推理,得到程序中所有函数的可疑度排序。本技术统一利用输出节点的联合状态作为“条件”,计算每个逻辑节点相对于输出节点的条件概率,用其衡量代码元素的可疑度。在一个失败的测试用例里,输出节点的联合状态代表该测试用例的错误输出结果。针对非bug节点的执行状态,错误的输出结果应该属于小概率异常,所以它们之间存在较小的条件概率。然而,针对bug节点,由于它的状态与最后的错误输出状态存在因果关系,所以它们之间存在较高的条件概率。因此,对于每个代码元素,如果其相对于错误输出状态的条件概率越大,说明正是其导致该错误输出结果的可能性就越大,该代码元素的可疑度就越高。
bnpdg的输出节点集合为
步骤3,按照函数的可疑度排序,以每一个函数内的语句为代码元素,构建对应函数的语句层贝叶斯网络程序依赖图。语句层bnpdg可以表示为一个三元组(v,e,p)。v代表节点集合,每个节点代表一个代码元素,即一条程序语句。语句节点,有其对应的状态空间,不同状态值代表该代码元素不同的执行过程。e代表有向边的集合,每条有向边都代表代码元素之间的依赖关系,可以是语义依赖,包括控制依赖和数据依赖,也可以是通过测试数据得到的统计依赖。p代表网络参数,即每个节点的条件概率分布。以附图5a源程序为例,构建语句层bnpdg包括以下子步骤,
步骤3.1,将要测试的程序代码生成程序依赖图,确定语句层bnpdg语义依赖关系。
在语句层bnpdg,程序语句为代码元素也是基本执行单元。语句层语义依赖包含语句控制依赖和语句数据依赖,并将代码元素间的这两种依赖关系表示为有向图结构,即程序依赖图。首先构建控制流图,程序p所对应的控制流图g是一个有向图,可以用元组(v,e,start,end)表示。其中,v={v1,v2,...,vn}代表g中的节点集合,单个节点对应p中的单条代码语句。e={e1,e2,...,em}代表g中有向边的集合。每条有向边ei=<vp,vq>表示执行完vp语句后,可能立即执行vq语句。start为程序的入口节点,表示程序从此处开始执行,start节点没有前驱节点。end为程序的出口节点,表示程序执行结束,end节点没有后继节点。附图5b即为附图5a中源程序所对应的控制流图。如图所示,执行完语句6后,可能因为判断条件为false而直接执行语句8,所以控制流图中存在有向边6→8。同时,执行完语句1后,不可能越过语句2而立即执行语句3,所以控制流图中不存在有向边1→3。
语句控制依赖关系表示某条语句的执行与否取决于另一条语句。在控制流图g中,如果节点vp和vq满足以下条件,则称vq控制依赖于vp:①存在一条以vp为起点的边e1,满足起始于e1结束于end的所有路径都包含vq节点;②存在一条以vp为起点的另一条边e2(e1≠e2),满足起始于e2结束于end的所有路径中存在一条路径不包含vq节点。附图5c表示的程序依赖图中,实线边代表控制依赖关系。如控制流图所示,存在边e1=<6→7>,起始于e1结束于end的所有路径都包含节点7;e2=<6→8>是不同于e1的另一条边,存在起始于e2结束于end的路径<6→8→4→10→end>不包含节点7。因此,节点7控制依赖于节点6,程序依赖图中存在代表此控制依赖关系的实线边<6→7>。将节点对应到源代码,表示findmax函数中的第7条程序语句“max=v;”控制依赖于第6条程序语句“if(v>max)”。
语句数据依赖关系表示某条语句中变量的取值来源于另一条语句对该变量的赋值。在控制流图g中,如果节点vp和vq满足以下条件,则称vq数据依赖于vp:①在vp所代表的程序语句中定义了一个变量m(或者对变量m进行赋值);②vq所代表的程序语句使用了变量m的值;③g中存在一条vp到vq的路径,该路径中除了vp没有任何其它节点对变量m进行重定义或赋值。程序依赖图中的虚线边代表数据依赖关系。如控制流图所示,在节点5所代表的程序语句“intv=read_int();”中定义了变量v,并给变量v进行了赋初值操作。在节点6所代表的程序语句“if(v>max)”中使用了变量v的值。并且,存在一条起始于节点5并结束于节点6的路径<5→6>,该路径中除了节点5没有任何其它程序语句对变量v进行重新赋值操作。因此,节点6数据依赖于节点5,程序依赖图中存在代表此数据依赖关系的虚线边<5→6>,该边上标注的变量v就是与此数据依赖关系相关联的变量。
如果两条程序语句之间存在控制依赖或者数据依赖,则认为它们之间存在语义依赖关系。代表这种依赖关系的有向边表示语句层bnpdg的语义依赖关系。为了得到语句层的语义依赖关系,具体实施时,可以通过frama-c生成对应函数的程序依赖图,如附图14所示。
步骤3.2,利用程序执行数据生成统计依赖关系图,确定语句层bnpdg统计依赖关系。
语句层统计依赖图的生成步骤如同步骤1.2,不同的是函数层以函数作为代码元素,得到函数层的统计依赖关系,而语句层以程序语句作为代码元素,得到语句层的统计依赖关系。
步骤3.3,融合控制依赖边、数据依赖边和统计依赖边,形成基于贝叶斯网络的程序依赖图初始结构。
语句层融合控制依赖边、数据依赖边和统计依赖边的步骤,如同步骤1.3。
步骤3.4,去掉基于贝叶斯网络的程序依赖图初始结构中的环路。
语句层去环的步骤,如同步骤1.4。附图9以附图5a中示例程序为例,展示了其语句层bnpdg的生成过程。附图9a表示程序语句之间的语义依赖关系,实线表示控制依赖,虚线表示数据依赖。附图9b表示程序语句之间的统计依赖关系,其中3条统计依赖边与语义依赖边重合,另外5条统计依赖边表示语义依赖边未表达出的依赖信息。附图9c表示语句层bnpdg的初始结构,是附图9a和附图9b中依赖边的融合。附图9d表示最终的bnpdg网络结构,为附图9c去环后的结果。
步骤3.5,学习基于贝叶斯网络的程序依赖图中的参数信息。
基于贝叶斯网络的程序依赖图中的参数学习步骤,如同步骤1.5。
步骤4,按照函数的可疑度排序对函数进行逐一检查。检查某函数时,在该函数的语句层bnpdg上进行测试预言的概率推理,找到bug函数。在该bug函数的语句层贝叶斯网络程序依赖图上进行错误定位的概率推理,得到函数中所有语句的可疑度排序。按照语句的可疑度排序对语句进行逐一检查,直到找到真正的bug语句。
在函数的语句层bnpdg上进行测试预言的概率推理,概率推理步骤如同步骤二,如果整个程序的失败测试用例对该函数来说也是失败的测试用例,则确认该函数为bug函数。如果整个程序的失败测试用例对该函数来说是成功的测试用例,则确认该函数不是bug函数,跳过该函数检查下一可疑函数,直到找到真正的bug函数。bug函数的语句层贝叶斯网络程序依赖图上进行错误定位的概率推理步骤如同步骤二,得到可疑度按从大到小进行排序的逻辑节点列表,即对应函数内语句序列。调试人员按照顺序检查逻辑节点对应的代码元素,直到找到真正的bug代码元素。由于在语句层bnpdg中,输入节点多为输入语句和变量的初始化赋值语句,输出节点多为打印语句和返回语句,这些语句出现bug的可能性比较低,所以技术只考虑bug出现在逻辑语句中的情况。
表1bnpdg待估参数列表示例
本技术中所描述的具体实施例仅仅是对本发明精神作举例说明。本发明所属技术领域的技术人员可以对所描述的具体实施例做各种各样的修改或补充或采用类似的方式替代,但并不会偏离本发明的精神或者超越所附权利要求书所定义的范围。