(北京航空航天大學 軟件工程研究所 北京 100191)
摘 要:通過編譯的C++程序代碼并不一定保證代碼中不存在缺陷。代碼中可能依然隱含了安全、設計或是風格上的缺陷,從而導致程序運行時出現內存泄露、指針誤用等現象,或導致程序代碼不清晰、可讀性差。為了有效查找這些缺陷,探討了可定制缺陷規則的C++代碼缺陷自動檢測技術,介紹了兩種缺陷定位方法,給出了一種基于XPath技術的缺陷規則定制方法,設計并實現了一種代碼缺陷自動檢測工具CDD(C++ defect detector),并通過實驗證明了缺陷定位方法的有效性以及CDD的易用性。
關鍵詞:靜態分析; 抽象語法樹; 缺陷檢測; 規則擴展
中圖分類號:TP311文獻標志碼:A
文章編號:1001-3695(2009)05-1628-04
Research and implementation of automated C++ code defect detection tool
GU Ke LIU Chao JIN Maozhong
(Software Engineering Institute Beihang University Beijing 100191 China)
Abstract:The C++ program which is all right in compiling process does not always insure there are no defects in the code. For the reason that there may be defects relative to securities design and code style it may result in memory leak misuse of pointers or make the program code unclearly and unreadable. The defects will place bad impact on the normal running and the maintain ability of the software. This paper introduced a good technology of defectextendable automated C++ code defect detection including two methods to detect the defects a description of defect rules based on XPath technology and an introduction of the C++ defect automation detector. Furthermore analyzed the detector in stability credibility and easytouse by experiment.
Key words:static analysis; abstract syntax tree; defect detection; rule extension
0 引言
軟件代碼的質量直接關系到軟件功能的實現、運行的可靠性、系統的健壯性,以及軟件的可維護性等諸多方面。從代碼質量的角度講,程序代碼不僅要確保語法和語義的正確性,同時還需保證其在內部數據訪問、內存使用等方面的安全性,以及接口定義、編程風格等方面的規范性。因此,在編寫代碼的過程中及時發現并清除代碼缺陷,是提高軟件質量和開發效率的重要保證。
傳統的代碼缺陷檢測都是通過人工代碼審查來完成,因此存在過渡依賴個人經驗和低效等問題。自動化代碼檢測工具定位于一種輔助審查的工具,可以通過對源代碼的分析來自動發現代碼中潛在的多種類型的缺陷。代碼缺陷自動檢測工具為顯著提高軟件質量和可維護性提供了有力的支持。
C++是一種功能強大、具有較高執行效率的程序語言,但它同時也是一種語法復雜、類型約束和安全控制機制寬松、編程風格自由的程序語言。即便是有多年編程經驗的C++程序員也很難保證所寫的程序不存在缺陷。編譯器一般只能進行程序語法檢查,而無法檢查代碼和設計的一致性、代碼對語言標準的符合型、代碼的可讀性、語義的正確性、代碼結構的合理性等方面的問題或缺陷。鑒于人工代碼審查的低效以及C++語言自身的復雜性,研究針對C++代碼的缺陷檢測工具對于加快軟件開發的進度和保證軟件的質量都具有重要的意義。
1 相關的研究現狀
無論是商用軟件領域還是開源社區,都有C/C++代碼缺陷自動檢測技術的研究以及相關工具的開發。在商用軟件領域,具有代表性的工具有PCLint[1]和Parasoft C/C++ Test工具包。這些工具的實用性以及可靠性是有目共睹的,也深受各個軟件公司的青睞。一些著名的軟件公司要求其產品在代碼入庫和產品發布之前,都要通過這些工具的測試。雖然這些工具采用的技術相對比較成熟,但是它們非開源軟件,技術內幕都沒有公開,而且在規則配置以及規則擴展方面需要用戶編寫腳本程序,這使得軟件的使用相對比較繁瑣。
在開源社區也有相關工具的研發。2000年發布的ITS4是第一個用于C++自動化缺陷檢測的開源軟件[2],隨后出現的Splint[3]、CPPCheck也都具備缺陷檢測的功能。但是這些軟件都存在不足。ITS4僅僅基于詞法分析,因此它無法分析代碼的語義,也就是說,它無法分析一個標志符代表的是函數名還是變量名;相比之下,Splint的準確性更強,它基于模糊靜態分析,加上用戶添加的擴展標記,檢測的準確率比ITS4要好。但是Splint依賴于用戶添加的擴展標記,這給用戶帶來了一定的負擔,而且它只針對C,對于C++特有的缺陷無法檢測;CPPCheck是封裝了PCLint工具的Eclipse插件,它的問題在于無法讓用戶自己配置規則,檢測的缺陷規則都是內置固定的。
本文在對自動化代碼缺陷檢測技術研究的基礎上,進一步引入了缺陷規則的定制方式來改進缺陷規則的擴展方式,同時基于抽象語法樹的遍歷,能更精確地對缺陷位置進行定位。
2 缺陷規則概述
缺陷檢測的本質就是檢測程序中是否有符合缺陷規則的代碼片段。缺陷規則描述了不符合語言標準、違背良好設計風格或編碼規范的代碼片段所具備的特點。本文探討的缺陷檢測技術所支持的缺陷規則集大致分為以下幾類:
a)導致程序運行時產生內存泄露,如捕獲異常時沒有釋放try塊中分配的內存空間。
b)導致指針誤用,如使用了內存分配失敗或文件打開失敗返回的空指針。
c)導致程序產生未定義行為,如基類沒有提供虛的析構函數。
d)破壞了數據的封裝,如返回私有數據成員的句柄。
e)違背良好的編碼規范,如多個語句出現在同一行,影響代碼的可讀性。
本文利用一個具有安全缺陷的類設計對缺陷規則進行更具體的描述。
設有如下代碼片段:
class String{
public:
String(size_t size_):m_size(size_){
data = new char[m_size];
}
~String(){
delete []data;
}
size_t size(){
return m_size;
}
private:
size_t m_size;
char *data;
};
這段看似正確的代碼其實存在兩個嚴重缺陷:
a)具有指針成員變量的類應該重定義該類的拷貝構造函數和賦值運算符。
b)用new運算符分配內存時,應該捕獲可能拋出的badallocation異常。
分析缺陷a)可知,如果不重定義拷貝構造函數和賦值構造函數,編譯器會以按成員拷貝的方式自動生成相應的默認函數,那么成員的拷貝將導致兩個類的成員指向同一片內存空間。以類String的兩個對象a、b為例。假設a.m_data的內容是“hello”,b.m_data的內容是“world”。執行b = a,默認賦值函數的按成員拷貝意味著執行b.m_data = a.m_data。這將造成三個錯誤:
(a)b.m_data原持有的內存沒有釋放,造成了內存泄露;
(b)b.m_data和a.m_data指向同一片內存,a或b任何一方變動都會影響另一方;
(c)在對象被析構時,m_data被delete了兩次。
這樣的缺陷給程序帶來了嚴重的安全隱患,無論是內存泄露還是指針的二度析構都將可能導致程序的崩潰[4]。
缺陷b)則可能導致指針的誤用,因為用new來開辟內存不一定每次都會成功,在存儲空間不夠、分配失敗時會拋出異常,如果不處理異常,將導致程序崩潰的嚴重后果。
本文所總結的缺陷規則主要來源于文獻[4~6],還有部分規則來源于Google近期公布的C++編碼規范[7],這些出版物都具有權威性,也是C++編程領域公認的缺陷規則。
3 自動化缺陷檢測
本文介紹的自動化缺陷檢測工具是基于Eclipse CDT插件開發的。本章將對CDT、檢測工具的體系結構以及第一類缺陷定位方法作具體介紹。
3.1 CDT簡介
CDT(C/C++ development tools)是在Eclipse平臺上開發C/C++程序的工具包。Eclipse 平臺只是用于容納開發工具的一個框架,它不直接支持 C/C++;CDT以插件形式內嵌于Eclipse中。CDT 項目致力于為 Eclipse 平臺提供功能完全的 C/C++集成開發環境,它針對的主要用戶是Linux程序員,但它在可使用 GNU 開發者工具的所有環境(包括 Win32(Windows 95/98/Me/NT/2000/XP)、QNX Neutrino 和 Solaris 平臺)中均能工作。
選擇基于CDT開發主要有以下幾個原因:
a)CDT前端分析器功能強大而且很健壯。它不但能夠對標準C++進行正確解析,同時還支持C99的語法分析。目前的CDT5.0的分析器具有C/C++語言支持種類多、分析能力強等特點。
b)CDT分析器產生的靜態信息豐富,而且信息組織合理,便于檢索。源文件通過靜態分析將生成一棵與源碼對應的抽象語法樹,同時CDT能根據語法樹進行計算和統計,構建一個存儲源文件靜態信息的數據庫,這樣就無須每次都遍歷語法樹來查找信息,大大提高了查詢效率。
c)CDT用Java語言開發,跨平臺性好。
d)CDT內嵌于Eclipse,缺陷檢測工具通過擴展CDT就可以復用Eclipse提供的用戶界面,工具可以直接服務于Eclipse平臺下的C/C++程序員。
e)CDT是完全用 Java 實現的開放源碼項目(根據 Common Public License 特許的)。
3.2 缺陷檢測工具的體系結構
本文所述的C++代碼缺陷檢測系統的體系結構可以分為后端的結果展示和前端的代碼缺陷檢測分析兩個大的模塊。其中后端的結果展示模塊將主要的注意力集中在結果的可視化方面,這里不作贅述。本文只對前端的缺陷檢測分析模塊的設計進行細致說明。
前端的缺陷定位分析可以分為四個子模塊,如圖1所示。
1)規則集加載器 它負責將XML文本形式的規則描述以及負責檢測規則的類加載到內存,其實現主要依賴XML文件的信息交互和Java的動態加載機制。由于存在兩種缺陷定位方式,加載的缺陷規則也分為兩大類。本文在后面對這兩種定位方法以及相應的規則類型作詳細的介紹。
2)靜態分析器 缺陷檢測依賴的源碼靜態信息來源于CDT插件的前端分析器,其特點以及采用它的原因參見4.1節,在此不作贅述。
3)缺陷定位器 其主要功能是根據用戶所選擇的規則,對源代碼中符合這些規則的缺陷進行定位。本文采用了兩種定位方法。
4)檢測結果信息庫 信息庫就是用來保存代碼審查結果信息,將一次代碼審查的信息結果進行保存,以便能夠在代碼審查的后續步驟中對結果進行分析,同時也是為了能夠將結果進行匯總輸出。信息庫采用GOF23模式中的單例模式[8]進行設計,因此它是全局的,從而保證了檢測過程中缺陷信息的一致性。
3.3 基于訪問者模式的缺陷定位方法
抽象語法樹的組織使用了GOF提出的組合模式,使得對單個對象和組合對象的使用具有一致性[8]。也就是說,每一個語法節點存在一套共用的接口。這是可以采用訪問者模式[8]對語法樹進行遍歷的重要基礎,并且CDT生成的語法樹允許用戶根據需要遍歷至不同的深度。也就是說并不一定每次遍歷都要途徑所有的語法節點,用戶可以忽略自己不關心的語法節點以及它所有的子節點。
本文所有采用這種方法進行缺陷定位的對象本質上都是一個訪問者,但是它們關注的信息不同。一個訪問者負責定位一類缺陷。加載器根據用戶的選擇將缺陷規則以及負責定位符合這些規則缺陷的對象加載到工具中。靜態分析器對源碼進行分析生成抽象語法樹,該對象以訪問者的身份遍歷語法樹,同時也對靜態信息庫的內容進行查詢,查找是否有匹配該缺陷規則的代碼片段,如果有,則將缺陷信息以及所在源碼中的位置存儲到檢測結果信息庫中,這樣便完成了缺陷的定位。
這種方法可以對缺陷進行準確定位,但是它存在局限性,即缺陷規則的擴充很繁瑣,因為負責缺陷定位的對象是語法樹的訪問者,這樣就說明每增添一條缺陷規則,就要通過編碼向檢測工具中新添一個對象。這對于普通用戶而言,幾乎是不可能完成的任務,所以,只有開發人員可以向工具中新添規則。為了解決這個問題,本文提出了一種基于XPath技術的缺陷定制方法。
4 基于XPath的缺陷定制及缺陷定位
4.1 XPath技術簡介
XPath(XML path language)是一種對XML文檔的內容進行定位、檢索的語言,是后續更強大的數據檢索語言如XQuery的基礎。XPath因使用路徑標記在XML文檔的層次結構中進行導航而得名。XPath的主要目的是用于對XML文檔元件尋址。在支持這個主要目的的同時,它也為字符串、數字和布爾操作提供了基本手段。
XPath將一個XML文檔建模成為一棵節點樹,有不同類型的節點,包括元素節點、屬性節點和正文節點。XPath 定義了一種方法來計算每類節點的字串值。其主要語法構件是表達式,主要由兩個表達式組合而成:一個表達式匹配產生式 Expr;另一個表達式被求值評估產出一個對象。該對象屬于下列四種基本的類型之一:
a)節點集合(無序的、無重復的節點集合);
b)布爾(真或假);
c)數字(一個浮點數字);
d)字符串(UCS 字符的順序)。
表達式求值發生與上下文有關。上下文組成包括:
a)一個節點(上下文節點);
b)一對非零正整數(上下文位置和上下文大小);
c)一個變量綁定的集合;
d)一個函數庫;
e)表達式范圍內的命名空間聲明的集合。
4.2 XPath結合抽象語法樹
本文通過對XML的數據模型與語法樹的數據模型之間的比較找到了XPath與語法樹之間的聯系,并通過這種聯系將XPath語言直接應用到對于語法樹內容的定位和檢索。
可以看出,源代碼所對應的AST和XML文檔所對應的數據模型在幾個關鍵點上都有很多相似之處:
a)均為樹狀結構。
b)節點在樹中是有序的。
c)每個節點都包含自己的屬性。對于AST中的每一個節點都包含有其節點的屬性,用來記錄代碼的一些詳細信息。例如AST中的class節點,是用來表示聲明一個對象的類型的,其屬性包括用來記錄其類名、出現位置的起始行號、起始列號和聲明的成員函數。
由于XML的數據模型和源代碼對應的語法樹有很多相似之處,本文利用兩者的聯系,將XPath語言引入到語法樹節點的檢索和定位中,從而間接對源代碼進行檢索。用戶可以利用XPath語言來描述一個新定義的缺陷規則。其中,用戶只要給出這種缺陷模式所對應的示例代碼,并在本系統的輔助下生成用來描述這個缺陷規則的XPath語句和對應的配置文件,這樣就可以很方便地完成一條新的缺陷規則的添加。
從缺陷定制的方式可以看出,在源碼中定位缺陷只要根據代碼示例產生的XPath匹配表達式在語法樹中進行檢索,分析語法節點之間的關系以及節點包含的信息就可以完成缺陷的定位,但并不是所有類型的缺陷都可以通過這種方式進行定位的。
4.3 局限性
基于XPath的缺陷規則定制在一定程度上解決了缺陷擴充的問題,但是它也存在一些局限性,例如XPath表達式的編寫給用戶帶來了一定的負擔,用戶必須了解如何編寫XPath表達式,有些規則所需的表達式極為復雜,難以保證其正確性。另外,并不是任意類型的缺陷規則都能利用XPath技術進行定制,因為有些缺陷的定位過分依賴于上下文信息,甚至需要進行數據流分析。對于這樣的缺陷規則,XPath無法定制,這類規則的擴充就只能通過向檢測工具中增添語法樹的訪問者來進行缺陷定位,也就是采用本文介紹的第一類缺陷定位方法。
由此可見,本文論述的兩類缺陷定位方法都有優勢和劣勢,兩者在一定程度上可以起到互補的作用。因此,本文將這兩種定位方法都應用到缺陷檢測工具中,讓它們都能發揮自己的優勢,從而在確保工具能精確定位缺陷的同時讓檢測工具可以對缺陷規則進行定制。
5 實驗研究與結果分析
為了檢驗本文提出的缺陷定位方法的有效性,本文自主研制了C++代碼缺陷檢測工具CDD。它主要針對于使用Eclipse+CDT+MinGW進行嵌入式程序開發以及QT程序開發的Linux程序員,可以幫助程序員準確定位代碼中的缺陷,并根據自己的需要對缺陷規則集進行擴充。
本文采用的實驗是利用CDD,對QESAT C++版前端分析器的代碼進行缺陷檢測。QESAT C++是一個逆向建模工具,可以用來進行軟件度量、UML建模以及白盒測試研究。它前端的分析器代碼行數超過十萬行,如果再算上對C++標準頭文件的擴展,代碼行數將遠遠超過十萬行。檢測這種規模的代碼對于檢驗CDD的健壯性和可靠性等軟件質量屬性都是有說服力的。
本文從以下幾個方面對實驗結果進行分析:
a)性能。本文在CPU主頻為1.80 GHz,內存大小為1 GB的PC機上對代碼行數為109 971的C++項目進行缺陷檢測耗時為254 s,即大概4 min。檢測前配置的缺陷規則數為30條,分析過程中,頭文件全展開。
b)健壯性。對規模不同的項目進行過多次檢測,程序沒有出現異常現象,并且檢測過程中用戶可以隨時終止檢測,工具會將已檢測的結果反饋給用戶。
c)精確性。對代碼行數超過十萬行的QESAT前端分析器進行檢測,結果顯示整個項目一共存在5 224個缺陷。由于缺陷數目較多,本文采用隨機抽樣的統計方法進行分析。
本文從結果中隨機挑選了五組缺陷,每組150個,配合人工代碼審查進行分析,每組缺陷代表一種類型的缺陷,缺陷分類與缺陷規則概述中的分類是一致的,統計結果如表1所示。
從表1可以看出,CDD在檢測風格和數據封裝方面的代碼缺陷時,準確率達到100%;在檢測其他三類缺陷時,準確率也在90%左右。因此可以說明,本文提出的兩種缺陷定位方法是有效的,對于指導C++程序員定位程序代碼的缺陷具有重要價值。
d)易用性。在自主開發的工具CDD內嵌于Eclipse平臺中,是純圖形化界面操作的工具,用戶進行配置缺陷規則、缺陷檢測和結果查看都相當方便。因此CDD可以服務于Eclipse平臺的Linux程序員,提高他們的開發效率。
從上述分析可以看出,采用本文論述的缺陷檢測技術,CDD具備檢測效率高、查準率高以及易用性好等特點,同時,在性能方面CDD還具備提升的空間。
6 結束語
本文主要論述了目前C++代碼缺陷自動檢測技術的研究以及相應工具CDD的開發,并且通過具體的實驗分析論述了自動化缺陷檢測技術的有效性和高效性。可以看出,自動化缺陷檢測技術作為代碼審查流程的一個環節,可以大幅地提高代碼審查的效率,降低其成本,有利于代碼審查技術的推廣和應用,從而保證了軟件開發的進度和質量。
本文提及的自動化缺陷檢測技術也面臨著一些問題,如對缺陷規則自定義的支持不夠理想,即無法以一種通用的方式來對一個缺陷規則進行描述,對于面向對象語言,由于其多態性與類之間繼承的應用使得自動缺陷檢測變得十分困難。而且本文僅僅依賴語法樹信息來檢測缺陷,這對于需要數據流分析和控制流分析的缺陷檢測是無法滿足的。這些問題的存在會導致C++自動化缺陷檢測工具的易用和可用性難以進一步提高。因此,這些問題也得到了很多工具開發人員的重視,對其解決方案的研究也在不斷改進當中。筆者也希望能在以后對缺陷檢測的研究中有所突破。
參考文獻:
[1]ZYZYCK J. A report generator for PCLint[J]. Dr Dobb’s Journal 2003,28(2):52.
[2]VIEGA J BLOCH J T KOHNO Y et al. ITS4: a static vulnerability scanner for C and C++ code [C]//Proc of the 16th ACSAC’00. Washington DC:IEEE Computer 2000.
[3]EVANS D LAROCHELLE D. Improving security using extensible lightweight static analysis[J]. IEEE Software 2002,19(1):42-51.
[4]MEYERS S. Effective C++[M]. 侯捷,譯. 武漢:華中科技大學出版社 2001:39-49.
[5]Institute ANS. ISO programming language C++[S]. 2003.
[6]林銳,韓永泉. 高質量程序設計指南——C/C++語言[M]. 北京:電子工業出版社 2008:240-247.
[7]WEINBERGER B. Craig Google C++ style guide[EB/OL]. (2008). http://googlestyleguide.googlecode.com/svn/trunk/cppguide.xml.
[8]GAMMA E HELM R JOHNSON J V R. Design pattern[M].李英軍,馬曉星,蔡敏,等譯. 北京:機械工業出版社 2004:218-228.
[9]CIOLKOWSKI M LAITENBERGER O ROMBACH D et al. Software inspections reviews and walkthroughs[C]//Proc of the 24th International Conference on Software Engineering. 2002:641-642.