引用

AHMAD B, TAN B, PEARCE H, et al. FLAG: finding line anomalies (in code) with generative AI [A]. 2023. arXiv: 2306.12643.

论文:https://arxiv.org/abs/2305.08360

仓库: https://zenodo.org/record/8012211.

运用生成性AI查找代码中的行异常

择要

代码中总是存在安全和功能性的毛病。
识别和定位这些毛病很困难,每每依赖于人工。
在本文中,我们提出了一种新的方法FLAG来帮忙调试。
FLAG基于天生式AI的词汇能力,特殊是大型措辞模型(LLM)来完成。
其输入一个代码文件,然后提取并重新天生文件中的每一行并进行自我比较。
通过将原始代码与LLM天生的替代代码进行比较,我们可以标记出显著的差异作为须要进一步检讨的部分,个中文本间隔和LLM的置信度也有助于这一分类。
与该领域的其他自动化方法不同,FLAG是措辞无关的,可以处理不完全(乃至无法编译的)代码,并且不须要创建安全属性、功能测试或规则定义。
在本文中,我们利用LLM进行此类分类,并评估了FLAG在已知毛病上的性能。
我们利用了121个跨C、Python和Verilog措辞的基准测试;每个基准测试都包含一个已知的安全或功能毛病。
我们利用OpenAI的两种最前辈的LLM,即code-davinci-002和gpt-3.5-turbo进行实验。
FLAG能够识别出101个毛病,并帮助源代码的搜索空间减少到12-17%。

1 弁言

当开拓者的意图与他们的代码实现之间存在差异时,代码中就会涌现毛病。
这些毛病可能引入安全漏洞或功能毛病。
创造它们是一项费力的事情,只管存在赞助工具,但它们常日只会在程序全部完成(或至少可以编译)时才有效,并且只关注一小部分毛病种别。
因此,在开拓过程中,开拓者及其团队应定期检讨他们的事情。
代码必须根据在源代码和注释中捕获的开拓者意图进行检讨,这种意图有时会显式地在诸如测试、断言或规则中捕获,有时会隐式地在实行代码进行模糊测试时捕获。

考虑到与精确代码比较,缺点代码相对较少,我们假设开拓者的意图紧张在源代码和注释中表示,且仅偶尔会有疏漏。
想象一位想要手动审查代码的开拓者;如果一个程序只包含几行代码和注释,这可能开始是可行的。
随着项目的增长,由于规模的缘故原由,会变得越来越具有寻衅性。
找到缩小代码潜在问题区域的方法可以帮助开拓者办理此问题。
源代码和注释的意图能否用来标记代码中的问题,从而缩小须要手动审查的范围?为了回答这个问题,我们研究了利用天生性AI,特殊是大型措辞模型(LLM),来标记代码中的非常。

像GPT-3和Codex这样的LLM在许多笔墨类任务,包括编写代码方面展示了强大的能力。
它们根据输入的持续产生输出。
这些输入可以是现有的代码和注释。
这供应了一个有趣的可能性:如果(a)代码中的缺点行较少,并且(b)大部分代码与作者的意图同等,是否可以利用LLM来衡量给定的代码行是不是非常的?如果其回答是,这可能表明该行代码有缺陷。

基于这种直觉,我们提出了一种新颖的方法,利用LLM根据现有的代码和注释天生替代的代码行;将这些替代代码行与开拓者的代码进行比较,以识别潜在的差异。
本文的事情供应了对LLM研究的补充,即关于LLM是否可以用来帮助识别安全漏洞和功能毛病形式的缺点。
我们的贡献如下:

我们提出了FLAG,一个利用LLM在通过比较原始代码与LLM天生的代码来检测缺点的新颖框架。
我们探索了源代码的特色及LLM的信息,以分辨代码是否存在缺点。
通过在多种措辞中对紧张LLM的不同模式进行实验,剖析了这些特色的有效性。
该工具和结果开源。

2 技能先容

FLAG同等性检讨器依赖于LLM编写代码的独特能力。
它的方法如图1所示。
对付源代码中的每一行,FLAG天生一个相应的提示,该提示包括该行之前的代码(前缀),以及该代码行之后的代码(后缀,可选)。
由代码和注释组成的提示输入到LLM,LLM输出单行代码或注释。
将天生的行与原始行进行比较,产生用于分类原始行是否有缺陷的特色。
这些特色供应两行之间差异的定量估计或LLM天生的行的置信度。
被分类为可能有缺陷的行会被标记。
对付给定的一行,FLAG流程有四个步骤:提示天生、行天生、特色提取和分类。

要对文件进行同等性检讨,我们必须给FLAG一个开始检讨的行。
这样做是为了给LLM足够的高下文开始天生干系的代码和注释。
常日,我们在代码头、初始注释以及模块和内部声明之后给出起始行号。
此外,文件会被预处理以跳过空行,并识别开始检讨的行之前是否存在注释。

2.1 提示设计

提示天生将源代码和待分类的行作为输入,并产生提示作为输出。
这个提示被发送到LLM进行后续行天生,如图2a所示。
这个过程对文件中的每一行重复进行。
随着FLAG检讨器前行,前缀增加而后缀减少。

我们将前缀和后缀的长度限定为最多50行,以保持在LLM的令牌限定之内。
提示被调度以从LLM得到更好的相应,例如,如果LLM在该当天生代码时天生了注释,我们会将原始代码行的前几个字符附加到前缀上。
这个过程可能会重复多次,直到得到有效的天生。
后缀仅在模型支持插入模式时利用。

2.2 行天生

行天生以提示作为输入,并输出LLM天生的一行代码或注释。
例如,在图2a中的提示天生的行显示在图2b中。
在这个例子中,LLM的相应与原始代码行不同。
它被标记为检讨工具,揭示原始代码行包含了安全毛病CWE-125,即越界读取。
本例中利用的LLM是gpt-3.5-turbo。

LLM须要被勾引产生合理的输出,由于有时LLM可能返回一个空行或返回一个注释而非代码。
这个过程在算法1中描述。
orig_lines是原始文件中行的列表。
假设列表的索引从1开始。
loc是我们希望LLM天生的行号。
num_lines是原始文件中的总行数(经由预处理后)。
我们将前缀和后缀初始化为空字符串,并利用max_pre_len和max_suf_len作为它们大小的限定。
这样做是为了保持提示的令牌大小合理。
在我们的实验中,我们将限定设置为50。
为了战胜偶尔的不可用输出,我们提示LLM再次产生相应,最多max_attempts次。
这在第15-19行的Try和Catch块中表示。
在第一次考试测验中,提示LLM用给定的前缀和后缀产生一个相应。
在第二和第三次考试测验中,我们将LLM考试测验天生的行的前5个字符附加到前缀来以引出非空相应。
如果try块中有任何缺点,缺点将在第14行记录,然后返回到第10行。
如果没有缺点且第16和18行的条件不成立,则实行第20行。
这表明一行已成功天生,并退出while循环。
我们利用温度为0,max_token限定为150,top_p值为1,并且结束行字符作为停滞令牌。

2.3 特色提取

特色提取接管原始和有缺陷的代码行,并输出特色。
它们用于将原始行分类为有缺陷与否的定量值。
它们代表两行之间的差异或天生代码的置信度。

特色

Levenshtein 间隔(ld)是两个字符串之间的编辑间隔。
它考虑三种操作:添加、删除和更换。
将一个字符串转换为另一个字符串所需的操作数量之和是莱文斯坦间隔。
完备匹配的结果是0。
我们利用ld来比较代码行。
由于目标是标记代码中的毛病,我们将ld作为识别毛病的紧张标准。

BLEU是用来评估候选句子与参考句子的一个指标。
完备匹配的结果是1,而完备不匹配的结果是0。
我们利用它来比较注释,由于它们类似于自然措辞。
我们网络了BLEU-1到BLEU-4得分,但创造只有BLEU-1产生了故意义的数字。
BLEU-2、BLEU-3和BLEU-4的质量微乎其微。
在本文的别的部分,BLEU指的是BLEU-1。

注释间隔(dfc)表示一行代码与其之前最近的注释的间隔。
如果该行也包含注释,则dfc的值为0。
如果该行之前没有注释,则dfc没有值。
我们只考虑代码之前的注释,由于这常日是代码的主流编写办法。

在LLM天生代码的高下文中,logprob是天生的令牌概率的对数。
如果一个令牌更有可能被天生,它将具有更高的logprob,其最大可能值为0。
如果一个令牌天生的可能性较小,它将具有更低的logprob,即负值且幅度更大。
LLM方向于“选择”有更高概率的令牌。
对付天生的一行,我们取天生的令牌的logprobs的均匀值,并称之为logprob。
logprob靠近0表示天生更有置信度,而更负的值表示置信度较低。

提取

为了得到这些特色值,原始行和天生的行被去除空缺。
如果行是代码和注释的组合,则代码和注释被分开进行比较。
原始行的代码与天生的行的代码进行比较以打算ld。
原始行的注释与天生的行的注释进行比较以天生BLEU指标。
此外,如果原始行是一个注释,则最近的前一个注释的位置更新为当前行,并打算dfc。
通过公共API无法得到gpt-3.5-turbo的logprob值, logprob值可用于code-davinci-002的实验。

2.4 分类

对付给定的文件,分类以特色作为输入并根据一些条件选择要标记的行。
这些条件被称为标准,被标记的行被称为reported_lines。
条件可以是包含性的或打消性的。
包含性条件旨在将行纳入reported_lines凑集。
打消性条件用于从reported_lines中打消行。
包含性条件在我们的FLAG框架中利用两个阈值,即Levenshtein间隔上限(ld_limit)和注释间隔上限(dfc_limit)。
选择这些阈值的缘故原由将不才文详细解释。
ld_limit和dfc_limit共同浸染以产生不同的标准。

利用ld_limit的缘故原由:ld表示两段代码之间的差异。
如果ld为0,那么两段代码是相同的。
这意味着LLM没有其他建议,因此没有情由标记此代码。
如果ld大于0,代码段是不同的,表明LLM指示与原始代码行的另一种可能性。
这可能值得标记。
但如果ld是一个非常高的数字,它可能表明LLM天生的是完备不同的东西。
基于缺点版本的代码常日与改动版本非常相似,我们利用ld的上限来针对那些不同但又不过于不同的天生代码。

利用dfc_limit的缘故原由:dfc表示一行代码与它之前最近的注释的间隔。
如果代码附近有注释,即dfc值低,我们假设LLM会天生干系信息的代码,从而置信度更高。
如果dfc大于dfc_limit,注释可能与代码行不干系,我们将其丢弃。
利用dfc放宽了我们选择要标记的行的标准,由于具有大于ld_limit的ld值的代码行,如果其dfc小于dfc_limit,仍旧可能被标记。

这种检测毛病的副浸染是reported_lines中有大量误报。
我们通过采纳一些方法来办理这个问题。
我们检讨reported_lines以移除可能是误报的被标记行。
第一个方法是在天生和原始行中移除空缺后重新打算ld。
这肃清了ld打算空缺的误报。
第二个是检讨原始行是否只包含一个关键字。
如果是这种情形,该行由于大略的关键字不能是缺点的而从reported_lines中移除。
第三个是利用logprob值作为打消的阈值。
如果LLM对天生的行有大的负logprob值,它将从reported_lines中移除。
我们在code-davinci-002的实验中利用阈值< -0.5来移除行。

3 实验和结果

为了评估FLAG同等性检讨方法,我们利用两个OpenAI LLM设计了实验。
第一个LLM是code-davinci-002,它针对代码完成任务进行了优化。
对付code-davinci-002,没有系统提示,但如果除了前缀外还给出后缀。
我们利用code-davinci-002进行了两个实验,分别是自动完成(无后缀)和插入(有后缀)模式。
第二个LLM是gpt-3.5-turbo。
它在GPT-3的根本上进行了改进,能够理解和天生自然措辞或代码。
在本文稿编写时,它是“最能力强大的GPT-3.5模型”。
它是一个辅导性模型,其角色是根据给定的系统提示来确定的。
给出系统提示后,模型然后吸收指界说务的。
我们对gpt-3.5-turbo进行了两次实验。
第一次实验没有利用任何系统提示,我们将这种模式称为自动完成。
第二次给出了如下系统提示:“你是一位闇练的AI编程助手。
完成下一行代码。
”这种模式称为辅导完成。

一个实验涉及选择一套特定的LLM和模式,例如,code-davinci-002在插入模式下,利用它对所有121个基准测试实行我们的同等性检讨方法。
我们总计为2个LLM及其2种完成模式的可能组合运行了4次实验。
为了评估一个实验,我们关注3个指标。
第一个是检测到的毛病数量(DD)。
对付给定的一组输入,DD是精确识别的毛病总和。
它实际上是真正的正例(TP),最大可能值为121,即所有来源的总毛病。
第二个是误报率(FPR)。
对付给定的一组输入,FPR是缺点高亮的行数与总行数的比例。
第三个是召回率,即真正例与总正例数的比率。

3.1 结果

结果在表4中展示。
我们根据来源和标准细分每个实验的结果。
对付C措辞,我们将C1细分为C1-CVEs和C1-CWEs。
通过展示不同标准的结果,我们解释了数据如何基于标准繁芜性而变革。
我们提出了标准2,由于它平衡了TPR和FPR,并利用了3.3.1节中的包含性和打消性特色。

gpt-3.5-turbo在检测毛病方面有更大的能力,但code-davinci-002的误报较少。
对付标准2,gpt-3.5-turbo在TPR方面比code-davinci-002表现得更好。
它能够在自动完成模式下检测到90个毛病,在辅导插入模式下检测到77个,而code-davinci-002在自动完成模式下检测到76个,在插入模式下检测到78个。
code-davinci-002在FPR方面表现得更好。
与gpt-3.5-turbo在辅导完成和自动完成模式下的0.172和0.181比较,它在自动完成模式下的FPR为0.121,在插入模式下为0.141。

3.2 对安全漏洞的深入探究

由于我们对FLAG的安全运用感兴趣,我们在图3中对安全漏洞的检测进行了详细剖析。
35个安全漏洞中有16个被两个LLM在两种模式下检测到,29个被至少一个LLM在一种模式下检测到。
在自动完成模式下,gpt-3.5-turbo表现最好,检测到了26个,而在辅导完成模式下为22个,在自动完成模式下的code-davinci-002为19个,在插入模式下的code-davinci-002为19个。
Python安全漏洞是LLM检测得最好的。
所有Python的毛病都被创造了。
其次是Verilog和C。
进一步的检讨让我们对这种情形产生了一些想法。
首先,与其他来源比较,P1源文件的大小较小。
它的均匀文件大小为17行,而C1和V1分别为1426和249行。
此外,较小的文件大小许可LLM将在漏洞之前的全体源代码作为提示的一部分,为其供应了文件预期功能的完全高下文。
其次,P1中的基准门设计用于安全评估,而V1和C1中的基准是专门为安全设计的基准和真实代码的结合。
V1中的6个基准来自SoC实现的真实代码,而C1中的8个基准来自真实代码。
这也阐明了为什么FLAG在C1-CVEs上的表现不佳。

我们比拟了FLAG在功能和安全漏洞之间的性能。
根据图4中的数据,可以推断FLAG检测功能性漏洞的效果比安全漏洞更好。
功能性漏洞的均匀TPR为0.689,而安全漏洞为0.676。
它们的均匀FPR较安全漏洞为低,分别为0.176和0.271。
进一步不雅观察创造,Python是一个例外,P1的TPR比P2高。
对付gpt-3.5-turbo,P1的TPR是P2的两倍,表明gpt-3.5-turbo在检测Python中的安全漏洞方面表现良好。
我们将C1-CVEs的数据打消在剖析之外,由于这些基准的行数是其他基准的许多倍,会使均匀值倾向C1-CVEs。

3.3 详细剖析

从C0到C2标准如何影响DD和AFPR?从C0到C1,DD和FPR都增加了。
这是由于ld_limit从10放宽到20,且利用dfc_limit包含可能超过ld_limit的行。
均匀而言,DD从51增加到84,而FPR从0.106增加到0.18。
从C1到C2,DD和FPR都减少了。
这是由于从reported_lines中移除了被高亮的行,一些误报被移除,但一些真正的正例也被移除了。
均匀而言,DD从85减少到80,而FPR从0.18减少到0.154。
虽然DD减少了4.76%,但FPR减少了14.6%。
这表明此操作在提高同等性检讨性能中的主要性。

不同完成模式如何影响DD和AFPR?我们本能地会认为插入模式该当比自动完成模式做得更好,由于LLM除了前缀外还可以访问更多形式的后缀信息。
此外,gpt-3.5-turbo在辅导完成模式下产生“下一行代码”该当比自动完成模式表现得更好,由于天生代码而不是代码阐明的可能性该当更高。
图6比较了在C2(20,10)下不同完成模式的表现。
对付code-davinci,插入模式确实让我们检测到了2个额外的毛病,但代价是FPR增加了16.5%。
这可能不是一个有代价的权衡。
对付gpt-3.5-turbo,辅导完成模式比它的自动完成模式做得明显更差。
它检测到的毛病少了13个,而FPR只减少了4.97%。

ld_limit和dfc_limit如何影响DD和AFPR?在我们的检讨方法中突出显示行的两个紧张决定成分是ld_limit和dfc_limit。
由于它们可以取任何连续值,除了考虑C0到C2标准中谈论的特定值外,还须要进行更深入的剖析。
我们对code-davinci-002模型在自动完成模式下对这两个限定进行扫描,以理解它们对DD和FPR的影响。
图7a和图7b分别展示了在ld_limit从0到30和dfc_limit从0到50的范围内,不同ld_limit和dfc_limit组合下DD和FPR的变革。
我们不雅观察到,ld_upper_limit在影响DD和FPR方面更为显著。
ld_upper_limit的变革比dfc_limit的等量变革引起的DD和FPR的变革更大。
在增加ld_limit时,DD和FPR也更早地饱和。
当ld_limit为30时,DD大致饱和,由于在该值下改变dfc_limit的影响有限。
类似地,当ld_limit为20时,FPR大致饱和。
将它们扩展到ld_limit=50,DD和FPR的最大可能值分别为90和0.14。
我们还不雅观察到,在较小的ld_limit和dfc_limit值时,DD和FPR更为敏感。
在ld_limit为15和dfc_limit为10时,我们可以达到DD最大值的80%,即72。
同样,在ld_limit为15和dfc_limit为25时,我们可以达到FPR最大值的80%,即0.11。
这种剖析有助于估计在设计类似工具时该当考虑的范围。

通过放大dfc_limit的较小值,并将ld_limit的值扩展到100,如图8所示,可以进一步探索此问题。
对付特定的dfc_limit,随着ld_limit的增加,DD和FPR都会增加。
虽然两者增加的速率都在减小,但有一个点,超过这个点,DD的增加率显著低于FPR的增加率。
这是由于超过该点后,获取更多毛病的好处会被增加误报的本钱所压倒。
相反,过小的ld_limit值找不到足够的毛病。
图8中的高亮区域表明同等性检讨方法供应“还可以”的结果的值范围。
类似地,保持ld_limit不变并扫描dfc_limit也供应了相似的结果。
然而,对付不同的ld_limit值,DD和FPR的值没有足够的区分度。

自然地,人们可能会问什么是ld_limit和dfc_limit的最佳组合?开拓者乐意接管多少误报以换取检测到更多毛病的好处是主不雅观的问题。
我们在热图中展示了TPR和FPR之间的权衡。
决定利用哪些限定的一种方法可能是首先取决于能够承受的误报数量。
例如,如果乐意在检测到每个毛病的情形下查看100行中的7行,即FPR为0.07,可能会选择(ld_limit,dfc_limit)为(5,10),如图7b所示。
在这些限定下,将能够定位到我们基准中覆盖的121个毛病中的58个。

LLM相对付随机预测表现得怎么样?我们以不同的ld_limit阈值形式,在吸收者操作特色(ROC)曲线上表示真正率(TPR)和误报率(FPR)。
我们用code-davinci-002的自动完成模式来实现这个目的。
在我们的实验中,我们为ld设置了0的硬性下限。
这意味着,一些ld为零的行永久不会被计为真正的正例或误报,导致ROC曲线不完全。
这种情形在图9a中显示。
然而,ROC曲线位于TPR==FPR线之上,显示我们的分类方法优于预测。
为了完全性,如果我们利用-1的下限和1000的上限,我们可以得到TPR和FPR为1,由于所有行都会被高亮显示为包含毛病。
这在图9b中表示。

某种措辞的毛病是否比其他措辞更随意马虎检测?根据图10中的数据,我们可以得出结论,FLAG同等性检讨器在C措辞上表现最好,在Python上表现最差。
只管C的TPR略低于Verilog的两个LLM,但它的FPR显著较低。
虽然它的TPR低10.5%,但它的FPR比Verilog低48.4%。
Python的TPR最低,仅次于FPR为第二低的两个LLM。
这是令人惊异的,由于我们预期演习数据中大量的开源Python代码会转化为比在Verilog上的更好表现。
这可能是由于Python的22个基准是真实代码,而Verilog只有6个。

注释有多大帮助?我们通过剖析真正例和误报的dfc和BLEU分数来表明注释的浸染。
dfc表示一行代码与注释的靠近程度,而BLEU分数表示LLM产生的注释的质量。
图11显示了真正例和误报之间dfc的不同。
这里的数据不包括dfc不可用的例子,即在有关行之前没有注释的情形。
在两种情形下,大部分数据都位于dfc的最小值范围内。
这是由于我们研究的数据集中常常编写注释。
真正例的dfc均匀值为18.4,比较之下,误报的均匀值为327.9。
当有关行更靠近注释时,LLM在分类上做得更好。
真正例的dfc标准偏差要小得多,为47.7,而误报的为632.1。
因此,误报覆盖了更广泛的范围。

均匀dfc或均匀bleu分数是否与检讨器的成功干系?图12展示了上一个注释的BLEU分数在真正例和误报之间的差异。
数据不包括在行之前没有注释或LLM未能产生注释的行。
对付正在剖析的代码行,FLAG跟踪它之前的最近一条注释。
FLAG将LLM在这一行产生的注释与原始注释进行比较,以打算上一个注释的BLEU分数。
高值表明LLM正按照编码者的意图精确进行。
真正例的上一个注释的BLEU分数均匀值为0.407,与误报的0.473比较较。
这表明上一个注释的BLEU分数在分类中并不起决定性浸染。
真正例的上一个注释的BLEU分数的标准偏差为0.193,与误报的0.236相似。

转述:李昕