原文标题:Deep Just-In-Time Defect Localization
原文作者:Qiu F, Gao Z, Xia X, et al.
发表期刊:IEEE TRANSACTIONS ON SOFTWARE ENGINEERING
原文链接:https://ieeexplore.ieee.org/abstract/document/9653844
笔记作者:[email protected]
笔记小编:[email protected]
在软件开发和维护期间,缺陷定位是软件质量保证的重要部分。尽管已经为缺陷定位提出了不同的技术,但是它们只能在缺陷被暴露之后才起作用,这对于适应日常开发中新引入的缺陷来说可能太晚了,并且成本很高。因此,很多JIT(Just-In-Time)缺陷定位技术被提出来,目的是在一个change commit被提交后定位可能存在Bug的代码。本文提出了一个名为DEEPDL的JIT缺陷定位方法,用于定位引入变更的缺陷代码行,并在14个开源Java项目上进行了评估。
在本文中,作者主要关注(1)实时缺陷预测和(2)缺陷定位。缺陷预测任务旨在帮助开发人员检查一个commit是否是有错误的commit;缺陷定位任务旨在通过分析各种动态执行信息(例如,失败/通过的测试、错误报告)来帮助开发人员定位潜在的错误代码元素(例如,语句或方法)。现有缺陷定位任务的缺点在于:严重依赖于动态执行信息,并且仅在缺陷被暴露后才起作用,这对于新引入的错误来说可能太晚并且代价很高。而现有缺陷预测任务的缺点在于:没有定位可疑位置。
与实时缺陷预测和缺陷定位相比,JIT缺陷定位的好处在于:(1)细粒度检:与在文件级或模块级检测错误变化的JIT缺陷预测相比,JIT缺陷定位可以在精细粒度(即行级)定位错误代码元素。这种细粒度的检测可以节省开发人员定位和解决缺陷的时间和精力;(2)早期检测:与严重依赖于缺陷症状并且只能在缺陷暴露后工作的故障定位相比,JIT缺陷定位是在代码更改发生时执行的,换句话说,JIT缺陷定位在提交提交时定位有问题的代码行。早期检测可以在早期防止错误代码,并给开发人员及时的反馈。
图1是作者给出的motivation示例。该commit的目的是"Dynamically load Hadoop security module when available",涉及对21个文件的change,其中包括474处添加和144出删除。在这个commit中手工地逐个检查改变的文件是费时费力的。因此,作者提出了DEEPDL工具,其使用场景为:(1)快速识别历史提交的缺陷位置。如图2所示,开发人员利用JIT缺陷预测工具来检测bug commit,然后使用DEEPDL根据检测出存在bug的代码行;(2)用来提醒开发人员在提交新的commit时识别潜在的bug位置。当开发人员提交代码变更时,DEEPDL可以自动定位可疑的bug代码并提供通知,从而帮助开发人员降低引入错误的风险,提高软件的可靠性。
DEEPL模型的框架如图3所示。
(1)数据准备
作者使用与Yan等人的文章[1]相同的数据集,如表1所示。在数据采集阶段,分别识别Bug代码行和Clean代码行。clean代码行作为训练数据集用于构建"Clean"的自然语言模型;bug代码行用作测试数据集以评估bug定位的性能。按照Yan等人的论文思路,本文选在与之相同的start date和end date区间,按照时间顺序选择前60%的commit用于模型训练,剩余的40%用于模型测试。
例如:如图4所示,对于Flink项目,首先统计从2010-12-15到2017-10-01时间段内的所有提交,共11982个Commit。接着,识别splitting commit(在2015-03-08发生),并用于切分训练和测试数据集;其中,日期位于splitting commit之前的commit用于训练,位于其后的用于测试。
在拆分好数据集之后,为每一个在splitting commit之前的项目下载对应的快照(快照表示项目在特定时间点的状态)。在构建"Clean"自然语言模型时,需要确保下载的快照只包含clean代码行。因此,需要从下载的快照中删除bug代码行。本文使用RA-SZZ实现这一目的,具体如下:
bug-fix commit识别。对于每一个在splitting commit之后的commit,首先识别其是否是bug-fix commit。具体来说,对于一个commit,如果其包含与缺陷相关的信息,如:Fixed #233,则检查在issue tracking system中相应问题的报告,以确定该报告是否被定义为缺陷。如果报告被定义为缺陷并被解决,则将对应的commit标记为bug-fix commit。
bug-introducing commit识别。对于每一个bug-fix commit,利用RA-SZZ识别引入bug的commit。具体来说,RA-SZZ首先通过比较bug-fix commit与其之前版本来识别change行。接着,RA-SZZ过滤掉与缺陷变更无关的行,如:空行、注释行、格式修改等。之后,RA-SZZ通过变更历史回溯剩余的行以识别引入这些行的commit,这些commit被标记为bug-introducing commit。
删除bug代码行。对于每一个bug-introducing commit,如果其处于splitting commit之前,则说明该提交已经向训练数据集引入了bug行。因此,需要删除由bug-introducing commit引入的bug行。具体来说,将bug-introducing commit添加的和后来由bug-fix commit修复的行定义为bug行。bug行被进一步映射到下载的快照版本中,并从下载的快照版本中删除这些行;剩余的行被视作为clean行。
识别测试数据集中的bug代码行。对于每一个bug-introducing commit,如果其处于splitting commit之后,则将其添加到测试数据集中。这里需要识别测试数据集中的bug行以作为ground truth。为保证正确识别测试数据集中的bug行,在Yan等人的工作之后,作者进一步使用了5个月的窗口以尽可能覆盖bug-fix和bug-introducting commit。选择5个月作为窗口的原因是:80%的错误提交平均在5个月内被修复[1]。接着确定由bug-introducing commit所引入的测试数据集中的bug行。
最终构建的训练数据集和测试数据集结构如表2和表3所示。
作者说代码开源了https://github.com/Lifeasarain/DeepDL。但是真的开源了吗?
言归正传,收集完数据后,可以获得一组"Clean"快照。在"Clean"快照中,删除测试代码、空行、import声明和批注。剩余的代码组成代码行列表:。对于一个代码行,我们将其前两行和后两行作为其上下文信息,即:对于,作为一个基本行块,用以构建训练数据集。最终的训练数据集是有基本行块组合而成,即:。对于中的每一个,都是一组token序列。
对于测试数据集,包含一组引入bug的commit以及相对的查明bug的行。每个bug-introducting commit都包含一组添加行(即:常规的添加行和bug添加行)。对于引入bug的commit中添加的没一行,将其周围的两行与其组合起来形成基本行块,每个基本行块被送入到神经网络模型中计算可疑性得分(文中叫做naturalness)。得分排名靠前的添加行被认为是潜在的bug代码行。
由于代码语料库中的标识符名称具有很大的随意性,并且因开发者不同而有很大差异,简单地利用代码语料库中的传统标记化方法会导致严重的OOV(Out-of-Vocabulary)问题。为此,需要首先将源代码行进行token化,然后采用BPE(Byte Pair Encoding)方法[2]进行子词分段。
图5是利用BPE对Java代码片段进行token化的例子。"Basic Line Block"的第2行block.addChildToFront( newBranchInstrumentationNode(traversal);
被基于BPE token化为一组子词单元序列:[‘block’, ‘.’, ‘add’, ‘Child’, ‘ToFront’, ‘new’, ‘Branch’, ‘In’, ‘stru’, ‘mentation’, ‘Node’, ‘(’, ‘traversal’, ‘)’, ‘;’]
。在token化该行时,BPE算法遇到两个OOV词:addChildToFront和newBranchInstrumentationNode。对于newBranchInstrumentationNode,BPE将其拆分成一组字符,并通过运算将这些字符合并成词汇表中的已知词,即newBranchInstrumentationNode被转换成[ ‘new’, ‘Branch’, ‘In’, ‘stru’, ‘mentation’, ‘Node’ ]
。
对于每一个token序列,添加符号<EOL>
用于分割每一行,添加符号<EOS>
用于标记每一个基本行块的结束,即:对于基本行块,
$L_{i}=[l_{i-2},
(2)模型训练
naturalness用于表示一个元素对于给定文档的"surprised"程度(这个surprised着实不知道怎么翻译好)。现有的工作研究证明了"使用语言模型来捕捉软件naturalness的有效性"。研究人员发现,语言模型认为bug代码明显更加不"自然"。因此,受此发现的启发,作者在本文中构建了DEEPDL模型。
对于给定的基本行块,DEEPDL模型的目标是判断中心代码行相对于周围代码行是否"自然"。本文将这一目标看做是Seq2Seq问题。对于DEEPDL模型,源序列为基本行块,目标序列为每一个基本行块的中心代码行。给定一个源序列,模型生成对应的中心代码行;该中心代码行对于其上下文是"自然"的。
模型的结构如图6所示。
DEEPDL使用Transformer Encoder作为编码器,由6个残差编码器块堆叠而成。每个残差编码器块被分为两个子层:self-attention层和前馈网络层。编码器以token序列作为输入,第一个残差编码器块将token序列从上下文无关的表示转换成上下文相关的表示,之后的残差编码器块进一步细化该表示,最后一个残差编码器块输出最终的上下文编码。 如图6所示,本文使用了两种编码器:central line encoder和contextual line encoder,这两种编码器的结构是相同。不同点在于central line encoder的输入是代码行,输出对应的语义向量;而contextual line encoder的输入是基本行块中的4个上下文代码行,提取中心代码行之前的两行(即:)作为前置上下文,中心代码行之前后的两行(即:)作为后置上下文,输出对应的上下文语义向量。
解码器的结构与编码器类似,由6个解码器块组成。每个解码器块都有两个self-attention层和一个前馈网络层。本文设计了一个cross-attention层用于将编码器和解码器连接起来。cross-attention层以和为输入,输出隐藏状态。被送入解码器计算并获得预测序列。
(3)模型应用
DEEPDL模型的输入是一个有bug的commit(由JIT缺陷预测工具识别)或一个新提交的commit。给定一个commit,首先提取这个提交中所有添加的代码行,并构建基本行块。DEEPDL模型接收基本行块作为输入,输出预测序列。输出的预测序列被认为是"Clean"的。接着计算预测序列与添加的代码行之间的熵,其中,添加的代码行的熵可以通过计算该代码行内每个token的熵的平均值获得。所有添加代码行的熵作为可疑性得分,具有最高可疑性得分的代码行被认为是代码中可能的缺陷位置。
本文的部分实验结果如下:
参考文献
[1] Yan M, Xia X, Fan Y, et al. Just-in-time defect identification and localization: A two-phase framework[J]. IEEE Transactions on Software Engineering, 2020.
[2] Gage P. A new algorithm for data compression[J]. C Users Journal, 1994, 12(2): 23-38.
安全学术圈招募队友-ing
有兴趣加入学术圈的请联系 secdr#qq.com