張祥平,劉建勛+
1.湖南科技大學(xué) 服務(wù)計(jì)算與軟件服務(wù)新技術(shù)湖南省重點(diǎn)實(shí)驗(yàn)室,湖南 湘潭 411201
2.湖南科技大學(xué) 計(jì)算機(jī)科學(xué)與工程學(xué)院,湖南 湘潭 411201
計(jì)算機(jī)軟件是現(xiàn)代社會最具代表性的產(chǎn)物,它被應(yīng)用在人們生活工作中的方方面面。在現(xiàn)代計(jì)算機(jī)軟件發(fā)展過程中,數(shù)以百萬計(jì)的開發(fā)者參與并貢獻(xiàn)了大量高水平的軟件代碼。然而由于代碼本身所具有的抽象性、復(fù)雜性與可變性,想要進(jìn)行高效、快速的軟件開發(fā)仍是一個(gè)艱巨任務(wù)。針對此問題,開發(fā)者們研發(fā)出許多軟件開發(fā)輔助工具,這些工具旨在幫助開發(fā)者理解、編寫以及維護(hù)代碼,從而提高開發(fā)人員的工作效率。
傳統(tǒng)的軟件開發(fā)輔助工具通常是基于人工制定的代碼分析任務(wù)構(gòu)建的,因此此類工具在特定任務(wù)范圍內(nèi)能夠起到很好的效果。但隨著代碼開源活動(dòng)的興起,全球開源項(xiàng)目呈指數(shù)級增長。開源貢獻(xiàn)者在開源平臺貢獻(xiàn)了大量高質(zhì)量的開源項(xiàng)目,這些項(xiàng)目中包含了豐富的程序信息,如源代碼、代碼注釋、故障報(bào)告以及測試用例等。面對規(guī)模巨大、信息繁雜的代碼數(shù)據(jù),傳統(tǒng)的代碼分析方式難以獲得令人滿意的效果。Hindle 等人提出了代碼的“自然性”假說,該假說認(rèn)為程序語言與自然語言類似,包含了豐富的代碼統(tǒng)計(jì)信息,這些信息可以用于構(gòu)建功能更加豐富的軟件開發(fā)輔助工具。代碼“自然性”理論使人們意識到運(yùn)用統(tǒng)計(jì)規(guī)律對代碼信息進(jìn)行分析是可行的,因此大量機(jī)器學(xué)習(xí)算法被運(yùn)用到代碼分析任務(wù)中,對代碼進(jìn)行多角度的信息挖掘,這極大地豐富了人們對于代碼的認(rèn)識。在此過程中,最基礎(chǔ)的環(huán)節(jié)是如何將代碼轉(zhuǎn)換為合適的代碼表征向量,以適應(yīng)不同的機(jī)器學(xué)習(xí)任務(wù)。
傳統(tǒng)的代碼表征方法簡單地將代碼視為自然語言進(jìn)行處理,使用自然語言處理領(lǐng)域中成熟的算法將代碼轉(zhuǎn)換為表征向量,并在其上應(yīng)用機(jī)器學(xué)習(xí)算法完成代碼分析。如使用TF-IDF(term frequencyinverse document frequency)算法將代碼轉(zhuǎn)換為與詞頻相關(guān)的代碼向量表征,此類方法僅僅使用簡單的詞頻統(tǒng)計(jì)信息,沒有將代碼中豐富的語義信息以及結(jié)構(gòu)信息融入到代碼向量中,因此所生成的代碼向量質(zhì)量較低。隨著近幾年深度學(xué)習(xí)取得的巨大成就,工業(yè)界與學(xué)術(shù)界對于在軟件工程領(lǐng)域中探索和使用深度學(xué)習(xí)算法表現(xiàn)出了極大的熱情??舍槍Σ煌拇a分析任務(wù),使用不同的神經(jīng)網(wǎng)絡(luò)模型,生成高質(zhì)量的代碼表征向量。如使用循環(huán)神經(jīng)網(wǎng)絡(luò)能夠?qū)⒆冮L的代碼序列轉(zhuǎn)換為固定長度的代碼向量,進(jìn)行代碼分類和代碼克隆檢測任務(wù)。使用序列到序列的神經(jīng)網(wǎng)絡(luò)模型進(jìn)行代碼自動(dòng)生成任務(wù),其中的編碼器能夠?qū)⒋a編碼為特征向量,之后使用解碼器將其輸出為代碼序列。
在海量代碼數(shù)據(jù)上使用深度學(xué)習(xí)模型進(jìn)行訓(xùn)練、學(xué)習(xí)、歸納,自動(dòng)地提取代碼中人工難以提取的深層次特征。通過不斷組合不同的神經(jīng)網(wǎng)絡(luò)模型,將低層網(wǎng)絡(luò)中獲得的低階特征組合成更加抽象的高階特征,進(jìn)而發(fā)現(xiàn)數(shù)據(jù)所隱含的高維信息。將這些信息融入到代碼表征向量中,能夠有效降低代碼特征提取部分對人工制定特征的依賴。與傳統(tǒng)人工制定的代碼特征提取方法不同,基于深度學(xué)習(xí)的代碼特征提取方法無需依賴軟件工程領(lǐng)域?qū)<业谋尘爸R,就能夠從海量代碼數(shù)據(jù)中自動(dòng)地提取出適用于不同任務(wù)的隱含特征,進(jìn)而避免了因?yàn)槿祟愑^察偏差所造成的失誤。
本文對近幾年基于深度學(xué)習(xí)的代碼表征工作進(jìn)行分析與總結(jié),并對代碼表征工作下一步的研究方向提出了一些看法。
如何從大量的數(shù)據(jù)中學(xué)習(xí)到數(shù)據(jù)之間的內(nèi)在規(guī)律并應(yīng)用于數(shù)據(jù)分析任務(wù),是當(dāng)前機(jī)器學(xué)習(xí)的首要目標(biāo)。作為機(jī)器學(xué)習(xí)方法的基礎(chǔ),一個(gè)高質(zhì)量的數(shù)據(jù)表征方法能夠極大地提高機(jī)器學(xué)習(xí)算法的性能。機(jī)器學(xué)習(xí)算法在數(shù)據(jù)表征的基礎(chǔ)上對模型進(jìn)行優(yōu)化,進(jìn)而擬合已有的數(shù)據(jù)分布,最終應(yīng)用于不同的任務(wù)場景。現(xiàn)如今,由于計(jì)算機(jī)計(jì)算能力的提升,代碼分析領(lǐng)域已從傳統(tǒng)的人工制定特征提取方法轉(zhuǎn)向?qū)Υa使用深度學(xué)習(xí)技術(shù)進(jìn)行特征提取。與傳統(tǒng)的使用人工制定的特征提取方法不同,后者對代碼分析的效率要遠(yuǎn)高于人工分析。首先,基于深度學(xué)習(xí)的代碼分析方法可以減少人工制定特征的時(shí)間,降低了對代碼分析的成本。其次,基于深度學(xué)習(xí)的代碼分析方法能夠發(fā)現(xiàn)更多人工難以發(fā)現(xiàn)的高維特征,也能夠從不同的視角獲取代碼之間聯(lián)系。
基于深度學(xué)習(xí)技術(shù)的代碼分析方法通常可以用圖1 來表示其分析流程。圖1 包含四部分,分別是:(1)代碼處理;(2)代碼表征;(3)任務(wù)實(shí)現(xiàn);(4)結(jié)果驗(yàn)證。
圖1 基于深度學(xué)習(xí)的代碼分析框架Fig.1 Framework of code analysis with deep learning
代碼處理是從海量的代碼庫中收集代碼,并根據(jù)任務(wù)的需要對代碼進(jìn)行簡單的處理,如對代碼進(jìn)行漂亮打?。╬retty-printing),將代碼轉(zhuǎn)換為抽象語法樹(abstract syntax tree,AST)等。代碼表征是指將代碼轉(zhuǎn)換為其對應(yīng)的向量。這個(gè)過程中主要是根據(jù)代碼不同的粒度,將代碼轉(zhuǎn)換為對應(yīng)的向量表示。這一步驟對之后機(jī)器學(xué)習(xí)模型的效果有極大的影響。目前最常見的方法是使用Word2vec 算法將代碼中詞匯轉(zhuǎn)換為對應(yīng)的詞向量,之后構(gòu)建特定的神經(jīng)網(wǎng)絡(luò)模型對代碼信息進(jìn)行總結(jié),得到代碼的表征向量。將神經(jīng)網(wǎng)絡(luò)用在所獲得的代碼表征向量之上,對具體的代碼分析任務(wù)做預(yù)測分析,這一過程需要根據(jù)具體任務(wù)設(shè)計(jì)不同的網(wǎng)絡(luò)結(jié)構(gòu)。最后,對模型所生成的結(jié)果進(jìn)行驗(yàn)證,通過不同的評價(jià)指標(biāo),對具體任務(wù)結(jié)果進(jìn)行驗(yàn)證。
而在代碼分析任務(wù)中,最重要的是對代碼進(jìn)行合適的表征,使其能夠包含任務(wù)所需的有效信息。
基于矩陣的表征模型通常基于一個(gè)詞共現(xiàn)矩陣計(jì)算得到詞的向量。詞共現(xiàn)矩陣,形狀為×,其中是詞典大小,表示所使用的上下文信息長度。共現(xiàn)矩陣每一行F表示一個(gè)單詞的分布表征,每一列F表示上下文信息。在文本處理中,詞典大小數(shù)量通常十分巨大。如圖2,要在內(nèi)存中維護(hù)一個(gè)規(guī)模龐大的共現(xiàn)矩陣將會消耗大量的內(nèi)存資源。因此有許多工作研究如何構(gòu)建合適的詞共現(xiàn)矩陣以便減少計(jì)算資源的消耗。這類工作的目標(biāo)可以用以下語言進(jìn)行描述:使用一個(gè)映射函數(shù),將詞共現(xiàn)矩陣映射為,即=(),的形狀為×,使得?。
圖2 基于矩陣的分布表征Fig.2 Matrix-based distributional representation
目前已有一些工作關(guān)注于對共現(xiàn)矩陣的降維。如Sahlgren通過控制滑動(dòng)窗口所滑動(dòng)的方向、滑動(dòng)窗口的大小來選取目標(biāo)詞不同范圍的上下文信息,構(gòu)造一個(gè)維度較小的共現(xiàn)矩陣,進(jìn)而減少計(jì)算資源的使用。這個(gè)方法從共現(xiàn)矩陣的行(詞匯個(gè)數(shù))出發(fā)對共現(xiàn)矩陣進(jìn)行降維。還有一些方法從共現(xiàn)矩陣的列(上下文信息)出發(fā),如常見的分布表征方法,隱含語義分析模型(latent semantic analysis,LSA)、隱含狄利克雷分布模型(latent Dirichlet allocation,LDA)。比如LDA 模型,在構(gòu)建共現(xiàn)矩陣的列時(shí),所采用的是文檔上下文信息而不是詞信息,因而可以獲得更高層次的語義信息。但是,將這些方法應(yīng)用于代碼表征任務(wù)中對所使用的代碼語料庫質(zhì)量要求較高。如果所使用的代碼庫質(zhì)量不高,如出現(xiàn)數(shù)據(jù)稀疏,包含較多噪聲數(shù)據(jù),使用上述方法難以獲得高質(zhì)量的代碼表征結(jié)果?,F(xiàn)有的一些方法研究如何在有限的內(nèi)存資源中維護(hù)規(guī)模巨大的共現(xiàn)矩陣,如Sojka 等人提出一種增量構(gòu)建方法,使得在超過2.7 億個(gè)詞的語料庫上也可以進(jìn)行LSA 和LDA 主題建模。
基于神經(jīng)網(wǎng)絡(luò)的表征模型能夠?qū)⒉煌愋偷臄?shù)據(jù)映射到連續(xù)、低維的向量空間中。由于其生成的向量是將原始數(shù)據(jù)的特征分布式表示在向量的各個(gè)維度上,其所生成的表征向量也可以稱為是分布式表征。在自然語言處理領(lǐng)域中對文本的表征通常是集中在單詞、句子和段落層面上。文本的分布式表征極大地降低了表征向量維度,如使用獨(dú)熱表征(one-hot)需要()個(gè)參數(shù)才能區(qū)分()個(gè)輸入空間(表示文本數(shù)據(jù)集中不同詞匯的數(shù)量),而如果采用分布式表征,同樣使用()個(gè)參數(shù)卻能夠區(qū)分(2)個(gè)輸入空間。并且分布式表征向量包含文本的語義信息,可以通過計(jì)算兩個(gè)文本的表征向量之間的余弦距離來獲得兩個(gè)文本之間的相似度。最常見的分布式表征方法有Word2vec 算法。Word2vec通過固定大小的滑動(dòng)窗口來獲得中心詞的局部上下文信息。Word2vec 算法包含兩種訓(xùn)練框架Skipgram 和CBOW,其對應(yīng)的模型結(jié)構(gòu)圖如圖3 所示。Skip-gram 框架根據(jù)所給中心詞來預(yù)測其周圍的詞。因此其優(yōu)化的目標(biāo)函數(shù)為:
圖3 Word2vec的兩種訓(xùn)練框架Fig.3 Two training frameworks of Word2vec algorithm
而CBOW 框架是根據(jù)周圍的詞去預(yù)測中心詞,因此CBOW 框架的目標(biāo)函數(shù)為:
現(xiàn)有的代碼分析工作通常是從以下兩個(gè)角度對代碼進(jìn)行分析:(1)代碼的靜態(tài)分析;(2)代碼的動(dòng)態(tài)分析。其中代碼的靜態(tài)分析指基于代碼的靜態(tài)屬性對代碼進(jìn)行特征提取,如代碼的符號序列信息、API 調(diào)用序列信息、代碼對應(yīng)的抽象語法樹以及控制流圖中存在的結(jié)構(gòu)信息。而代碼的動(dòng)態(tài)分析關(guān)注于代碼運(yùn)行過程中產(chǎn)生的中間結(jié)果,基于中間結(jié)果對代碼任務(wù)進(jìn)行分析。本文所研究的內(nèi)容是靜態(tài)情況下代碼信息的抽取與表征,因此本章內(nèi)容將從不同的代碼靜態(tài)信息出發(fā),分析深度學(xué)習(xí)技術(shù)在不同代碼分析任務(wù)中所起到的作用。而對于代碼的動(dòng)態(tài)分析方法,本文不作詳述。
在基于符號序列的代碼表征方法中,最基礎(chǔ)的步驟是根據(jù)語法規(guī)則將代碼中的詞匯單元(token)劃分出來。在這個(gè)過程中需要用到詞法分析器,詞法分析器能夠按照預(yù)定的語法規(guī)則將代碼中的字符串分割為一個(gè)個(gè)詞匯單元,這些詞匯單元通常包含關(guān)鍵字、數(shù)字、標(biāo)識符等。在此過程中,代碼中的空白符也會被移除。將代碼表示為詞匯單元序列之后,利用深度學(xué)習(xí)技術(shù)對其進(jìn)行建模,學(xué)習(xí)代碼序列中所包含的有效信息,如功能語義信息、語法結(jié)構(gòu)信息等,最后生成具有豐富代碼信息的表征向量,應(yīng)用于不同的代碼分析任務(wù)。
Harer 等人在C/C++的詞匯單元上使用Word2-vec 算法用于軟件漏洞的預(yù)測。其中詞匯單元的表征向量用于初始化TextCNN 模型進(jìn)而用于代碼分類。White 等人也使用Word2vec 模型生成Java 詞匯單元的表征向量用于自動(dòng)代碼修復(fù)。Chen等人使用Word2vec 生成Java 詞匯單元表征向量用于在自動(dòng)程序修復(fù)任務(wù)中找到正確的部分,該方法通過計(jì)算不同代碼表征向量之間的余弦相似度來找到相似的代碼片段。Henkel 等人從C 語言項(xiàng)目中使用Word2vec 生成抽象符號軌跡表征向量。他們在符號執(zhí)行過程中收集軌跡信息,然后對軌跡中的符號名稱進(jìn)行重命名以降低詞表大小。Nguyen 等人研究了Java 和C#中API 的表征向量,并用它發(fā)現(xiàn)了兩種語言之間相似的API 用法。他們使用Word2vec 生成API 元素嵌入,并且基于已知的API 映射,該模型可以在兩種語言中找到使用相似的簇類。將源代碼翻譯為API 序列,并使用Word2vec 從抽象的API 序列中學(xué)習(xí)了API 嵌入。Pradel等人使用代碼片段嵌入來識別潛在的錯(cuò)誤代碼。為了生成訓(xùn)練數(shù)據(jù),作者從代碼庫中收集代碼示例,并應(yīng)用簡單的代碼轉(zhuǎn)換來插入一個(gè)人工構(gòu)造的錯(cuò)誤,之后生成了正反碼示例的嵌入,并用于分類器的訓(xùn)練過程。以上方法通過使用Word2vec 算法,預(yù)先將代碼中的詞匯單元轉(zhuǎn)換為實(shí)數(shù)向量,便于之后的深度學(xué)習(xí)模型進(jìn)行特征提取。然而,受Word2vec 算法的限制,所生成的詞匯單元向量僅僅保留了基本的詞共現(xiàn)信息,對于代碼中存在的邏輯信息,如變量定義與使用的先后順序,循環(huán)語句中的邏輯判斷信息都無法獲取。因此在代碼表征的過程中,Word2vec 算法通常只在最初的詞匯單元轉(zhuǎn)向量階段使用,對于代碼中包含的詞法和語義信息,仍需要學(xué)習(xí)能力更強(qiáng)的神經(jīng)網(wǎng)絡(luò)模型才能進(jìn)行完整的信息提取。
高質(zhì)量軟件項(xiàng)目中,通常每份代碼都會有其對應(yīng)的代碼注釋。而實(shí)際情況下,編寫代碼注釋同樣會消耗軟件開發(fā)者大量的開發(fā)時(shí)間。因此目前也有很多學(xué)者研究如何自動(dòng)地生成代碼對應(yīng)的注釋以減少開發(fā)者的工作內(nèi)容。目前有許多工作基于Seq2seq框架構(gòu)建模型完成該任務(wù)。Seq2seq 模型是一種能夠根據(jù)給定的詞匯序列,通過特定的方法生成另一種詞匯序列的方法,比如在機(jī)器翻譯中,使用Seq2seq模型可以將中文文本序列轉(zhuǎn)換為其對應(yīng)的英文文本序列。而在代碼注釋生成領(lǐng)域中,Seq2seq 模型使用編碼器(encoder)對代碼中的詞匯序列進(jìn)行編碼,得到上下文向量的中間表示,之后使用解碼器(decoder)對上下文向量進(jìn)行解碼,從而生成這些代碼序列所對應(yīng)的功能注釋。其中編碼器與解碼器通常是使用序列神經(jīng)網(wǎng)絡(luò)作為基本的序列特征提取模型。例如,Iyer 等人提出了CODE-NN 模型,該模型使用附加注意力機(jī)制的LSTM 序列神經(jīng)網(wǎng)絡(luò)模型對代碼詞匯序列進(jìn)行編碼。在編碼過程中注意力機(jī)制會對代碼中的每一個(gè)詞匯單元都附加上注意力權(quán)重,最后獲得代碼對應(yīng)的向量表征。如要進(jìn)行代碼注釋生成工作,只需直接對上下文中間向量使用解碼器即可生成對應(yīng)的代碼注釋。該方法在C#和SQL所構(gòu)成的代碼數(shù)據(jù)集上進(jìn)行代碼注釋生成,在METEOR 和BLEU 兩個(gè)指標(biāo)上取得了當(dāng)時(shí)最優(yōu)的結(jié)果??紤]到注意力機(jī)制對模型輸入輸出長度不同的數(shù)據(jù)有較好的建模效果,Allamanis 等人使用基于注意力機(jī)制CNN 對代碼進(jìn)行建模,該模型將函數(shù)表示為詞匯單元序列,在詞匯單元序列上進(jìn)行卷積操作,考慮到注意力機(jī)制能夠獲取代碼序列中不同詞匯的重要程度,在模型中使用了注意力機(jī)制。以上有關(guān)于代碼注釋生成的工作不同于傳統(tǒng)的基于人工定制規(guī)則的方法,基于神經(jīng)網(wǎng)絡(luò)的代碼生成模型能夠挖掘更深層次的代碼信息,獲得更加豐富的代碼表征向量用于下一步的代碼分析任務(wù)。但是這些方法也存在明顯的缺點(diǎn),因?yàn)榇a具有較強(qiáng)的邏輯結(jié)構(gòu)關(guān)系,如不同API 之間的調(diào)用關(guān)系、語句的執(zhí)行順序等。如果僅僅考慮代碼中文本信息進(jìn)行模型的訓(xùn)練,那么代碼中的結(jié)構(gòu)信息大部分都會丟失。
代碼補(bǔ)全任務(wù)是根據(jù)已有代碼詞匯序列預(yù)測下一個(gè)詞匯或者下一段代碼序列。循環(huán)神經(jīng)網(wǎng)絡(luò)模型能夠較好地對序列數(shù)據(jù)進(jìn)行建模,因?yàn)槠淠軌蛟谌我忾L度的上下文窗口中存儲、學(xué)習(xí)和表達(dá)相關(guān)信息,并且在時(shí)間序列上有延拓?,F(xiàn)在很多工作使用循環(huán)神經(jīng)網(wǎng)絡(luò)對代碼序列這類長文本數(shù)據(jù)進(jìn)行特征提取。例如White 等人使用RNN(recurrent neural network)神經(jīng)網(wǎng)絡(luò)對代碼中的詞匯單元序列進(jìn)行建模。作者在Github 網(wǎng)站上收集了16 221 個(gè)Java 項(xiàng)目進(jìn)行模型的訓(xùn)練。最終在Java 代碼的代碼補(bǔ)全任務(wù)上取得了72.2%的準(zhǔn)確率,這一結(jié)果遠(yuǎn)遠(yuǎn)高出了當(dāng)時(shí)的元語法模型所取得的效果。Li等人考慮了在代碼補(bǔ)全任務(wù)上超出詞表問題(out of vocabulary,OoV),在傳統(tǒng)的RNN 神經(jīng)網(wǎng)絡(luò)的基礎(chǔ)上增加了注意力機(jī)制,結(jié)合指針網(wǎng)絡(luò)能夠復(fù)制已出現(xiàn)過的上下文信息的特性,實(shí)現(xiàn)了超出詞表外代碼片段的補(bǔ)全任務(wù)。在兩個(gè)基準(zhǔn)數(shù)據(jù)集上的實(shí)驗(yàn)證明該方法中注意力機(jī)制和指針混合網(wǎng)絡(luò)在代碼補(bǔ)全任務(wù)中的有效性。Bhoopchand 等人針對代碼中不同標(biāo)識符之間存在的長依賴問題,提出了基于指針網(wǎng)絡(luò)的程序語言模型。該方法采用RNN 的變種模型LSTM 神經(jīng)網(wǎng)絡(luò)作為構(gòu)建神經(jīng)語言模型的基礎(chǔ)模型。LSTM 模型作為RNN 模型的一個(gè)變種模型,能夠在一定程度上緩解長文本中詞匯之間的依賴問題(如,一個(gè)變量在代碼開頭就被定義,但是直到程序末尾時(shí)才被使用)。該方法通過使用指針網(wǎng)絡(luò)在之前的輸入數(shù)據(jù)中選擇已出現(xiàn)過的標(biāo)識符作為輸出的備選結(jié)果,最后通過一個(gè)選擇器綜合考慮語言模型和指針網(wǎng)絡(luò)兩者的結(jié)果,共同預(yù)測輸出詞語的概率分布。該工作從Github網(wǎng)站上收集了4 100 萬行Python 代碼,在該數(shù)據(jù)集上進(jìn)行代碼補(bǔ)全實(shí)驗(yàn),實(shí)驗(yàn)結(jié)果顯示LSTM 模型的補(bǔ)全預(yù)測結(jié)果能夠提供距離當(dāng)前補(bǔ)全位置較遠(yuǎn)的上文中所出現(xiàn)過的標(biāo)識符,能夠取得更高的補(bǔ)全準(zhǔn)確率。與傳統(tǒng)的基于統(tǒng)計(jì)規(guī)則的模型相比,這類基于詞匯序列的神經(jīng)網(wǎng)絡(luò)模型的效果明顯優(yōu)于前者。但這些模型也存在許多不足,若是將代碼簡單地轉(zhuǎn)換為詞匯序列,那么代碼中不同語句之間的順序信息、不同API 的調(diào)用信息等代碼所特有的結(jié)構(gòu)信息就會丟失,這勢必會影響代碼分析任務(wù)的性能。同時(shí),受制于所使用基本模型本身的缺陷,如要使用神經(jīng)網(wǎng)絡(luò)對文本信息進(jìn)行處理,必須要先確定最大文本長度以適應(yīng)模型的輸入,這同樣會增加代碼信息的提取難度。
代碼搜索指根據(jù)用戶對代碼功能的描述語句在代碼庫中檢索符合功能的代碼。傳統(tǒng)的代碼搜索大多采用的是信息檢索領(lǐng)域中的檢索技術(shù),通過計(jì)算查詢語句與代碼之間的相似程度進(jìn)行搜索。最簡單的方法就是關(guān)鍵詞匹配。隨著深度學(xué)習(xí)技術(shù)在各個(gè)領(lǐng)域所取得的突破性進(jìn)展,也有大量學(xué)者將其應(yīng)用于代碼搜索領(lǐng)域。運(yùn)用深度神經(jīng)網(wǎng)絡(luò)將代碼與自然語言查詢語句轉(zhuǎn)換為同一個(gè)語義空間中的向量,通過對向量之間的相似度計(jì)算,能夠有效地挖掘出代碼與查詢語句之間更高層的聯(lián)系。深度學(xué)習(xí)在代碼搜索上的應(yīng)用通常是在代碼的詞匯序列上使用RNN神經(jīng)網(wǎng)絡(luò)模型進(jìn)行建模,將詞匯序列轉(zhuǎn)換為序列向量。同時(shí)使用RNN 神經(jīng)網(wǎng)絡(luò)模型對自然語言查詢語句進(jìn)行建模,將其轉(zhuǎn)換為查詢語句向量。在檢索時(shí),在同一向量空間中搜索與查詢語句向量相似的代碼向量作為檢索結(jié)果。在此過程中,最重要的研究內(nèi)容就是如何將序列表征向量與自然語言查詢語句的向量映射到同一代碼語義向量空間中。Gu 等人首先將深度學(xué)習(xí)技術(shù)應(yīng)用在代碼搜索領(lǐng)域,對給定的自然語言查詢語句生成其所描述的API 用法序列。該方法將問題轉(zhuǎn)換為自然語言中的機(jī)器翻譯問題,使用RNN 神經(jīng)網(wǎng)絡(luò)對自然語言查詢語句進(jìn)行向量表征。同時(shí)使用不同模型對代碼的不同粒度的信息進(jìn)行表征建模,使用RNN 模型對代碼的詞匯單元序列進(jìn)行表征,使用RNN 模型對代碼的方法名進(jìn)行表征,對代碼中的API 序列使用MLP 模型進(jìn)行表征。為了獲得三個(gè)向量中最顯著的特征,對這三個(gè)向量使用了最大池化操作,最后將這三個(gè)向量進(jìn)行拼接,作為代碼最終的向量表示。該工作使用了三元組損失函數(shù)(triplet loss),考慮了查詢語句、與查詢語句對應(yīng)的代碼(正例)、與查詢語句不對應(yīng)的代碼(負(fù)例)三者之間的關(guān)系,通過最大化正例之間相似度以及最小化負(fù)例之間的相似度來訓(xùn)練模型,使得模型能夠具備將不同源信息映射到同一語義向量空間中的能力。作者在Github 網(wǎng)站上收集了1 820 萬條帶有注釋的Java 方法代碼片段進(jìn)行實(shí)驗(yàn)。實(shí)驗(yàn)結(jié)果表明,該方法與其他傳統(tǒng)的基于文本匹配的方法相比,搜索的準(zhǔn)確度上取得了顯著的提升。Shuai 等人考慮到自然語言查詢與代碼之間的語義關(guān)系,提出了一種共同注意力表征學(xué)習(xí)模型,用于同時(shí)學(xué)習(xí)代碼與自然語言查詢語句之間的關(guān)聯(lián)表征。該方法構(gòu)建了一個(gè)代碼和查詢序列的相關(guān)矩陣,通過按行或者按列的最大池化操作獲得它們之間的語義關(guān)系。同樣的,在文獻(xiàn)[16]的數(shù)據(jù)集上進(jìn)行實(shí)驗(yàn),實(shí)驗(yàn)結(jié)果在平均排名倒數(shù)(mean reciprocal rank,MRR)指標(biāo)上得到了26.72%的提升。
基于應(yīng)用程序接口(application programming interface,API)序列的代碼表征方法是將代碼中的API 調(diào)用序列作為研究對象。當(dāng)開發(fā)者對所需要使用的開發(fā)框架或庫不熟悉時(shí),需要學(xué)習(xí)具體框架的API 使用方法,尤其是當(dāng)使用規(guī)模龐大的框架如JDK(Java development kit),成千上萬個(gè)API 大大增加了軟件開發(fā)難度。如果能夠通過輸入自然語言查詢語句獲得API 的使用方法,將能極大地提高軟件開發(fā)效率?,F(xiàn)有的與API 調(diào)用序列有關(guān)的工作將API 序列作為研究對象。對程序中包含的API 序列進(jìn)行建模,獲得API 序列的表征向量,之后通過對比自然語言和代碼API序列向量的相似度來完成API序列檢索。
Gu 等人提出了基于深度學(xué)習(xí)的API 檢索方法,該方法根據(jù)自然語言查詢語句來檢索與其匹配的API 序列。作者將API 檢索問題轉(zhuǎn)換為機(jī)器翻譯問題,通過使用Seq2seq 框架構(gòu)建一個(gè)自然語言到API 序列的模型。首先將自然查詢語句視為源語言,將自然語言查詢語句中的每一個(gè)單詞轉(zhuǎn)換為詞向量,之后使用RNN 模型構(gòu)建一個(gè)編碼器。該編碼器計(jì)算每個(gè)單詞的隱藏狀態(tài),并且根據(jù)前一個(gè)單詞的隱藏狀態(tài)去預(yù)測下一個(gè)可能的輸出單詞,最后得到上下文向量。同時(shí),也使用RNN 模型構(gòu)建一個(gè)解碼器,將編碼器所生成的上下文信息作為該解碼器的初始隱藏狀態(tài),每個(gè)時(shí)間步都會生成一個(gè)單詞作為API 調(diào)用序列的內(nèi)容。在模型訓(xùn)練中使用了束搜索(beam search)算法,以最小的代價(jià)來搜索最優(yōu)的API序列。該方法在其處理好的751 萬對數(shù)據(jù)上進(jìn)行實(shí)驗(yàn),利用BLEU(bilingual evaluation understudy)來評估生成的API 序列的質(zhì)量,結(jié)果表明該方法較其他方法取得了40%左右的提升。Lu 等人提出了一種用于惡意軟件行為分析的深度學(xué)習(xí)和機(jī)器學(xué)習(xí)組合模型。其中一部分分析了API 調(diào)用序列之間的依賴關(guān)系,另一部分采用基于殘差的雙向LSTM 神經(jīng)網(wǎng)絡(luò)獲取API 序列中的冗余信息。這種組合方式顯著提高了惡意軟件檢測精度。
Saifullah提出了一種使用帶注意力機(jī)制雙向LSTM 的模型,將代碼的詞匯信息、句法信息以及上下文語義信息相融合,用于生成API 調(diào)用序列。在整個(gè)過程中,作者首先對原始代碼進(jìn)行預(yù)處理,包括代碼中方法名的抽取、變量名稱規(guī)范化、參數(shù)規(guī)范化。在處理好的數(shù)據(jù)上使用基于注意力機(jī)制的雙向LSTM 編碼器進(jìn)行編碼,生成上下文信息向量,之后在該向量上使用解碼器進(jìn)行解碼,同時(shí)采用了束搜索策略,生成最優(yōu)的API調(diào)用序列。
在代碼注釋生成任務(wù)中,Hu 等人從代碼中抽取出API 序列以及摘要,使用Seq2seq 模型對兩者進(jìn)行預(yù)訓(xùn)練。將API 序列作為Seq2seq 模型的輸入,而代碼摘要作為Seq2seq 模型的訓(xùn)練目標(biāo),這一步驟使得生成的API 序列表征與摘要表征映射到相近的向量空間。同時(shí),該方法也采用了與CODE-NN 模型類似的結(jié)構(gòu)對代碼詞匯單元序列和摘要進(jìn)行訓(xùn)練,將這些知識輸入到網(wǎng)絡(luò)模型中輔助代碼注釋的生成過程。該工作的創(chuàng)新點(diǎn)在于將預(yù)先構(gòu)建的Seq2seq 模型對API 序列進(jìn)行預(yù)訓(xùn)練,并且將該預(yù)訓(xùn)練模型中的編碼器部分作為后面模型的編碼器,通過將其拼接到代碼token 序列和摘要訓(xùn)練模型的解碼器上生成代碼摘要。該工作取得了當(dāng)時(shí)最優(yōu)的結(jié)果。
抽象語法樹(AST)是源代碼的抽象語法結(jié)構(gòu)的樹狀表示,可以有效地表示程序的語法及其結(jié)構(gòu)。抽象語法樹并沒有包含代碼中所有的真實(shí)語法信息,它忽略了一些不重要的細(xì)節(jié),只保留了必要的節(jié)點(diǎn)信息。樹中的每個(gè)節(jié)點(diǎn)都對應(yīng)源代碼中的一種結(jié)構(gòu)。在抽象語法樹中,非終結(jié)符對應(yīng)于樹的中間節(jié)點(diǎn)(如Assign、If、For 等),與代碼片段的具體類型相對應(yīng),與程序結(jié)構(gòu)緊密相關(guān);而終結(jié)符則位于樹的葉子節(jié)點(diǎn)(如字符串、變量名、方法名等),與程序語義密切相關(guān)。抽象語法樹可以被用于構(gòu)建代碼優(yōu)化插件,如對代碼進(jìn)行語法檢查,對代碼進(jìn)行格式化,對代碼編寫風(fēng)格進(jìn)行檢查等。不同的編程語言有不同的抽象語法樹構(gòu)建工具,表1 列出的是目前流行抽象語法樹構(gòu)建工具。
表1 不同編程語言的抽象語法樹生成工具Table 1 Tools of AST generation for different programming languages
利用深度神經(jīng)網(wǎng)絡(luò)對抽象語法樹進(jìn)行建模得到其向量表示,根據(jù)該特征向量完成代碼模式檢測任務(wù)。在克隆檢測任務(wù)中,White 等人提出了基于循環(huán)神經(jīng)網(wǎng)絡(luò)的代碼克隆檢測方法,該方法將代碼分為詞匯以及句法兩個(gè)層次。對于詞匯級別的信息,作者在代碼的詞匯單元序列上使用RNN 神經(jīng)網(wǎng)絡(luò)進(jìn)行建模。而對于代碼的句法級別的信息,作者首先將代碼轉(zhuǎn)換為其對應(yīng)的抽象語法樹結(jié)構(gòu),之后將抽象語法樹轉(zhuǎn)換為其對應(yīng)的滿二叉樹,最后作者將滿二叉樹轉(zhuǎn)換為Olive Trees,并在其上使用另一個(gè)RNN 神經(jīng)網(wǎng)絡(luò)進(jìn)行建模。該文將這兩個(gè)特征相結(jié)合作為整個(gè)程序的特征向量,根據(jù)該向量進(jìn)行代碼克隆檢測任務(wù)。Mou 等人提出了一種基于樹的卷積神經(jīng)網(wǎng)絡(luò)模型(tree-based convolutional neural network,TBCNN)。該模型考慮到不同代碼對應(yīng)的抽象語法樹是不同的這一現(xiàn)象,采用了“連續(xù)二叉樹”的概念,直接在代碼所對應(yīng)的抽象語法樹上進(jìn)行卷積操作。在卷積操作之后獲得了不同數(shù)目的AST 結(jié)構(gòu)特征向量,由于數(shù)目不同不能直接作為神經(jīng)網(wǎng)絡(luò)的輸入,因此該方法還采用“動(dòng)態(tài)池化”技術(shù),最終將數(shù)目不同的特征向量轉(zhuǎn)換為了一個(gè)向量。TBCNN 是一個(gè)通用的代碼表征生成模型,所生成的向量能夠包含代碼片段中特有的代碼模式,因而可以應(yīng)用于不同的代碼分析任務(wù)中。如文中使用所生成的代碼表征向量用于代碼功能分類任務(wù),在POJ104數(shù)據(jù)集上(由C 語言編寫的104 個(gè)任務(wù)的代碼數(shù)據(jù)集),分類任務(wù)的準(zhǔn)確率能夠達(dá)到94%。Wei等人提出了CDLH(clone detection with learning to Hash)方法,該方法在抽象語法樹的基礎(chǔ)上使用LSTM 模型,通過共享權(quán)重的方式學(xué)習(xí)兩個(gè)代碼片段之間的表征向量,這種模型使其能夠檢測出第四類代碼克隆類型。Chen等人同樣采用了基于樹的卷積神經(jīng)網(wǎng)絡(luò),該方法考慮到已有的方法僅僅使用抽象語法樹會造成代碼語義信息的丟失,因此該方法將API 的調(diào)用信息作為補(bǔ)充信息合并到抽象語法樹中,之后進(jìn)行代碼表征。在POJ104 和BCB數(shù)據(jù)集上進(jìn)行實(shí)驗(yàn),在F1 指標(biāo)上獲得了0.39 和0.12 的提升。Zhang 等人提出了一種基于抽象語法樹的神經(jīng)網(wǎng)絡(luò)代碼表征方法,該方法將代碼轉(zhuǎn)換為其對應(yīng)的抽象語法樹,不同于傳統(tǒng)的基于抽象語法樹的代碼表征方法,該方法對抽象語法樹進(jìn)行語句級別上的切割,將完整的抽象語法樹分割為多個(gè)語句樹。針對每個(gè)語句樹,該方法設(shè)計(jì)了語句編碼器用于將語句樹轉(zhuǎn)換為對應(yīng)的語句表征向量,通過使用雙向GRU(gated recurrent unit)神經(jīng)網(wǎng)絡(luò)對語句向量進(jìn)行建模,對雙向GRU 層輸出的隱含狀態(tài)向量進(jìn)行最大池化(max-pooling)操作,以獲得最顯著的代碼特征。其中,將抽象語法樹分割為多個(gè)語句樹變相地緩解了由于抽象語法樹規(guī)模過大所帶來的長依賴問題。該方法所生成的代碼表征向量被應(yīng)用于代碼克隆檢測任務(wù)中,在BCB 數(shù)據(jù)集和POJ104 數(shù)據(jù)集上取得了當(dāng)時(shí)最好的檢測結(jié)果。Wang 等人同樣考慮了僅僅使用代碼的抽象語法樹進(jìn)行代碼表征建模實(shí)際上仍然有代碼結(jié)構(gòu)上的缺失這一問題,構(gòu)建了代碼抽象語法樹的圖形表示,通過將抽象語法樹各個(gè)葉子結(jié)點(diǎn)相連構(gòu)建出適合圖神經(jīng)網(wǎng)絡(luò)處理的數(shù)據(jù)。上述方法均在抽象語法樹上進(jìn)行代碼的表征學(xué)習(xí),力圖充分提取代碼中的結(jié)構(gòu)信息與語法信息。但是為了獲得代碼的結(jié)構(gòu)信息,這些方法首先都是將代碼轉(zhuǎn)換為對應(yīng)的抽象語法樹,才能夠?qū)⑵渥鳛樯疃葘W(xué)習(xí)模型的輸入,同時(shí)由于深度學(xué)習(xí)模型本身的局限性,如循環(huán)神經(jīng)網(wǎng)絡(luò)僅能處理序列數(shù)據(jù),還需要將樹形數(shù)據(jù)轉(zhuǎn)換為序列數(shù)據(jù),而在這個(gè)過程中必定存在結(jié)構(gòu)信息的丟失。
Hu 等人提出了基于AST 的代碼注釋生成模型,該方法同樣是將代碼轉(zhuǎn)換為其對應(yīng)的抽象語法樹,同時(shí)為了能夠?qū)⒊橄笳Z法樹作為神經(jīng)網(wǎng)絡(luò)模型的輸入,作者還提出了一種基于抽象語法樹的結(jié)構(gòu)遍歷方法,進(jìn)而將抽象語法樹轉(zhuǎn)換為節(jié)點(diǎn)序列。并且作者也考慮了超出詞表問題,當(dāng)抽象語法樹的節(jié)點(diǎn)類型和值不在預(yù)先定義的詞表中時(shí),使用節(jié)點(diǎn)的類型來替換該詞。最后,使用基于注意力機(jī)制的Seq2seq 框架完成注釋生成任務(wù)。Alon 等人利用抽象語法樹路徑(AST path)來表示程序,通過AST 路徑來表示程序的結(jié)構(gòu)和語法。該方法首先將代碼轉(zhuǎn)換為對應(yīng)的抽象語法樹,從抽象語法樹中提取不同的路徑信息,將路徑上所有結(jié)點(diǎn)的值構(gòu)成的元組稱為路徑上下文信息(path-context)。將每個(gè)路徑上下文信息中的結(jié)點(diǎn)進(jìn)行嵌入得到節(jié)點(diǎn)向量,之后將這些向量拼接成一個(gè)向量來代表這個(gè)路徑上下文信息。同時(shí)作者還考慮使用注意力機(jī)制關(guān)注不同權(quán)重的路徑信息。該方法在變量名預(yù)測、方法名預(yù)測以及表達(dá)式類型預(yù)測任務(wù)中都取得了目前最好的結(jié)果。之后Alon 等人又對該模型進(jìn)行了改進(jìn),使用雙向LSTM 對抽象語法樹的路徑信息進(jìn)行建模,將該模型應(yīng)用于代碼摘要任務(wù)中。Alon 等人還在代碼補(bǔ)全任務(wù)上同樣使用基于抽象語法樹路徑信息的模型,該模型通過估計(jì)抽象語法樹上不同節(jié)點(diǎn)的條件概率來預(yù)測下一個(gè)節(jié)點(diǎn)的信息,進(jìn)而完成代碼補(bǔ)全任務(wù)。
圖的結(jié)構(gòu)可以對程序中的數(shù)據(jù)流動(dòng)關(guān)系進(jìn)行建模,例如控制流圖以及數(shù)據(jù)依賴圖。圖中節(jié)點(diǎn)表示程序中的元素,例如程序中的token 或變量、程序中指針(pointer)的內(nèi)存地址,圖中的邊表示節(jié)點(diǎn)之間的依賴關(guān)系或數(shù)據(jù)流動(dòng)情況。代碼的控制流圖反映了代碼中語句執(zhí)行時(shí)的跳轉(zhuǎn)流向,而代碼的數(shù)據(jù)依賴圖反映了代碼中數(shù)據(jù)的流向。通過將程序表示為圖的形式使得模型能夠更好地理解代碼中不同部分之間的依賴關(guān)系。
隨著深度學(xué)習(xí)的發(fā)展,基于深度神經(jīng)網(wǎng)絡(luò)的圖模型被用于代碼理解相關(guān)任務(wù)中,包括代碼風(fēng)格修正、代碼修復(fù)、代碼注釋生成等。Allamanis 等人考慮到代碼中的長依賴問題,如在代碼中變量的定義位置與使用位置之間的距離問題,提出了基于圖的代碼表征方法,力圖學(xué)習(xí)代碼中的語法以及語義結(jié)構(gòu)。該工作首先將代碼轉(zhuǎn)換為對應(yīng)的抽象語法樹,之后通過不同的連接規(guī)則連接抽象語法樹各個(gè)節(jié)點(diǎn),獲得了不同節(jié)點(diǎn)之間的關(guān)聯(lián)關(guān)系,這其中就包含了變量之間的依賴關(guān)系。最后將構(gòu)建好的代碼圖數(shù)據(jù)作為輸入,輸入到圖神經(jīng)網(wǎng)絡(luò)中進(jìn)行表征學(xué)習(xí)。該方法在程序變量命名以及變量名誤用檢測任務(wù)中取得了不錯(cuò)的效果。
Lu 等人從代碼中提取數(shù)據(jù)流與函數(shù)調(diào)用信息,將其融合到抽象語法樹中,從而將代碼構(gòu)建為一個(gè)包含豐富信息的圖結(jié)構(gòu)表示。在傳統(tǒng)的GGNN(gated graph sequence neural networks)模型上引入了注意力機(jī)制,用于獲得圖中每個(gè)節(jié)點(diǎn)的重要程度,進(jìn)而獲得更具有區(qū)分度的代碼表征向量。所生成的代碼表征向量用于代碼功能分類任務(wù)中,實(shí)驗(yàn)結(jié)果表明,該方法能夠有效地應(yīng)用于代碼分類任務(wù)中。
Brockschmidt 等人同樣在代碼的抽象語法樹上增加相應(yīng)的邊以構(gòu)建代碼圖,代碼圖的構(gòu)建方法與文獻(xiàn)[50]類似。之后采用圖神經(jīng)網(wǎng)絡(luò)對程序的結(jié)構(gòu)和數(shù)據(jù)流進(jìn)行建模完成程序自動(dòng)生成任務(wù)。Ben-Nun 等人提出了一種與語言以及平臺無關(guān)的代碼表征方法inst2vec。該方法首先使用編譯器對代碼進(jìn)行編譯,得到代碼的中間表示。但由于該中間表示并沒有包含代碼之中的數(shù)據(jù)流信息以及控制流信息,因此該方法將數(shù)據(jù)流和控制流也融合到該中間表示中,進(jìn)而構(gòu)建了代碼上下文流圖。最后在所構(gòu)建的圖上使用循環(huán)神經(jīng)網(wǎng)絡(luò)進(jìn)行建模,獲得代碼的表征向量。該向量在程序分類實(shí)驗(yàn)中準(zhǔn)確率取得了當(dāng)時(shí)最好的效果。這些基于圖的方法本質(zhì)上是通過對代碼的抽象語法樹的改造,顯式地將不同節(jié)點(diǎn)之間的聯(lián)系以圖的形式表現(xiàn)出來,這一過程能夠增加一些人工歸納的規(guī)則,因此對一些代碼分析任務(wù)能夠取得較好的效果。
對代碼表征的介紹離不開具體的應(yīng)用場景,對代碼表征的質(zhì)量進(jìn)行評價(jià)通常也是基于具體的代碼分析任務(wù)。因此,本章將對主要的幾個(gè)代碼分析任務(wù)進(jìn)行簡要介紹,并詳細(xì)介紹代碼表征在這些任務(wù)中的具體工作。
在軟件開發(fā)過程中,對代碼的修改、復(fù)用、變換以及重構(gòu)會使得軟件系統(tǒng)中出現(xiàn)大量的相似甚至重復(fù)的代碼片段,這個(gè)代碼復(fù)用的過程就被稱為代碼克隆。已有研究表明,代碼克隆這一現(xiàn)象在軟件系統(tǒng)中是廣泛存在的。如在FreeBSD 5.2.1 和Linux 2.6.6 系統(tǒng)中,克隆的代碼片段分別占比20.4%和22.3%。在軟件開發(fā)過程中,通過復(fù)制和粘貼現(xiàn)有的代碼雖然可以在一定程度上提高軟件開發(fā)的速度,但這些克隆的代碼同樣也對軟件系統(tǒng)的維護(hù)帶來了巨大的挑戰(zhàn)。當(dāng)被克隆的代碼片段存在缺陷時(shí),該缺陷將會隨著其克隆活動(dòng)的過程傳播到軟件系統(tǒng)中,將缺陷引入了軟件系統(tǒng)中,會對軟件系統(tǒng)的穩(wěn)定性造成破壞,進(jìn)而提高軟件系統(tǒng)的維護(hù)成本。而代碼克隆檢測技術(shù)能夠幫助軟件開發(fā)者發(fā)現(xiàn)軟件系統(tǒng)中相似的代碼片段,提升軟件系統(tǒng)的穩(wěn)定性,降低軟件系統(tǒng)維護(hù)人員的工作量,避免引入與克隆代碼相關(guān)的缺陷。
目前已有的代碼克隆檢測方法大多遵循以下思路:(1)首先對代碼片段進(jìn)行預(yù)處理;(2)對處理好的代碼片段進(jìn)行信息抽取,將其轉(zhuǎn)換為中間表征;(3)根據(jù)表征的方式不同計(jì)算不同代碼片段之間的相似度,完成克隆檢測任務(wù)。在這些步驟中,最重要的問題是如何將代碼轉(zhuǎn)換為合適的表征,使該表征盡可能多地保留原始代碼中所包含的信息,以便進(jìn)行代碼相似度計(jì)算。
本節(jié)根據(jù)上文對代碼表征方法的分類介紹,從代碼表征的角度,將代碼克隆檢測方法分為兩類:(1)基于代碼序列的克隆檢測;(2)基于代碼結(jié)構(gòu)的克隆檢測。根據(jù)代碼克隆相似程度的不同,Bellon 等人將代碼克隆分為四種類型,即完全相同的代碼(Type-1)、重命名的代碼(Type-2)、幾乎相同的代碼(Type-3)和語義相似的代碼(Type-4),從Type-1 到Type-4,代碼克隆的相似程度逐漸降低,檢測的難度也逐漸增加。在普通情況下,僅對代碼的文本序列信息進(jìn)行檢測,就能夠有效地檢測出大部分Type-1、Type-2 的代碼克隆以及少部分Type-3 代碼克隆,而對于Type-4 類型的代碼克隆,則需要基于代碼結(jié)構(gòu)的克隆檢測方法才能夠檢測到。以下將對不同的克隆檢測方法進(jìn)行介紹。
基于代碼序列的代碼克隆檢測,將代碼視為自然語言文本進(jìn)行處理。通常使用如后綴樹匹配、最長相同序列匹配等匹配算法進(jìn)行相似度計(jì)算。CCFinder、CP-Miner將原始的代碼通過移除空格、注釋,進(jìn)行參數(shù)轉(zhuǎn)換等預(yù)處理步驟之后,直接在其上進(jìn)行相似度計(jì)算。Duploc將代碼視為基本的字符串,通過檢測兩份代碼最長的相同子序列進(jìn)行相似度比較,從而判斷代碼的克隆程度。SDD是eclipse開發(fā)工具中的一個(gè)插件,該方法將代碼視為純文本,通過使用倒排索引和N 近鄰算法減少克隆檢測時(shí)間的開銷。這種檢測方法的優(yōu)點(diǎn)在于其檢測速度快,計(jì)算開銷小,不依賴于特定的編程語言,甚至對于無法運(yùn)行的不完整代碼也能夠進(jìn)行克隆檢測。但其缺點(diǎn)也很明顯,由于這類方法僅僅從文本序列的角度出發(fā),沒有考慮代碼本身所包含的豐富的語法、語義信息,因此對于Type-3、Type-4 的代碼克隆檢測能力不足。還有一些基于代碼文本序列的克隆檢測方法,如CDSW、XIAO、CCAlingner、srcClone將源代碼轉(zhuǎn)換為其對應(yīng)的哈希值,之后直接使用哈希比較算法計(jì)算代碼之間的相似程度。與單純地將代碼視為文本的克隆檢測方法相比,由于這類方法使用了更符合編譯原理的符號序列進(jìn)行相似度計(jì)算,這些方法的克隆檢測效果有一定程度的提升。但是,這種檢測方法沒有考慮代碼中所包含的結(jié)構(gòu)信息,因此檢測不到代碼的結(jié)構(gòu)信息層面上的變化。
基于代碼結(jié)構(gòu)的克隆檢測方法通常是根據(jù)語法規(guī)則,將代碼轉(zhuǎn)換為對應(yīng)的抽象語法樹,進(jìn)而進(jìn)行檢測分析。抽象語法樹是源代碼編譯過程中產(chǎn)生的一個(gè)中間表示,將源代碼中所包含的語法信息以樹的形式表現(xiàn)出來,之后采用基于樹的相似度匹配算法計(jì)算代碼之間的相似度。目前,許多工作都嘗試從抽象語法樹中提取更加豐富有效的代碼特征,將抽象語法樹轉(zhuǎn)換為不同的表征向量,在這些代碼表征向量上進(jìn)行代碼克隆檢測。由代碼的抽象語法樹所生成的代碼表征向量,不但包含了代碼中的token 信息,同時(shí)也包含了代碼的結(jié)構(gòu)信息、語法規(guī)則,因而這類方法具有對Type-3、Type-4 克隆類型的檢測能力。Zhang 等人將代碼轉(zhuǎn)換為其對應(yīng)的抽象語法樹,并對抽象語法樹進(jìn)一步劃分,得到代碼的語句樹。通過在語句樹級別上的建模,獲得了代碼表征向量。該表征向量被用于代碼克隆檢測,檢測結(jié)果遠(yuǎn)超于僅使用文本序列的檢測方法。但是基于樹的方法通常需要遍歷樹,其計(jì)算開銷會大于基于代碼序列的克隆檢測方法。
表2 展示的是近五年來基于深度學(xué)習(xí)的一些代碼克隆檢測工作。從中可以看出,使用深度學(xué)習(xí)技術(shù)能夠有效地進(jìn)行Type-3、Type-4 類型的代碼克隆檢測。這些方法使用不同的神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)進(jìn)行代碼的特征提取。在此表中所使用的深度學(xué)習(xí)技術(shù)中序列神經(jīng)網(wǎng)絡(luò)占比較大,這說明主流的研究方向仍然是將代碼作為序列進(jìn)行處理。但是從此表中也可以看出,目前代碼克隆檢測模型也僅僅是關(guān)注于少部分語言,如Java 和C。造成這一現(xiàn)象的主要原因是當(dāng)前代碼克隆檢測領(lǐng)域僅有少量的標(biāo)準(zhǔn)數(shù)據(jù)集。
表2 基于深度學(xué)習(xí)的代碼克隆檢測方法Table 2 Deep learning-based code clone detection methods
隨著近幾年開源軟件和開源社區(qū)的發(fā)展,越來越多企業(yè)和個(gè)人在開源社區(qū)貢獻(xiàn)出軟件代碼。Singer 等人的一項(xiàng)研究發(fā)現(xiàn),軟件開發(fā)人員在軟件開發(fā)過程中最頻繁的活動(dòng)之一就是進(jìn)行代碼搜索。當(dāng)軟件開發(fā)者在實(shí)現(xiàn)某個(gè)特定功能的代碼需要調(diào)用已有的應(yīng)用程序接口(API)時(shí),如果開發(fā)者對其的使用方式不太了解,就需要進(jìn)行代碼搜索。通過在搜索引擎中鍵入API 的名稱等信息,可以搜索到該API 的具體使用方式。又或者,軟件開發(fā)者甚至都不清楚哪些API 能夠?qū)崿F(xiàn)他所需要完成的功能,也可以通過在搜索引擎中鍵入功能描述來搜索到可以滿足開發(fā)需求的API,甚至完整的代碼。通常情況下,軟件開發(fā)者會使用搜索引擎(如百度、谷歌)進(jìn)行代碼的搜索。但這些通用的搜索引擎不是專門針對編程任務(wù)所開發(fā)的搜索引擎,在搜索過程中不會考慮代碼的語義特征,當(dāng)所需要搜索的代碼功能比較復(fù)雜時(shí),搜索效果較差。因此,當(dāng)通用的搜索引擎不能滿足開發(fā)者的需求時(shí),開發(fā)者會在一些專業(yè)的代碼問答網(wǎng)站(https://sourceforge.net/、https://stackoverflow.com/)或者開源社區(qū)(https://www.github.com/)上進(jìn)行搜索。然而這些網(wǎng)站所返回的搜索結(jié)果通常與所想要解決的問題相關(guān)度較低,盡管代碼片段中包含了查詢語句中相關(guān)的詞匯,但是所返回的結(jié)果仍然無法完成開發(fā)者的開發(fā)需求。
現(xiàn)有的代碼搜索技術(shù),通常是將源代碼視為純文本,直接利用信息檢索領(lǐng)域中的檢索模型去查找并匹配相關(guān)的代碼片段。這些方法主要是通過比較自然語言查詢語句和源代碼之間的文本相似性進(jìn)行搜索。然而因?yàn)槠鋬H僅考慮文本相似性,而沒有考慮語義或者功能相似性,通常搜索返回的結(jié)果不盡人意。隨著計(jì)算機(jī)技術(shù)的快速發(fā)展,基于數(shù)據(jù)驅(qū)動(dòng)的機(jī)器學(xué)習(xí)方法得到了廣泛的關(guān)注。有研究對源代碼進(jìn)行統(tǒng)計(jì)分析,發(fā)現(xiàn)源代碼與自然語言有著相似的統(tǒng)計(jì)學(xué)規(guī)律。因此,對源代碼的分析開始借鑒自然語言處理領(lǐng)域的相關(guān)方法,如通過計(jì)算代碼之間的文本表征向量來計(jì)算代碼之間的相似度,對代碼進(jìn)行摘要提取、代碼的跨語言翻譯等。這種研究方式極大地推動(dòng)了代碼數(shù)據(jù)分析和挖掘的研究進(jìn)程。
代碼搜索和自然語言處理領(lǐng)域相結(jié)合,通常的做法是將自然語言查詢語句和源代碼轉(zhuǎn)換為同一個(gè)向量空間的連續(xù)向量,并計(jì)算兩者的相似程度。這類方式依賴于連續(xù)向量所能蘊(yùn)含的上下文隱含語義信息量的多少。Gu 等人提出了CODEnn 模型,該模型沒有使用傳統(tǒng)的文本相似度匹配算法。它將自然語言描述語句和源代碼片段共同映射到同一個(gè)高維度的向量空間中,使得描述語句和代碼具有相似的向量表示。之后,通過計(jì)算自然語言查詢語句向量和代碼向量之間的余弦相似度來返回合適的代碼片段。Sachdev等人提出了NCS(neural code search),該方法的主要思想是使用連續(xù)向量來獲得代碼的語義信息。在方法級別上將代碼劃分為不同的代碼片段,并對每個(gè)代碼片段生成連續(xù)的向量嵌入。同時(shí),將自然語言查詢也映射到相同的向量空間中,通過計(jì)算向量之間的距離來衡量代碼之間的相關(guān)程度,以供查詢。Lv 等人提出了CodeHow,該模型是一個(gè)包含擴(kuò)展布爾模型和API 匹配的代碼搜索工具。首先從代碼的在線文檔中收集每個(gè)API 的描述,獲得API 的描述之后,對其進(jìn)行編碼并計(jì)算文本描述與查詢之間的相似性,以及API 名稱與查詢之間的相似性。最后組合每個(gè)API 對之間的相似性,返回與查詢匹配的可能相關(guān)API。Fang 等人與文獻(xiàn)[16]采取了相似的做法,將代碼拆分成方法名、API、詞匯單元三部分,將這三部分轉(zhuǎn)換為對應(yīng)的向量,并在其上使用自注意力機(jī)制,獲得各個(gè)部分內(nèi)部元素的注意力分?jǐn)?shù)附加在表征向量中,同時(shí)將代碼的描述文本也用同樣的方式轉(zhuǎn)換為向量,最后使用余弦相似度來衡量代碼與代碼描述語言之間的相似度,返回代碼搜索結(jié)果。
有許多研究也關(guān)注于代碼本身所具有的特殊性,通過將代碼轉(zhuǎn)換為對應(yīng)的抽象語法樹,或者程序依賴圖,使用深度學(xué)習(xí)技術(shù)獲得這些特殊結(jié)構(gòu)所包含的代碼信息。與基于文本的嵌入技術(shù)類似,這類方法通常將項(xiàng)目中的代碼轉(zhuǎn)換為代碼圖,之后使用圖嵌入技術(shù)用于表征代碼圖中節(jié)點(diǎn)信息。之后根據(jù)代碼圖解析查詢語句,將查詢語句的關(guān)鍵詞與代碼圖中的候選節(jié)點(diǎn)進(jìn)行匹配,生成子圖并推薦搜索結(jié)果。Gu 等人首先對代碼對應(yīng)的抽象語法樹進(jìn)行簡化,之后在其上引入了樹型序列化方法,將抽象語法樹轉(zhuǎn)換為詞匯單元序列,進(jìn)行多模態(tài)的代碼搜索。Meng設(shè)計(jì)了一種抽象語法樹解析算法,能夠在將代碼轉(zhuǎn)換為向量的過程中保留代碼的詞法特征和結(jié)構(gòu)特征。Xu 等人提出了TabCS 模型,在代碼的文本特征、代碼對應(yīng)的抽象語法樹結(jié)構(gòu)特征以及查詢語句上同時(shí)使用注意力機(jī)制,捕獲它們之間的語義相關(guān)性。Zou 等人通過使用圖嵌入技術(shù),將軟件項(xiàng)目源代碼轉(zhuǎn)換為代碼圖。這類方法可以有效地表示軟件代碼圖的深層結(jié)構(gòu)信息。
代碼補(bǔ)全是自動(dòng)化軟件開發(fā)的重要功能之一,能夠有效地提高軟件開發(fā)者的工作效率。代碼補(bǔ)全技術(shù)基于開發(fā)人員的輸入,實(shí)時(shí)預(yù)測待補(bǔ)全代碼中的類名、方法名、變量名等,通過這種方式能夠有效減輕開發(fā)者的鍵入負(fù)擔(dān),減少拼寫錯(cuò)誤,進(jìn)而提高開發(fā)效率。但是,早期的代碼補(bǔ)全工具不能很好地滿足開發(fā)人員的需求,這些工具常常忽略了編程語言的語法規(guī)則,所提供的代碼補(bǔ)全列表仍然需要進(jìn)行人工修正。并且,這些代碼補(bǔ)全工具通常只利用已有的代碼和語法規(guī)則,很少考慮待補(bǔ)全代碼與上文之間的語義關(guān)聯(lián)。Bruch 等人就提出了智能代碼補(bǔ)全技術(shù),從已有的代碼庫中挖掘更多的有效信息用于代碼補(bǔ)全。
代碼補(bǔ)全技術(shù)將待補(bǔ)全位置的代碼的上下文信息和代碼庫中學(xué)習(xí)到的代碼上下文信息進(jìn)行關(guān)聯(lián),通過計(jì)算兩者之間的相似度,推薦相似度高的代碼片段作為代碼補(bǔ)全推薦結(jié)果。根據(jù)代碼補(bǔ)全的對象,可以將代碼補(bǔ)全技術(shù)分為:(1)標(biāo)識符補(bǔ)全;(2)代碼片段補(bǔ)全;(3)關(guān)鍵詞、縮略詞補(bǔ)全。標(biāo)識符補(bǔ)全指的是根據(jù)已有的代碼,對不完整的標(biāo)識符進(jìn)行補(bǔ)全。一般補(bǔ)全的對象包括方法名、變量名、參數(shù)名等。代碼片段補(bǔ)全指的是對于語句空缺的代碼片段,使用代碼補(bǔ)全工具自動(dòng)地生成符合編程語言規(guī)范的語句用于補(bǔ)全。關(guān)鍵詞、縮略詞補(bǔ)全指的是對于輸入的簡短的詞匯,將其補(bǔ)全為完整的函數(shù)或參數(shù)。但是由于所輸入的詞匯所能包含的信息量較少,通常情況下這種補(bǔ)全方法的補(bǔ)全結(jié)果較差。
雖然對于代碼補(bǔ)全技術(shù)的研究在近十幾年中取得了一定的成果,但是在實(shí)際的開發(fā)應(yīng)用中仍然面臨以下幾個(gè)問題需要解決:(1)缺少統(tǒng)一的模型評估指標(biāo)。目前已有的與代碼補(bǔ)全有關(guān)的評估指標(biāo)大多都是機(jī)器學(xué)習(xí)領(lǐng)域的通用指標(biāo),如準(zhǔn)確率、召回率、平均倒數(shù)排名等。目前大部分文獻(xiàn)僅采用其中一種指標(biāo)用于模型的評估,因此對于不同的代碼補(bǔ)全方法的性能難以進(jìn)行比較。(2)缺少真實(shí)的基準(zhǔn)數(shù)據(jù)集。在機(jī)器學(xué)習(xí)領(lǐng)域,對模型性能的評估不但與評價(jià)標(biāo)準(zhǔn)有關(guān),還與所使用的基準(zhǔn)數(shù)據(jù)集有關(guān)。目前代碼補(bǔ)全的文獻(xiàn)中所使用的數(shù)據(jù)集大多是研究者自行爬取的數(shù)據(jù)集,缺少一個(gè)公認(rèn)的、合理的、真實(shí)的數(shù)據(jù)集用于評估代碼補(bǔ)全模型的效果。
隨著近幾年嵌入技術(shù)在自然語言處理領(lǐng)域中獲得的成功,越來越多的代碼分析工作也開始采用類似的方法對代碼分析領(lǐng)域中尚未解決的問題進(jìn)行研究。如本文中描述的代碼表征研究方向,在目前的代碼分析工作中有著重要的地位。其中的詞嵌入技術(shù)和分布式假說賦予了深度學(xué)習(xí)工具強(qiáng)大的表征能力。使用神經(jīng)網(wǎng)絡(luò)技術(shù),能夠?qū)⒋a所特有的語義信息和結(jié)構(gòu)信息特征轉(zhuǎn)換為計(jì)算機(jī)擅長處理的實(shí)數(shù)向量形式。在此過程中無需進(jìn)行復(fù)雜的特征工程分析,減少人為操作所帶來的信息損失。但是在現(xiàn)有的代碼表征工作中,仍然存在以下幾點(diǎn)困難尚未解決,包括代碼庫中存在的超出詞表問題、表征模型的構(gòu)建方式以及模型所生成的代碼表征的質(zhì)量評價(jià)標(biāo)準(zhǔn)等。接下來本章將對這些問題進(jìn)行詳細(xì)分析。
當(dāng)前代碼分析文獻(xiàn)大都根據(jù)代碼文本中所存在的自然性這一特征進(jìn)行具體的代碼分析任務(wù),如代碼補(bǔ)全、代碼克隆檢測這類任務(wù)都是將代碼視為自然語言文本進(jìn)行處理。如本文背景知識部分所述,當(dāng)使用自然語言技術(shù)對代碼文本進(jìn)行處理時(shí),由于軟件開發(fā)人員在編寫代碼時(shí)可以自由地創(chuàng)建標(biāo)識符,這一過程將會產(chǎn)生一個(gè)規(guī)模巨大且稀疏的代碼詞表。代碼詞表的規(guī)模會直接影響代碼分析任務(wù)的效率。在神經(jīng)網(wǎng)絡(luò)模型訓(xùn)練的過程中,通常的做法是對這個(gè)大詞表中的詞匯單元個(gè)數(shù)進(jìn)行數(shù)量的限制,因此當(dāng)某個(gè)詞匯在詞表中沒有出現(xiàn)過,那么神經(jīng)網(wǎng)絡(luò)模型將無法對其進(jìn)行處理。這就是代碼分析中的超出詞表問題(OoV)。
在自然語言處理領(lǐng)域,對于超出詞表問題已有很多相關(guān)文獻(xiàn)。這些方法將完整的詞匯單元拆分為能夠組合成完整詞匯的子詞匯單元,在自然語言分析領(lǐng)域中取得了一定的成果。在代碼分析領(lǐng)域中,同樣也有對超出詞表問題進(jìn)行研究的一些工作。文獻(xiàn)[126],該工作本質(zhì)上是使用當(dāng)前自然語言處理領(lǐng)域中字節(jié)對編碼(byte pair encoder,BPE)方法對大型代碼庫進(jìn)行分析,并未考慮代碼本身所具有的特性,如代碼的局部重復(fù)性、標(biāo)識符的整體性。文獻(xiàn)[127]研究了代碼中的超出詞表問題對之后的機(jī)器學(xué)習(xí)模型性能的影響,發(fā)現(xiàn)詞表的大小、輸入數(shù)據(jù)中超出詞表詞匯的占比對機(jī)器學(xué)習(xí)模型的影響是很大的。文獻(xiàn)[118]對代碼詞匯單元的拆分方式如圖4 所示,若是將setter 變量名拆分為set、ter 兩部分,將直接丟失其作為一個(gè)完整變量的信息。
圖4 Java 代碼的詞匯單元與子詞匯單元Fig.4 Word and subword units of Java code
因此,如何處理代碼的超出詞表問題,減輕其對后續(xù)代碼分析任務(wù)的影響亦是一個(gè)重點(diǎn)的研究方向。本文認(rèn)為可以從以下幾點(diǎn)進(jìn)行研究:(1)衡量詞庫中詞匯單元的信息量,剔除信息量小的詞匯單元;(2)構(gòu)建特征提取能力強(qiáng)的神經(jīng)網(wǎng)絡(luò)模型,從字符層面對代碼信息進(jìn)行提取。
本文之前所列舉的代碼表征工作中,均是以結(jié)合具體的代碼分析任務(wù)進(jìn)行介紹。這是因?yàn)檫@些工作均是以具體的代碼分析任務(wù)進(jìn)行表征模型的設(shè)計(jì)。如代碼克隆檢測、代碼分類這種依賴于代碼模式特征的任務(wù)。對代碼補(bǔ)全任務(wù),應(yīng)盡可能多地提取代碼的結(jié)構(gòu)信息。對于代碼遷移任務(wù),則通常需要采用序列神經(jīng)網(wǎng)絡(luò)對代碼進(jìn)行建模。在自然語言分析領(lǐng)域中,存在諸如BERT(bidirectional encoder representation from transformers)、GPT(generative pre-training)、ELMo(embeddings from language models)這類通用的預(yù)訓(xùn)練模型,能夠直接應(yīng)用于大多數(shù)的自然語言分析任務(wù)。而在代碼分析領(lǐng)域中,尚缺少對于預(yù)訓(xùn)練模型應(yīng)用于代碼分析任務(wù)的工作?,F(xiàn)有的生成代碼表征的模型通常都是有監(jiān)督學(xué)習(xí)模型,即所使用代碼表征模型都是基于具體的代碼分析任務(wù)構(gòu)建的。這從根本上使得所學(xué)習(xí)得到的代碼表征向量不能適用于多種任務(wù)。如目前在代碼表征領(lǐng)域影響較高的Code2vec 工作,其生成的代碼表征向量也被證明不能直接簡單地應(yīng)用于代碼分析任務(wù)中。對于如何構(gòu)造一個(gè)具有更強(qiáng)泛化能力的代碼表征模型本文有以下幾個(gè)觀點(diǎn):(1)將傳統(tǒng)的代碼分析工作中代碼特征提取部分融合進(jìn)代碼表征模型的構(gòu)建過程。(2)增加模型訓(xùn)練的數(shù)據(jù)量,提高代碼表征模型的特征提取能力。
代碼實(shí)際上具有高度的抽象性和復(fù)雜性,代碼的大部分性質(zhì)難以用簡單明了的數(shù)學(xué)語言來定義。因此,對于代碼分析模型所生成的代碼表征的評價(jià)就顯得尤為重要。但是目前對代碼表征質(zhì)量的評估并沒有一個(gè)統(tǒng)一的標(biāo)準(zhǔn)?,F(xiàn)有的工作都是基于具體的分析任務(wù)對表征向量進(jìn)行評價(jià)。甚至在同一種類型的分析任務(wù)中,都存在著多種不同的指標(biāo)。如在代碼克隆檢測的相關(guān)工作中,所使用的評價(jià)指標(biāo)包括精確率(precision)、召回率(recall)、F1 值、準(zhǔn)確率(accuracy)等評價(jià)指標(biāo),同時(shí)代碼檢測任務(wù)中所使用的數(shù)據(jù)集包括BigCloneBench、GoogleCodeJam、OnlineJudge等不同形式的數(shù)據(jù)集。在代碼搜索任務(wù)中,也是通過計(jì)算最后推薦結(jié)果的歸一化折損累計(jì)增益(normalized discounted cumulative gain,NDCG)來衡量代碼表征質(zhì)量的好壞。這種基于具體任務(wù)的結(jié)果來判斷代碼表征質(zhì)量優(yōu)劣的方式,并不是直接對表征向量的質(zhì)量進(jìn)行衡量。同時(shí)在代碼分析任務(wù)中,不同任務(wù)所使用的數(shù)據(jù)集是不同的,也存在著許多工作是基于研究者自己所標(biāo)注的數(shù)據(jù),這使得應(yīng)用于不同代碼分析任務(wù)中的表征模型不能在同一標(biāo)準(zhǔn)上進(jìn)行比較。對于此問題,本文建議以眾包的形式構(gòu)建代碼分析領(lǐng)域通用高質(zhì)量數(shù)據(jù)集,并在統(tǒng)一的評價(jià)標(biāo)準(zhǔn)上進(jìn)行方法比較。
代碼表征在軟件分析任務(wù)中占據(jù)了重要的地位。通過使用深度學(xué)習(xí)模型生成代碼表征向量,自動(dòng)地提取代碼中所包含的隱含特征,降低對人工制定特征的依賴,進(jìn)而提升代碼分析任務(wù)的效率。本文介紹了代碼表征的基本概念,并對近幾年來不同的代碼表征及其應(yīng)用的工作進(jìn)行了綜述,對這些工作的原理與技術(shù)進(jìn)行了分類與總結(jié),最后分析并討論了現(xiàn)有的代碼表征工作中仍然存在的問題以及對應(yīng)的解決方法。