DeepSim: Deep Learning Code Functional Similarity

論文閱讀 DeepSim: Deep Learning Code Functional Similarity

代碼的功能相似性檢測

1、現存的大多數方法聚焦於代碼的語法相似性,功能相似性還是一個挑戰

(現存方法:都一般遵循相同的流程
首先從源代碼中提取語法特徵,以原始文本、tokens或者AST的形式
然後使用某個距離度量公式,如歐氏距離來檢測相似的代碼)

本文中提出的方法:將代碼的控制流和數據流編碼成一個語義矩陣,矩陣的每個元素都是一個高維稀疏的二進制向量

在這種表示下,我們設計一個深度學習模型來衡量代碼功能相似性
通過將從“代碼對”中學到的“隱藏表示”連接起來,這個DNN模型把檢測功能相似性轉化爲了一個二元分類的問題,從而可以學習到這樣的模式:雖然語法差異比較大,但是功能比較相似的代碼之間的模式。

2、JAVA數據集

構建程序依賴圖(PDG),通過子圖同構來判斷的變現比AST好,但是子圖同構複雜性高(they either do not scale due to the complexity of graph isomorphism圖同構具有複雜性,不可伸縮??;要麼使用可擴展性的序列來近似圖,而不精確,例如:將PDG中的子圖映射到AST深林,比較AST中提取的語法特徵向量)

3、DeepSim有兩個關鍵點:

①如果特徵表示具有更高的抽象,則它對於度量代碼語義更爲強大
因爲更高的抽象需要捕獲更多的代碼語義信息
控制流和數據流代表的是比語法特徵更高的抽象
所以,DeepSim使用控制流和數據流作爲相似性度量的基礎
②我們提出了一種新型的編碼方法:將代碼的控制流和數據流編碼成爲一個壓縮的語義矩陣,矩陣的每個元素都是一個高維稀疏二值特徵向量。
通過這種編碼,我們將代碼相似性度量的問題簡化爲識別矩陣中的相似模式。這比發現子圖更具有收縮性。
在這裏插入圖片描述

包含主要的兩部分:

①Code semantic(語義) representation through encoding control flow and data flow into a feature matrix
可以將任意語言的代碼片段作爲源代碼、字節碼或二進制代碼形式的輸入,只要可以構造數據流圖和控制流圖
②Code similarity measurement through deep learning
該模型包含兩個聯繫緊密的模塊:
NN模塊:提取高級特徵(隱藏表示)
二元分類模塊:判斷代碼對在功能上是否相似
數據集

在JAVA數據集上已經實現

兩個數據集:

a dataset of 1,669 Google Code Jam projects(數據集地址) and the popular BigCloneBench(數據集地址),which contains over 6,000,000 tagged clone pairs and 260,000 false clone pairs.

本篇文章關於深度學習反向傳播的解釋:

The goal is to minimize the error (e.g., squared error) of reconstructing the inputs from its
hidden representation:
在這裏插入圖片描述
模型訓練過程:
在這裏插入圖片描述

將控制流和數據流代表的語義信息編碼爲一個稀疏矩陣的過程:

控制流捕獲代碼基本塊和過程之間的依賴關係(控制流分爲順序、分支、循環結構)
數據流捕獲沿着程序路徑和操作的數據值流

以上是表示代碼行爲的基礎

我們basic idea是將控制流和數據流編碼爲變量之間和基本塊之間的關係

Our basic idea is to encode code control flow and data flow as relationships between variables and
between basic blocks (e.g., operations, control jumps).

To obtain the control flow and data flow, we may perform the analysis on either source code, binary code, or any intermediate representation (e.g., Java bytecode, LLVM bitcode).
In this paper we focus on Java bytecode using the WALA framework
代碼示例和控制流圖(CFG):
在這裏插入圖片描述
在CFG中,每個矩形代表一個基本塊,每個塊中可能存在多箇中間指令
每個基本塊可以生成一個數據流圖(DFG)變量和基本塊的類型信息都包含在圖中

Encoding Semantic Matrices:

我們考慮三種用於編碼控制流和數據流信息的特徵:
變量特徵(Variable features)、基本塊特徵(Basic block features)、變量和基本塊之間的關係特徵(Relationship features between variables and basic blocks)

變量特徵:

①data type : V (t) (bool, byte, char, double, float, int, long, short, void, other primitive types, JDK classes, user-defined types)
②modifiers(修飾符):V (m) (final, static, none)
③other information: V (o) (basic, array, pointer, reference)
共19種

我們把他們編碼成一個19維的特徵向量

V = {V (m),V (o),V (t)}
如: X is a static int variable, so we have V = {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0}

基本塊特徵:考慮7種類型:

normal, loop, loop body, if, if body, switch, switch body
7維的one-hot向量,用B表示
如:BB1 is a if basic block and its representation B = {0, 0, 0, 1, 0, 0, 0}
在這裏插入圖片描述
變量和基本塊之間的關係特徵
我們把數據流和控制流編碼爲變量之間的操作和基本塊之間的控制跳轉
共識別了43種不同的操作類型(43維onehot編碼, I向量表示)
爲了保留DFG中各變量之間的操作信息,T = {Vop1,Vop2, I },81維(19+19+43),Vop1,Vop2
表示操作數,19維
爲了保存控制流,將T擴展爲T = {Vop1,Vop2, I ,B},88維(81+7),這88爲特徵向量包含數據流和控制流
在這裏插入圖片描述

現在一般定義3個規則來對變量和基本塊之前的聯繫編碼信息

在這裏插入圖片描述
分別定義了
①兩個變量之間的操作
編碼兩個變量op1和op2之間的操作,以及兩個變量的類型信息和相應的基本塊。這裏bbop1表示op1所屬的基本塊

②變量和相關塊之間的信息
編碼變量與其對應塊之間的關係。這裏V0,I0表示零特徵向量

③兩個鄰居基本塊之間的聯繫。
編碼兩個相鄰基本塊之間的關係。這裏bb2是CFG中bb1的繼承者

定義3.1(語義特徵矩陣A)。給定一個代碼片段,我們可以使用上面捕獲其控制流和數據流信息的編碼規則生成一個矩陣。A中的每一行和每一列都是一個變量或基本塊。它們的順序對應於代碼指令和基本塊順序。A(i,j)=T(i,j)是大小爲E=88二進制特徵向量
i,j取值:1…n,n=nv+nb,變量數目與基本塊數目的總和
Given a code fragment, we can generate a matrix using the encoding rules above that captures its control flow and data flow information. Each row and column in A is a variable or a basic block. Their order corresponds to the code instruction and basic block order. A(i, j) = T (i, j) is a
binary feature vector of size E = 88 that represents the relationship between row i and column j, here i, j = 1, 2, …,n, where n = nv +bb is the summation of variables count and basic blocks count.

在這裏插入圖片描述

考慮圖2中的示例。它的CFG有11個變量(9個局部變量和2個靜態字段)和6個基本塊(包括輸入、返回、退出塊)。根據上面的編碼規則,我們可以導出一個如圖3所示的稀疏矩陣。(所以矩陣A的尺寸是17*17,A的每個元素是T向量,88d,表示一個變量或基本塊)
在這裏插入圖片描述

與原始的CFG和DFG相比,我們的語義特徵矩陣表示有兩個優點。

首先,它減少了尋找同構子圖以檢測矩陣中相似模式的問題。
其次,這種結構(即一個矩陣)使得以後的過程很容易使用。
值得注意的是,對於大多數語法上相似的代碼,它們的標識符名稱、文本值或代碼佈局都不同,生成的語義矩陣將相同,因爲這些差異是由CFG和DFG規範化的。
克隆類型
級別1:除了空白區域、整體佈局與註釋上有差異意外,其他方面完全一致
級別2:在級別1差異的基礎上,還在變量名和文字值上具有差異
級別3:在級別2的差異的基礎上,還會有語句片段的添加、刪除與修改
級別4:兩個相似代碼片段具有不同的語法結構之外,實現的功能是一樣的
這個特性確保我們的方法也可以處理語法相似性。

我們承認,我們的矩陣表示法並不編碼PDG中的所有信息。然而,所有依賴都隱式地編碼在我們的表示中。例如,code :y=x;z=x包含一個輸入依賴項;它被編碼爲T(y,x)和T(z,x)。其他類型的依賴以類似的方式編碼

有了語義矩陣,可以有效表示代碼段,但是我們不能直接在語義矩陣上使用歐氏距離度量兩個元素之間的相似程度。

語義矩陣有兩大缺陷:

①無法確定哪些元素更重要,所以歐氏距離不可用
②不能檢測出哪些元素的功能是相似的

但是功能相似、語義不同的兩個語義矩陣不同,所以我們不能使用簡單的距離度量(歐氏距離)來度量他們的相似性,因爲我們不知道語義矩陣中的某些元素是否比另一個元素更重要。而且,不同的元素可能功能相似(如int和long),但是很難直接在語義矩陣上檢測這種相似性。

本文設計出一個可以有效識別語義矩陣之間的相似模式的深度學習模型。

(DNN就是用來識別兩個語義矩陣之間的相似性)
使用前饋神經網絡從每個代碼對的語義矩陣中生成high-level features,然後使用二元分類器將他們分成功能相似或者不相似
在這裏插入圖片描述
輸入處理:與RNN類似,要求輸入一個大小固定的語義矩陣 k*k,k=128
用0填充或者原始特徵矩陣大小is=nv+nb,截斷後k=nv′+nb′=128,其中nb′=nb,nv′=128-nb (nv變量數目,nb基本塊的數目)
保留所有CFG的信息,丟棄部分超過矩陣大小的DFG信息來截斷矩陣
T:88d,是控制流和數據流的編碼
將T映射到h0 ,c=6

處理代碼語句重新排序。
然後,我們將A的每一行展平爲一個長度爲c·k的向量,並以展平的行特徵向量爲輸入,添加兩個完全連接的層,以提取與每一行所表示的變量或基本塊相關的所有信息。最後,加入一個池化層,對所有的行特徵向量進行彙總。爲了減少代碼語句重新排序的影響,我們使用平均池化而不是整平整個矩陣
在這裏插入圖片描述
程序的第4行和第5行的句子順序導致x和y的值不同
在矩陣的表示中,我們編碼這三個變量之間的數據流,句子順序決定了矩陣的每一行表示那個變量或者基本塊
因爲,我們生成了2個不一樣的矩陣A

兩個程序的前三行沒有依賴關係,所以如果只考慮前三行的話,兩個程序是等價的。
所以爲了使我們的程序具有re-order的健壯性,我們忽略獨立變量或者基本塊之間的順序,只保留有依賴關係的變量或者基本塊的order。這可以通過對錶示獨立變量或者基本塊的A的所有相鄰行來執行average pooling,然後flatten剩餘的行(句子順序被保存在這個flatten vector中)。但是這會導致不同方法的向量長度不同,輸入到一個靜態的NN模型中有問題,所以對矩陣中的所有行採用average pooling處理。池化使得模型在沒有任何額外參數的情況下將輸入的大小從ckk轉化爲c*k
(與卷積層的pooling類似)。減小了模型的複雜性,可以提高訓練和預測的效率。
以上表示可能會區分兩個相似但是並不是完全相同的輸入,如int和float array,使用帶監督的二分類來解決
在這裏插入圖片描述
如圖4所示,我們以不同的順序連接方法m1和m2的潛在表示向量h13和h23:[h13,h23,[h23,h13]。然後,我們在兩個具有共享權重的連接向量上應用一個完全連接的層,對輸出狀態執行另一個平均池,最後添加分類層。我們現在可以使用交叉熵作爲成本函數,並最小化它來優化整個模型。
通過這種方式,我們的模型能夠學習具有不同語法的每個代碼對之間的相似性

大小爲N的代碼存儲庫需要運行N*(N-1)/2,對於N個方法,先運行模型的前向階段得到各個方法的潛在表示h3,對於每個方法對,只運行分類階段

實驗

在GCJ和BigCloneBench數據集上進行測試
在這裏插入圖片描述
人工證實有12個完全不同的problem。

GCJ數據集的發現
我們發現超過20%的項目包含至少一個明顯的語句重新排序。它們大多是由循環中使用的變量定義的不同順序或函數(如min、max等)的參數的不同順序引起

BigCloneBench是一個大型代碼克隆基準測試,包含超過6000,000個標記克隆對和260,000個從25000個系統收集的錯誤克隆對

BigCloneBench中的每個pair也是一對方法,並被手動分配一個克隆類型。從下載的數據庫中獲得的克隆對總數與[53]中報告的略有不同。我們丟棄沒有任何標記的真或假克隆對的代碼片段,並丟棄LOC小於5的方法。最後我們得到了50K個方法,包括5.5M個標記克隆對和200K個標記假克隆對。大多數真/假克隆對屬於克隆類型WT3/T4
級別3:在級別2的差異的基礎上,還會有語句片段的添加、刪除與修改
級別4:兩個相似代碼片段具有不同的語法結構之外,實現的功能是一樣的
實驗

使用DECKARD , RtvNN and CDLH 來做對比
DECKARD是一種典型的基於語法樹的技術,它使用預先定義好的規則爲每個子樹生成特徵向量,並對他們進行聚類以檢測代碼克隆的方法。
RtvNN使用RNN+歐氏距離來度量相似性。與我們的方法不同之處在於:RtvNN對源代碼的tokens和AST進行操作,並分別學習每個代碼的隱藏層表示(無監督學習),而不是組合代碼對來學習他們之間的相似模式。
CDLH使用LSTM+ASTs

實驗1

在這裏插入圖片描述
①DECKARD achieved low recall and precision.
原因是:該模型要求兩個方法的語法樹非常相似,本質上是整個代碼的語法相同。
爲了檢驗,我們選取了12個問題之一的所有解決該問題的代碼,發現超過一半的解決方案具有不同的語法樹結構,他們被錯誤地預測到不同的簇類中。
②DeepSim的表現遠高於DECKARD、SdA-base and SdA-unsup,說明了該方法能夠有效地從語義矩陣中學習higher-level的特徵。,編碼後的語義矩陣也比DECKARD中所用的語法表示更好。
③ RtvNN的查全率最高,查準率最低。我們發現 RtvNN幾乎將所有的方法對都報告爲相似。
原因是: RtvNN依賴於tokens和AST去生成隱藏層的表示,然後使用一個很簡單的距離度量來區分它們。但是,兩個功能相似的函數可能具有顯著的語法差異,兩個功能不同的函數可以共享語法上相似的成分,如IO操作。
通過進一步分析,我們發現大多數方法之間的距離在[2.0,2.8]的範圍內,通過降低距離閾值,RtvNN的準確率可以顯著提高到90%,但是查全率迅速下降到10%,F1分最高爲0.325
④DeepSim的精確度爲71.3%,仍然有很大的改進空間。 檢查了DeepSim的誤報和漏報後,我們發現這主要是由於該工具在處理方法調用方面的侷限性。
比如在一個問題中,有些解決方案使用標準的循環和賦值語句,而其他解決方案則使用Map和Replace之類的utility methods(通用方法)。由於我們沒有講被調用方法中的信息明文編碼到語義特徵矩陣中,DeepSim無法區分這些方法及其相應的語句。
對於大多數靜態代碼相似性度量技術來說,這是一個常見的限制。

Considering that DeepSim could generate a final hidden representaion for each method after training, incorporating this information to encode method invocation instructions is feasible (re-training may be necessary). In addition, utilizing constraints-solving based approaches [29, 33] as a post-filtering to the reported results may also further improve the precision(DeepSim在訓練後爲每個方法都生成一個隱藏層表示,所以把這些信息合併到調用指令的編碼中是可行的,此外,使用基於約束求解的方法對報告結果進行後濾波也可以進一步提高精度)

實驗2

在這裏插入圖片描述
對於這個數據集,DeepSim在查全率和查準率方面都明顯優於所有其他方法。
DeepSim本應該保證在T1-ST3上的F1達到1.0,沒有達到的原因應該是在該工具從源代碼中產生DFG時,丟失了一些數據流,原因是我們在WALA中引入了處理外部依賴項的近似方法。

實驗3

使用問題ID爲4的真/假克隆對作爲訓練數據集(因爲它包含大約80%個真/假克隆對的整個數據集),其餘數據作爲測試數據集。(由於來自BigCalBoobe的真/假克隆對來自不同的功能 )

在這裏插入圖片描述
對於wt3/T4的克隆類型,BigCloneBench上的DeepSim的F1高於GCJ數據集上的F1(這與文獻[57]中報道的BigCloneBench和另一個OJ數據集OJClone的比較結果一致)。我們檢查了幾個WT3/T4克隆對,發現儘管它們在語句級別(這是WT3/T4克隆類型的標準)上的相似性不到50%,但其中許多都遵循相似的代碼結構,並且只在調用的api的序列上有所不同。相反,GCJ項目都是由具有不同代碼結構的不同程序員從頭開始構建的。因此,在GCJ數據集中檢測功能克隆更加困難。
Why DeepSim outperforms the other DNN approaches?

原因有三。

首先,DeepSim基於編碼DFG/CFG的語義特徵矩陣,這是比其他DNN方法(如RTVNN和CDLH)所使用的詞彙/語法表示(例如,token或AST)更高級別的抽象。與兩個SdAbaseline相比,DeepSim以語義特徵矩陣爲輸入,而對於SdAbaseline,每個88d特徵向量T被手動重新編碼爲8d,整個矩陣被展平爲一個向量。手動重新編碼可能會丟失信息,因爲8d向量中的每個元素實際上都是分類數據,並且在這種情況下,標準神經網絡模型受到限制。此外,flatten整個矩陣會加劇語句重新排序的影響,因爲整平向量是嚴格排序的,這會導致報告更多的誤報。

第二,DeepSim和兩個SdA baseline都包含一個方法對的分類層,而RtvNN只計算兩個方法的隱藏表示之間的距離,不比我們之前討論的那樣適用,這應該部分歸因於模型中二元分類公式的有效性。

第三,DeepSim模型使用了兩個average pooling layers,第一個計算矩陣所有行的平均狀態,從而減少了語句重新排序帶來的副作用。第二個計算兩個不同隱藏狀態的表示的拼接的平均狀態,保證了代碼相似性的對稱性,即:

編碼方法的有效性。

在DeepSim的第一層,我們將每個88維的特徵向量T映射到隱藏空間中的一個6維的向量。爲了分析我們的訓練模型,我們構造了8個不同的特徵向量,每個特徵向量都是變量之間特定操作的編碼。我們將它們映射到嵌入空間,並使用PCA降維對其進行可視化。
在這裏插入圖片描述
DeepSim生成的8個不同輸入特徵向量的第一層隱藏表示。
①有四個非常接近的點,代表四種不同的指令。這表明我們的編碼確實有效地保留了數據流信息,並且我們的模型成功地學習了這些操作之間的相似性。

在這裏插入圖片描述
儘管操作相同,但是來自於不同的基本塊在嵌入空間中並不緊密。
這表明,我們的函數代碼相似性度量模型能夠將代碼結構(即控制流)信息有效地分解到特徵向量中,併成功地學習到這些信息。

八條指令隱藏狀態的可視化顯示了對數據流和控制流進行編碼的各種特性矩陣的有效性。
更重要的是,我們的DNN模型可以從輸入的語義特徵矩陣中學習高級特徵,以及具有不同語法的功能相似代碼之間的模式。這顯著地區別了我們的方法和其他方法。

語義特徵矩陣的大小被固定爲128,對我們的數據集有效,但是這個固定長度針對較短或者較長的數據集可能會降低計算效率或者丟失一些方法的信息,作者提出可以與RNN結合,但是RNN需要固定的輸入序列順序,這很難處理代碼語句的重新排序。具有attention機制的LSTM可能會是一個潛在的解決方案。

另外,DeepSim的性能受到數據集大小和質量的限制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章