Towards Reliable AI for Source Code Understanding
一.introduction
云平台在应用部署上的成熟和广泛程度已经根本改变了代码的开发,管理和分配方式。开源软件(OSS)蓬勃发展,随着微服务范式、云本地CI/CD管道、无服务器平台等技术的兴起,云平台对OSS的依赖稳步增长。OSS在云平台生态也扮演了一个意义显著的角色。
但是,开源软件组件(OSS components)同时可能隐藏安全缺陷和漏洞。根据最新的报告(State of the Software Supply Chain Report),开发者build进自己应用中的开源软件组件中11%的是已知有漏洞的,平均38个漏洞。针对OSS的网络攻击激增430%,新的0天漏洞在公开披露后3天内被利用。因此,确保代码质量对于每个行业的OSS组件上的基础设施和应用程序来说都是至关重要的。
静态扫描源代码依旧是学术界和工业界的研究热门,传统的规则匹配方式假阳性率高。而AI在源代码领域的应用推动了静态扫描的发展,但是,作者观察到了AI在代码理解生态链方面的一些特性,激发对其可靠性进行验证的需求。例如,帮助确定模型训练日益消耗的大量资源是否能更好地用于其他类型的代码分析或测试,比如更长时间地模糊应用程序以捕获更多错误。
在这篇paper中,作者使用漏洞分析用例,将影响AI-for-code的可靠性问题突出并分类为AI-pipeline的三个不同阶段:
- data collection
- model training
- prediction analysis
作者希望学术界在每个阶段做出一些努力,确保:
- 数据收集正确性的可信度(Credibility of data collection correctness)
- 确保模型学习到了任务相关的信号(Accountability in ensuring task-relevant signal learning by the model,这点说的有点抽象)
- 在分析影响模型性能的数据的行为趋势方面的可追溯性(Traceability in terms of analyzing behavioral trends across data affecting model performance, 这写的也有点抽象)
对于每个阶段,作者都会提出潜在的解决方案,并结合一个共同的主题,即利用成熟的软件工程(SE)和数据驱动技术来帮助提高AI可靠性。
二.The Reliability Problem
2.1.Data Collection
高质量的数据是AI的支柱,因为基于学习的模型的性能与它所用的数据是强相关的。标注良好且低噪声的数据集能显著地提升模型性能。数据收集的方法同时决定了模型在真实场景下的性能。
可靠性的问题可能会通过多种途径引入到数据收集阶段,包括:
- Cross-split Leaks:当测试分割包含训练集代码样本的重复项或近似重复项时,它可以显著增加模型报告的性能指标,有时高达100%。
- Correlation Capture:数据集的管理方式可能导致某些code artifacts出现在属于一个类的样本中,但在另一个类中缺失。(可能,比如:if循环出现在正样本,但是并没有出现在负样本中,导致出现if循环就可能被分类为正)
- Erroneous Labeling:
- 1.合成数据集的标记相对容易且无错误,因为它们是从预定义的模式生成的,但它们无法捕获真实世界代码的复杂性和多样性。
- 2.许多真实世界的数据集都基于相对简单的
GitHub commit messages
分析,这会在标记中引入错误。比如Devign数据集中,当一个commit被认为是bug fix commit
时,由这个commit patch的所有function都会被标注为buggy
。还有的方法(Automated Vulnerability Detection in Source Code Using Deep Representation Learning.)通过静态分析工具来标注样本,这样会产生许多假阳性,所以需要随后进行手动验证,但这只能应用到小规模的数据集。
- Scope Limitation:许多的源代码数据集仅限于function-level,并没有捕获一些对完整地分析代码比较关键地过程间(inter-procedural)信息。在这样地数据集上训练出来的模型,并没有真正捕获inter-procedural flows。这在On the performance of method-level bug prediction: A negative result.和Flow2Vec: value-flow-based precise code embedding 2篇文章中得到讨论。
当然,作者提到了D2A数据集并没有受限于上述limitations。
summary:虽然一些数据集偏差问题可以通过模型消化之前的智能预处理来解决,但最关键的元素需要严格的代码管理策略,以最大限度地提高后续可靠建模的机会。
2.2.Model Training
用于代码的AI模型似乎也受到AI通常的低通用性和健壮性弱点的影响。但这其中只有一部分可以归因于上述数据收集阶段的缺陷。作者这里展示了一些observation来为模型真正学到了什么提供insight。
- Low Robustness:一个好的模型,通常学到的都是跟任务最相关的一个片段,比如漏洞检测中。一定是某些代码模式的出现让该代码片段被分类为有漏洞。但是作者观察到一个99% F1的模型仅仅因为出现了细微的语法上的结构变体(while变for)就翻转了它的预测值。
- Weak Generalization:在一个数据集上训练表现良好,但是换一个数据集却拉跨了。这是令人担忧的,因为用于此实验的数据集都针对几乎相同的有限组常见漏洞类型,包括空指针、解除引用、缓冲区溢出、释放后使用等。这就提出了一个问题,即模型实际上在学习什么。
- Exponential growth for incremental benefits:为了增加精度不断有更复杂的模型被提出,但是花费的成本却非常高,把这些物力成本花在fuzzing上是否会取得更好的结果?
Summary:对于代码,AI中明显的弱点带来了关于模型可靠性的问题。具体来说,无论模型是真正学习相关的代码结构,还是仅仅是任务无关的数据集细微差别。以及模型质量的这一方面是否被模型性能的常用统计度量所捕获。
2.3.Prediction Analysis
AI的黑盒特性使得人们无法轻松理解它分类的逻辑,这与静态path/flow分析以及动态分析不同。针对黑盒AI的解释方法也有局限性,比如:
- 对于attention-based模型,attention score可以解释为不同特征的权重系数。
- 对于gradient-based方法,输入token的heatmaps可以用来解释源代码中的重要特征。但是,这些saliency maps并不是完全精确的通常也会有误导性。
除了白盒解释,当前研究中缺少的一个重要方面是从数据集的角度分析模型的预测。这就包括了分析样本中模型正确预测的一个特征和模型预测错误的特征。这种事后预测分析有助于揭示模型逻辑,方法是在正确(或错误)预测的样本中使用共同的代码特征,以得出模型可能从代码中提取的信号,以及这些信号是否与手头的任务相关。
如果学习行为符合预期,这有助于增强对模型可靠性的可信度。
三.Data-Driven And SE-Assisted Solutions
作者在这里提出一些提高AI-for-code的可靠性的解决方案。分别要解决上一章节提出了data collection, model training, prediction analysis 3方面的问题。在每一个阶段,作者介绍如何把AI社区(CV, NLP)的一些idea引入到AI-for-code领域。作者通过借鉴SE(软件工程)领域成熟的技术,提出潜在的数据驱动解决方案,但这里使用的例子均来自漏洞检测。 然而,作者的数据驱动前景并不只适用于源代码的理解任务,通常适用于其他任务比如code summarization, method naming以及variable misuse detection。
3.1.Data Credibility
为了提高数据的可信度以及大规模数据集需求,像crowd-sourcing这种来自NLP的方法可以借鉴,AskIt!系统可以把一个样例数据映射到一个开发者上,这个开发者最适合对这个样例进行标注。(开源的寻找专家标注)。CrowdFill则是另一个由专家投票进行二次验证的系统,这里面来自AI-for-code领域的专家都会参与标注。
但是人力资源是有限的,所以自动标注总会是优先选项。所以作者创建了D2A数据集,这个数据集通过Infer工具和差异分析进行标注。
因为静态检测工具(Infer,FlawFinder)等等可能会产生相当多的误报,所以直接使用它们来标注数据并不可靠。所以,作者这里结合commit message来进行标注,首先筛选一些bug fix commits。然后对相应的代码用Infer进行检测,如果在commit(fix)之前的版本检测出了bug,在commit之后的版本没有检测出bug,那么就很有可能是真阳性。如果在commit之前和之后的样本均检测出了bug,那很有可能是假阳性。上图展现的是一个假阳性的。
Auto-Labeler的输入是一个git repository。作者首先用一个在NVD commit上训练的NLP模型来识别该repo中很有可能是bug fix commit的commits。对识别出的所有commit,作者都用Infer对before-fix和after-fix版本进行扫描,最后进行差异分析。作者手动检查了一些样例,这么做的确可以提升数据标注的质量。
但是,D2A依旧有需要提升的地方,下面展现了D2A Auto-Labeler错误标注buggy代码为non-buggy的一个样例:
这种特殊情况是由于通过D2A内部使用的NLP模型造成的错误的bug-fixing commit message与相应的commit-message-profile不匹配。也就是NLP模型没有正确预测commit message类型。这也阐述了commit-message-analysis NLP model可靠性在数据标注中的重要性。
下图展现了错误将non-buggy代码标注为buggy的示例:
这就强调了一个更精确的差异分析方法的重要性以及commit message analysis模型的重要性。
扩充数据集是在数据不足的情况下的廉价方案,包括解决类别不平衡和数据不足问题。像SMOTE这样的方法能够从模型学习到的类别分布中构造合成样本,这种方法也可以应用到文本领域。但是这是在向量空间进行操作的,在源代码领域中,合成数据在语法和语义方面通常无效。而软工领域已有方法可以在保留代码有效性的情况下进行数据增强。Delta Debugging结合编译器验证来提供一个生成新样本的方法,同时有利于减少噪声。
3.2.Model Accountability(这段其实没看懂)
前面提到了,AI模型在进行漏洞检测的时候不一定真正学到了漏洞语义信息,作者将这个问题称为model’s quality signal awareness。模型在训练的过程中会自动学习到黑白样本之间的差异,但是这个差异并不一定跟任务相关(比如不一定是漏洞)。这个signal awareness并不是用常用指标(ACC, F1等)能衡量的。所以这里第一步需要能够衡量模型的signal awareness,作者这里提出了一个data-driven方法来揭开模型捕获task-relevant signals的能力。作者这里引用Delta Debugging来从源代码输入中提取最简单的摘录,模型需要得到这些摘录并坚持其原始预测。然后验证minimal snippet是否与original code具有相同的任务配置文件。
对于漏洞检测设置,当original code中的漏洞在minimal snippet中丢失时,可以发现模型对错误信号的依赖,但是模型预测这两个代码段都有漏洞。通过使用这种方法,作者发现很多AI模型在源代码领域都会有性能上的下降(recall从90%降到60%)。
现在,这种测量模型signal awareness的能力能够系统地探索如何在确保模型学习相关代码构造方面改进模型性能。
3.3.Prediction Traceability
这里主要是AI模型的可解释性问题,分析预测失误是改进模型的有效手段,像朴素贝叶斯,逻辑回归这种统计模型有助于确定输入特征中对误分类贡献较大的一些特征(简单模型可解释性较强),但深度模型的可解释性就比较差了。
为了让深度黑盒模型透明化,一些解释方法被提了出来:
- gradient-based方法会计算inputs相关的activation maps来给输入特征分配权重。
- approximation-based使用简单的可解释的代理模型来近似深度模型的行为,然后为输入的特征分配权重并排序。
除了这些模型相关的白盒方法之外,AI for code领域还有将SE技术用于纯data-driven的分析(纯粹从代码的角度进行分析,不分析向量)。其中1个方法就是分析模型正确预测的样本的代码特征与模型错误预测的样本的代码特征。通过使用与源代码相关的度量,例如代码行数、循环计数、分贝数,通过预测精度对样本进行分组,它可以帮助发现模型能够理解代码的哪些方面,以及它可能面临的困难(也就是什么样的代码模型容易分类,什么样的不容易)。这样就可以深入了解如何改进模型的学习,以针对预测失误的代码常见的代码特征。
四.conclusion
作者总结了AI-for-code领域可能面临的问题的3个阶段:
- data collection
- model training
- prediction analysis
对每个阶段,作者都提出了相应的解决方案。