谢春丽,梁 瑶,王 霞
江苏师范大学 计算机科学与技术学院,江苏 徐州 221116
GitHub、Stack Overflow等平台存在着大量的开源项目和源代码片段,开发人员如果能有效地将功能相似的代码引入到自己的项目中,可以大幅度提高软件开发效率。那么,如何在海量源码中快速而准确地找到满足功能和性能需求的源码成为软件工程领域关注的主要任务。除此以外,代码克隆检测、故障定位、代码注释、代码摘要生成、命名推荐等问题也是该领域的研究热点。这几个任务都有一个相同的关键问题,即需要对源代码进行一定的预处理,从中抽取特征进行源代码表征。如图1所示,源代码表征是解决各类问题的第一步,在此基础上设计各类算法,从而完成代码克隆检测、故障定位、摘要生成或其他任务。源代码的表征方式决定了对源代码特征抽取的程度,进而影响后续任务所能检测的精度。
图1 基于深度学习的代码表征框架Fig.1 Code representation framework based on deep learning
根据对源代码信息的利用程度,代码表征方法可以分为基于文本、词汇、语法、语义四个层面:基于文本的表征方式不经任何修饰直接把源代码看作文本/字符;基于词汇的表征方式首先对源代码词法分析,然后把源代码符号序列化;基于语法的表征方式往往从源代码的语法树或者抽象语法树抽取代码语法信息;基于语义的表征方式则进一步从源代码的控制流和数据流等信息中提取隐含的语义信息。这几种不同层次的表征方式各有自己的特征与优缺点,基于文本的表征最直接、最简单,基于词汇的表征相比基于文本的方式具有一定的抽象能力,基于语法的以及基于语义的表征抽象程度更高,但需要把源代码转换为树型或者图型结构等额外的预处理技术。根据源代码表征的技术,可以分为三大类:传统方法、基于机器学习的方法和基于深度学习的方法。传统方法手工提取源码中操作符、操作数、代码行数、函数、数据类型、常量等特征[1],利用统计学原理表征源码。由于,源码和自然语言有诸多相似之处,随后,Hindle等人提出了代码的自然性假设,为源代码表征提供了新的思路,大量的机器学习算法,例如N-Gram,被运用到代码表征中,但此时机器学习方法仍然需要从源代码中手工提取代码特征[2]。近年来,由于深度学习在自然语言中的出色表现,逐渐有学者开始把循环神经网络(Recurrent Neural Network,RNN)、卷积神经网络(Convolutional Neural Networks,CNN)、图神经网络(Graph Neural Networks,GNN)等模型引入到源代码表征中,试图挖掘隐藏在源代码中的深层次复杂特征,进一步提高代码表征的能力[3]。
目前尚无工作单纯对源代码表征方式的研究进展进行梳理和归纳的研究工作。鉴于此,本文拟针对当前源代码的表征方式的研究进展,进行归纳和总结,讨论当前该领域存在的关键问题、解决思路与研究发展趋势。
为分析源代码表征技术的研究现状,本文选用ACM、IEEEXplore、SpingerLink电子文献数据库、中国知网数据库以及百度学术搜索对公开发布的期刊论文、会议论文进行搜索,检索的关键字包括code representations、code similarity、code clone等,检索范围包括主题、关键词、篇名,从中检索在代码表征方法中提出新模型、新方法的文献,检索时间从2019年开始。同时对中国计算机学会推荐的软件工程/系统软件/程序语言领域中的TSE、ASE、TOSEM等国际学术期刊和FSE/ESEC、ICSE、ASE、MSR、SANER等国际学术会议进行检索,另外,有部分相关文献可能出现在人工智能领域,所以对人工智能领域的顶级会议AAAI、NIPS、ICLR等也进行了相关检索。并在阅读文献的过程中,把文献中提供的经典算法的参考文献也作为研究对象。经过检索和筛选,去除一些综述文献,本文共对54篇文献进行了研究,图2显示了这54篇文献的期刊或会议出版情况。
图2 相关论文发表情况Fig.2 Statistics of papers published
如图3所示,对源代码特征的抽取,根据粒度大小,可分为词汇级、语句级、函数级。词汇是表达源码的最小粒度,但是很难表达词和词之间的关系,语句级介于词汇级和函数级之间,能结合上下文表达源码,函数级的抽象层次比前两个要高,相对而言能更好地表达语义信息,但是复杂度提高,除此之外,还有文件级、项目级的表达,在此不再描述。
图3 代码表征分类Fig.3 Code representation classification
(1)词汇级:源代码本身就是词汇序列,或者经过词法分析,转换为标识符、关键字、数值、字符串、注释、特殊符号和运算符等各类标识符(Token)组成的序列。采用Token表达源代码可以去除空白、制表符、注释等无关信息,同时还可以消除由变量命名不同所导致的不同语义[4]。著名的CCFinder、CP-Miner等克隆检测工具都是基于Token级的,可以很好地检测完全相同的代码对以及参数化后的代码对克隆问题[5-6]。Nguyen等人在代码的Token序列上利用概率统计模型,预测下一个可能的Token,完成代码词汇级的补全[7]。Dam等人对代码的Token构建了端对端的长短期记忆神经网络(Long-Short Term Memory,LSTM)模型,用于建模软件及其开发过程,解决代码推荐和预测等任务[8]。
(2)语句级:代码是由各种不同类型的语句构成的,语句是代码片段的基本构成,代码的执行是按照语句逐条进行的。但是不同的程序语言一行代码能够表达的内容是不一致,有可能是一个简单的运算,也可能是一个算法。因此语句级的划分上,主要采用抽象语法树(Abstract Syntax Tree,AST)、数据流图(Data Flow Graph,DFG)等不依赖具体语言的表达方式。Ben-Nun等人提出了Inst2Vec模型,首先把源代码片段转成一种中间语言,并进行预处理,去除了注释等无关信息,将变量和常量用统一的符号替换,同时提出了一套生成语句上下文的方法,结合上下文关系将代码语句映射到向量空间,使得具有相同上下文的代码语句具有相似的含义[9],该方法充分考虑了语句的数据依赖关系和执行特性。由于代码的AST一般规模都比较大,代码表征过程中很容易产生梯度消失问题,Zhang等人提出了一种针对语句级的方法,将一个代码片段的AST分割成多个语句级的子树,产生语句级向量,和完整的AST相比,语句级嵌入在树的大小和语法特征抽取有一个较好的折中,有效缓解了梯度消失问题[10]。
(3)函数级:直接对函数进行表征的研究很少,大部分方法都是把函数分解为Token序列,对Token向量进行一定的加权组合来表达函数。Mou等人转换成函数的AST,对AST中结点,采用自下向上的方法,通过孩子结点的加权组合学习父结点的表达,最顶层根结点的表征则是该函数的表征向量[11]。DeFreez等人提出了Fun2Vec方法,解决代码的路径爆炸问题,该方法采用随机游走算法,随机选择部分执行路径,捕获程序的层级结构,每条执行路径转换为一个标签序列,借助Word2Vec方法,把标签映射为连续实值向量,并通过神经网络训练函数的嵌入向量[12]。
如图2所示,根据源代码的抽象层次不同,代码表征有文本级、结构级、语义级、功能级四个层次。层次越高,抽象程度越高,能够提取的信息就越多。
(1)文本级:自从自然语言模型出现之后,任意文本都可以转为词序列,并映射为词向量,源代码的词汇级表征分为两种,一种是文本形式的词汇,即源代码不经过词法解析直接分解为词序列,利用Word2Vec把各个词转换成向量,向量的加权平均用来表达文本[13]。另一种常见形式是先对源码词法解析,得到一个词汇序列,也叫Token序列,并过滤掉无用的字符、空格、注释等。Token序列提高了代码的抽象表示,该方法通常对具有较小更改的代码段,例如代码间格式、间距变化或重命名等的检测能力更强,更具有鲁棒性,广泛用于源代码相似性测量,并用于CCFinder、CP-Miner、SourceCC等克隆检测工具中[5-6,14],这种方法的缺点在于没有考虑代码行的顺序,忽略了代码中的结构信息。
(2)语法级:也称为结构级,如果对代码只修改了变量名称、数据类型和注释信息,改变书写代码风格、增加或删除一些无关紧要的语句,而不改变程序执行结构,那么这两段代码从结构上是一样的,则表征的结果也是一样的。基于语法树的代码表征是典型的结构级表征,通过语法解析将源程序转换为解析树或AST,AST可以避免格式化和词汇差异问题,而关注两个程序之间的结构信息。然而,基于树的表征具有较高的计算复杂度,两个具有n个结点的AST相似性比较的时间复杂度为O(n3)[15]。Baxter等人把基于树的代码表征应用到克隆检测任务,把源代码转成带注释的解析树,然后将子树分组到桶中,仅在同一个桶中的子树通过树匹配算法相互比较[16]。Jiang等人把AST子树中相关结点出现的次数作为特征值,抓取AST的结构信息,映射为特征向量,采用局部敏感散列(Locality-Sensitive Hashing,LSH)来聚类相似的向量,增加了优化机制降低时间复杂度[1]。为了更准确地抽取AST的结构信息,Mou等人构建基于树型结构的卷积神经网络模型,用孩子结点的加权组合学习父结点的表达,提高了语法信息的抽取能力[11]。
(3)语义级:代码不仅具有静态结构信息,同时还是可执行的,结构相同的代码,执行路径可能不同,代码语义也不同。基于图的方法是主要的语义级表征,通过程序的数据流图、控制流图(Control Flow Graph,CFG)、程序依赖图(Program Dependence Graph,PDG)等方式来提供比简单语法相似性更精确的信息。例如,程序依赖图中结点代表表达式和语句,边表示控制依赖关系和数据依赖关系,代码相似问题就变成了同构子图的问题。Komondoor和Horwitz提出了一种基于程序依赖图的方法,该方法能计算程序两个版本之间的语法和语义相似,并使用程序切片技术找到同构子图[17]。其后也有研究者陆续提出基于图的检测方法[18],但子图同构问题的计算开销非常大,可扩展性差,因此国内外相关研究较少。随着图嵌入技术的出现,语义级表征的问题逐渐成为新的研究热点。Alon等人提出了Code2Vec模型,把AST中的路径上下文作为神经网络模型的嵌入层,源代码表征为路径上下文向量表征的加权组合,该模型可以提高函数名推荐的精度[19]。
(4)功能级:有些代码片段即使语法和语义不同,但是功能是相同的,DeFreez等人提出Func2Vec方法,其基本思想是首先抽取源码的控制流,把控制流图中的路径上的结点加上标签,这些标签包含指令类标签、结构体类标签、函数标签、错误类标签,然后采用随机游走算法从众多执行路径中选择部分路径,即得到一系列标签序列,这些标签序列作为训练的语料库,用Word2Vec训练每个标签的向量表征。采用随机游走算法解决了路径爆炸问题,并在路径生成过程中捕获了程序的层级结构特征[20]。
通过对选定文献的表征粒度、表征层次和应用场景进行分析。其中不同表征粒度的分布如图4(a)所示,从图中可以看出目前已有文献主要集中在词汇粒度的表征,不管是传统的方法,还是基于统计学原理的语言模型或是深度学习,词汇是基本的研究对象。如图4(b)所示,由于选择的文献绝大多数是近几年的方法,所以多数以研究代码语义为主,这也体现了新的研究趋势。受自然语言、图神经网络等技术的快速发展的影响,各种深度学习代码表征模型纷纷出现,这也引起了代码表征的研究热潮。图4(c)表明了代码表征通用技术已经成为新的研究热点,目前,普遍的方法是在大规模无监督数据集上进行预训练,当应用到具体下游任务时,只需在预训练好的模型上进行微调,即可获得较好的性能。
图4 文献分布图Fig.4 Distribution of papers
2016年,Hindle在论文中假设代码和自然语言一样具有一定的统计规律[21],紧随其后,2017年,Vincent等人从词汇方面分析了源代码和自然语言的不同特征,指出自然语言的词汇表非常有限且更新速度较慢,而代码的词汇表更新速度太快,新词汇层出不穷,且是动态增长的,给数据训练带来很大的困难。在实验中,通过调整传统N-Gram模型,产生的结果比RNN和LSTM的效果还要好[22]。2018年,Allamanis等人再次提出,程序应该具有自然属性,软件语料和自然语言语料具有相似的统计特性,语料越大包含的模式更丰富,并将机器学习中的概率模型引入到代码表征领域,以学习和研究代码的特性,此后各种不同的编程语言模型纷纷被提出,并迅速应用到Bug修复、代码推荐、克隆检测、注释自动生成等不同任务上[23-24]。为解决代码的那些不在词表中词的问题(Out of Vocabulary,OOV),2019年,Karampatsis等人提出了一个基于开放词汇的神经语言模型,该模型不限于固定的标识符词汇表,采用Subword技术处理未知或罕见词汇问题,取得了较好的效果[25]。
虽然源代码具有自然性,但由于源代码的词汇可以任意创建,使得代码语料库非常复杂,语料库的构建直接影响神经网络模型的性能。2019年,Babii等人对如何更好地构建源代码语料库进行了探讨,从代码的非英文单词的处理、字面常量的处理、注释的处理、空白符/空白行的处理、分词和大小写问题的处理以及SubWord分词的处理六个决策方面全面探讨了各种选择对语料库和模型性能的影响,列出了源代码语料库的重要特征,并在含有10 106个项目的大型语料库中做了验证,证明了决策选择对快速训练精确的神经语言模型具有决定性的作用[26]。标识符是源代码最基础的组成元素,据调查,标识符约占据源代码总量的70%,标识符包括变量名、函数名、类名、域等,其语义对源码具有重要的作用。Wainakh等人通过500名开发者对标识符相关性、相似性和上下文相关性的调查问卷,构建了标识符嵌入的基准,提供了一个标识符的IdBench[27]。随后,Wang等人构建了COSET作为程序语义嵌入的基准框架,COSET有多种不同类型的源代码的数据集组成,由专家对程序属性进行手工标注,并对TreeLSTM,门控图神经网络(Gated Graph Neural Network,GGNN),ASTPath神经网络等模型进行了初步研究,该框架对发现模型的优势、局限性很有作用[28]。
因此,随着机器学习研究的最新进展,软件维护从符号形式方法转向数据驱动方法。在这种情况下,隐藏在源代码标识符中的丰富语义为构建代码的语义表示提供了机会,Efstathiou等人为六种流行的编程语言预训练了向量空间模型,提供了分布式代码表示并指出自然语言和源代码之间的差异[29]。
N-Gram模型是代码表征最经典的机器学习方法,它是一种基于概率的语言模型(Language Model,LM),根据单词的顺序序列,预测下一个输出序列的概率,即这些单词的联合概率。N-Gram引入到程序分析领域后,被应用到代码推荐、代码维护、代码迁移、错位定位以及函数名/类名推荐领域,获得了很好的效果。Hindle等人构建了N-Gram模型用来预测下一个Token,该模型包含前N-1个词所能提供的全部信息,必须要相当规模的训练文本来确定模型的参数,当N很大时,模型的参数空间过大,容易发生数据稀疏问题,进而导致数据平滑问题[21]。
Nguyen等人为了提高N-Gram预测的性能,把数据类型、主题等信息加入到N-Gram模型中[30-33],Tu等人增加了缓存机制获取本地文件中的Tokens[34-35]。Allamanis等人利用N-Gram预测下一个API调用[36-38]。N-Gram模型只适合处理较短的文本,Yang等人把Tokens根据词汇和变量相关性分组,利用PCC优化Token级别的N-Gram模型,使之可以处理语句级的长文本推荐[39]。Nguyen等人提出AUTOSC模型框架结合程序分析和自然语言的概率统计方法进行代码补全,采用N-Gram模型在大型代码语料库上对源代码的词法进行训练,训练出能产生候选语句的语言模型。然后,使用程序分析从候选集中筛选合乎语法的抽象语句,并将其具体化为有效的候选语句并进行排序。实验验证AUTOSC的性能超过现存最好的方法[7]。Karnalim等人针对树型结构相似性检测的计算量大的问题,提出一个启发式规则把树型结构线性化为结点序列,采用N-Gram模型计算代码相似度值[40]。如表1中所示,N-Gram技术主要应用于代码补全等生成式模型中。
传统语言模型虽然容易训练,但是缺乏上下文泛化能力且平滑技术复杂。随着深度学习在自然语言和图像识别领域的成功应用,各种神经语言模型被提出,并应用到软件工程领域。目前软件工程领域的神经语言模型主要有循环神经网络(RNN)、卷积神经网络(CNN)、seq2seq模型以及图神经网络(GNN)。深度学习的一般过程为通常将代码及类别标签作为数据输入,利用深度学习模型学习代码中隐藏的深层次特征,把代码从高维稀疏表示映射为低维空间连续向量,并利用这种向量表征,完成各类下游任务。一个基本的模型结构如图5所示,包含输入层、模型层、输出层和应用层。输入层的数据主要有组成代码的词汇序列、词法分析的Token序列、语法分析的抽象语法树以及上下文环境的控制流图、数据流图、程序依赖图等。模型层可以是各类深度学习模型,包括循环神经网络、卷积神经网络、深度神经网络、编码-解码器等,输出层得到代码的向量表示,并通过连接一些激活函数、相似性计算函数、全连接层完成具体应用,表1中列出了近几年基于深度学习的代码表征模型及其特征。
图5 基于深度学习的代码表征模型Fig.5 Code representation based on deep learning
表1 基于深度学习的源码表征模型的现有工作Table 1 Existing studies of code representation framework based on deep learning
一般的神经网络是从输入层到隐藏层再到输出层的结构,层与层之间是全连接的,层内结点是无连接的,在处理序列化数据时,显得无能为力。循环神经网络将层内结点通过时序连接起来,能够对序列数据之间的依赖关系建模,被应用在机器翻译、语音识别等领域。RNN具有较好的处理序列数据的能力,其一般结构如图6所示,网络接收输入序列作为输入,产生固定大小的向量作为序列的表示,其输出可以作为其他网络结构的输入,应用于分类、预测、翻译等问题。White等人第一个把RNN引入到代码推荐系统,结合循环神经网络和递归神经网络,提取代码的词汇信息和结构信息,赋予代码表征以丰富的深层次信息,通过实验验证了RNN模型在代码推荐、代码克隆检测任务中有着远远超过传统N-Gram方法的性能[42,49]。Dam等人提出了DeepSoft模型,构建了一个基于LSTM的端到端的软件分析框架,学习软件建模中的长期依赖性,在代码推荐等任务取得了较好的效果[8]。
图6 RNN模型结构Fig.6 Structure of RNN model
Zhang等人提出了语句级别的向量嵌入,如图7所示,首先将一个代码片段的AST分割成多个小粒度的语句树,编码器遍历语句树,并对所有的语句树执行基于树的嵌入,产生语句级的向量,把语句级向量序列送入双向RNN模型,捕捉语句以及语句之间的序列依赖关系,进而生成代码片段向量,有效捕获了代码的语法和语义信息[10]。Ben-Nun等人针对代码本身具有的一些结构化的特性,提出了Inst2vec模型,该模型首先把源代码转换成一种新的中间语言,基于这种中间语言,构建基于上下文流动图,送入RNN模型训练出语句级的向量表征,该模型在代码分类、性能预测等多种任务中得到了很好的效果[9]。
图7 语句级RNN模型Fig.7 Structure of RNN model on sentence level
RNN模型的主要用途是处理和预测序列数据,因此在代码翻译、代码推荐等必须要考虑上下文相关信息的任务上,表现出较好的性能。但是当上下文序列很长的时候,容易有梯度消失时的问题。
卷积神经网络是一种特殊的前馈神经网络结构,为减少网络中参数个数,用卷积层来代替传统的全连接层,提高神经网络的训练效率,卷积神经网络可以提取信息最多的数据特征,生成一个固定大小的向量表示结构。
Mou等人[43]设计了一个树形结构的卷积层,AST中上层结点的词向量由它的孩子结点表示。如图8所示,对于每个非叶结点p以及它的孩子c1,c2,…,cn,具有表达式:
图8 CNN模型结构Fig.8 Structure of CNN model
其中,vec(p),vec(c1),vec(c2),…,vec(cn)是结点p以及孩子结点的向量表示,w i是结点c i的权重矩阵,b是偏置项。根结点的向量即为整个AST的表示,用来表示函数的特征,该方法在代码分类和搜索任务中有较好的表现[46]。
CNN模型适用于树、图等具有局部空间相关性的数据,主要用于代码的AST结构上。AST的叶子结点能够表示Token级别的语义信息,CNN网络可以提取代码的语法结构信息,因此,CNN模型能够挖掘深层次的语法和语义信息,在代码分类、代码克隆、函数命名等任务有较好的性能表现,但同样在深层次模型中存在梯度消失现象。
由于LM模型的限制,Nguyen等人在前期工作的基础上提出了深度神经网络模型Dnn4C,如图9所示,其基本思想是设置三类映射通路,通过深度学习模型将三类信息实体映射到同一个隐空间,这三类信息包括源代码的词汇序列,对解析树的非终端结点抽取并按照一定规则转换成的语法符号序列,以及类型转换序列,源代码的表征即为三类信息隐藏向量的融合[45]。
图9 DNN模型结构Fig.9 Structure of DNN model
为解决子图匹配计算量的问题,Zhao等人[46]提出一个新型的编码方法,如图10所示,对代码中的变量特征、基本块特征、变量和基本块之间的关系特征进行编码,将代码的控制流和数据流编码成一个压缩的语义矩阵,矩阵的每个元素都是一个高维稀疏的二进制向量,代表了该元素的控制流和数据流信息。基于这个语义矩阵,设计一个DNN模型把代码相似性问题转化为一个二元分类的问题,进而学习相似代码的模式。与传统的CFG和DFG相比,语义特征矩阵减少了寻找同构子图以检测矩阵中相似模式的问题,并且在下游任务很容易使用。Wang等解析程序源代码中的抽象语法树,利用深度置信网络从中抽取语义特征,利用这些特征进行软件缺陷检测[50]。
图10 新型编码模型Fig.10 New coding model
DNN模型就是多层神经网络,在一维数据上具有较好的表现,可通过各种组合形式,把不同类型数据融合一起,但实际应用中采用较少的模型。
编码-解码模型是一个解决Seq2Seq问题的模型,就是根据一个输入序列X,来生成另一个输出序列Y,通过一个编码和一个解码过程来重构输入数据,学习数据的隐层表示。基本的编码-解码模型,结构如图11所示,所谓编码,就是将输入序列转化成一个固定长度的向量;解码,就是将之前生成的固定向量再转化成输出序列,其中编码器和解码器可以是任何一种深度神经网络模型,例如CNN/RNN/GRU/LSTM等等。Alon等人基于编码-解码器提出了Code2Seq模型,从源码的AST中分别提取叶子结点和非叶子结点,AST的叶结点代表变量名和变量类型名,非叶结点代表程序的结构集,比如循环、变量声明、表达式等。编码器为双向LSTM,输入为AST中任意一条路径上的结点的嵌入向量的全连接,解码器采用单向LSTM,初始状态为各条路径编码的加权平均。该模型和基于Token的方法的区别是Encoder的输入不是Token句子序列,而是由Encoder为AST中的每条路径分别创建一个向量表示。Decoder过程处理这些AST路径编码[41]。
图11 Encoder-Decoder模型结构Fig.11 Structure of Encoder-Decoder model
编码-解码模型是一个通用的模型,Encoder、Decoder可以是任何类型的数据,针对不同的任务,可以构建CNN、DNN、LSTM等任意深度学习模型。因为是一个端到端的学习算法,在代码翻译领域应用较多。
源代码分析的最大难点在于程序语言是一种高度结构化的语言,现有方法大多只考虑了源代码的浅层特征,如方法名、Token分词,但忽略了抽象语法树中的语义信息和控制流图的结构化特征。其次,尽管基于深度学习的方法在源代码的表示上表现良好,但缺乏可解释性,几乎不可能理解源代码的哪些特征对最终结果贡献更多。为了解决上述两个问题,Wan等人提出了一种用于语义源代码检索的新型多模态注意力机制网络,开发了一种全面的多模态表示,用于表示源代码的非结构化和结构化特征,其中LSTM用于表示代码的分词,Tree-LSTM用于表示代码AST,GGNN用于表示代码的CFG。该模型在代码检索领域获得了最好的性能[47]。
把代码表达为文本序列或者Token序列,甚至解析为AST,即使和深度学习结合,也更多只是抓住了代码浅层的文本结构信息。错失了代码丰富的语义信息。为了弥补这一问题,Allamanis等人提出图神经网络抓取代码的语法和语义特征,图神经网络通过消息传递迭代更新所有结点的隐藏状态,每个结点既接受相邻结点的信息,又向相邻结点发送信息。图神经网络结构如图12所示,首先把源代码解析为AST,在AST中通过增加数据流和类型层级两种信息,将程序编码成图,图由语法结点即语法中的非终结符和语法标记即终端结点组成,图的边代表Token之间的语法和语义关系,直接将这些语义输入到机器学习模型中以减少对训练数据量的要求。结点的初始状态为Token的文本表示与它的类型的结合,通过多次迭代更新得到每个结点的最终状态[24,51-52]。
图12 GNN模型结构Fig.12 Structure of GNN model
GNN网络用于学习非欧式空间数据的信息,GNN既可以学习图中Token结点信息,也可以学习边的预测,同时还可以学习CFG、DFG、PDG等图的整体信息,用来表示一个程序的语义特征。所以GNN使用非常广泛,可以用于代码分类、缺陷预测、代码推荐或者代码摘要生成等任务。但是GNN不适用于图中结点不断变化的动态图,而且还有很多缺陷需要进一步研究,例如图的不动点计算问题、边信息传播问题,都需要新的方法进一步来解决。
随着神经网络、语言模型的不断发展,深度学习在软件系统中的应用越来越受到广泛关注,基于深度学习的代码分析研究已经成为软件工程领域的新的研究热点[53]。不过,从前面的分析,可以看出深度学习在代码分析领域的应用才刚刚起步,未来还需要在以下几方面进一步努力。以下总结了三个可能的研究方向:
(1)现有语言模型的改进
自从验证了自然语言的模型对代码分析具有很好的效果,各种编程语言模型纷纷出现,并广泛应用到代码生成、代码克隆检测、Bug检测等各个领域。这些方法把自然语言深度学习模型直接应用到源码中,学习代码中词、Token、语法树中各类结点的向量表示。同时,Allamanis等人指出语言模型依赖代码的文本自然特征,但是程序代码和自然语言只有小部分词汇是重复的,即使相同的词,在自然语言和程序代码中的含义也不相同,而且程序员可以随意给变量命名,造成代码的词汇是开放性的,因此,不能直接把语言模型应用到代码上[23],必须挖掘程序代码的其他特征。
(2)多模态深度学习框架
从前面的分析可以看出,借助自然语言模型对代码词汇、Token、AST、CFG/DFG等进行向量表征,虽然该方向取得了一定研究进展,但目前仍存在以下问题:首先,现有方法能够解决简单相似以及结构相似问题,但对功能相似的研究还比较少,即使有些用同构子图的方法来研究,但鉴于代价太高,不能广泛应用。代码不仅具有文本自然性,同时具有语法结构信息,而且代码是可执行的,具有动态语义特征,和词汇、语法和语义都有相关性,因此结合深度学习技术,采用多模态代码表征方式,构建新的学习框架,从词汇、语法、执行路径等多个方面学习代码的语义信息,直接学习隐藏在源代码中更加复杂的语义和语法特征,将会成为程序分析领域的一个重点研究方向。
(3)通用的深度学习框架
面向特定应用领域的深度学习表征模型获得了较好的性能,纯粹的、通用的代码表征是研究者致力解决的新问题。类似于自然语言的Word2Vec,Alon等人提出了Code2Vec方法,利用神经网络训练代码的语义向量[19],紧接着Kang等人把Code2Vec方法应用到了注释生成、作者识别、代码克隆三种不同类型的任务,评价了Code2Vec对各项任务的通用性,实验结果表明非特定任务的训练,其效果并不比一般方法好[54]。
目前常用的表征粒度中,标识符可以表达领域语义和开发者的理论基础,在特征定位和软件模块化有作用。抽象语法树可以捕获程序的语法结构和模式。CFG和DFG可以表达程序的部分语义信息,这些不同的表征,其抽象程度不同,适合不同的具体任务,Tufano等人提出集成学习方法,把不同粒度的表征向量通过加权平均组合为代码的抽象表征[48]。Feng等人基于Transformer的神经网络构建了CodeBERT模型,在大规模数据集上预训练,致力学习源码的通用表示方法,支持下游自然语言代码搜索、代码文档生成等应用[55]。但总体上来说,通用的代码表征框架和模型还不够成熟,未来还有待进一步更广泛的研究。
随着开源代码的日益增多,将深度学习技术融入代码表征,开展更深层次的代码表征的研究,已经成为软件工程领域解决各类问题的新方法。通过从海量数据中学习代码的语法、语义特征,用于解决各类下游任务。与传统的基于机器学习的方法相比,基于深度学习的表征模型能够自动学习代码的结构和抽象语义的隐藏特征,更有效地提高代码表征的准确性。本文主要介绍和分析了基于深度学习的代码表征的研究现状和进展,并根据现有工作的局限性讨论了今后可能的发展方向和趋势。