軟件測試實驗學習筆記系列2 -- lint,splint的使用
lint簡史
1979年,貝爾實驗室SteveJohnson于1979在PCC(PortableCCompiler)基礎上開發的出了代碼分析工具Lint,可以檢查出很多的不符合規范的的錯誤(如將“==”寫成了“=”)以及函數接口參數不一致性的問題等,完成代碼健壯性的檢查。Lint后來形成了一系列的工具,包括PC-Lint/FlexeLint(Gimpel),LintPlus(Cleanscape)以及Splint.
功能
通常C/C++編譯器假設程序是正確的,Lint恰好相反,因此它有優于編譯器執行的一般性的代碼檢查。Lint還可以在多個文件之間執行錯誤檢查和代碼分析。下面是一些Lint可以檢查出來的部分的錯誤列表:
可能的空指針
在釋放內存后使用了指向該內存的指針
賦值次序問題
拼寫錯誤
被0除
失敗的case語句(遺漏了break語句)
不可移植的代碼(依賴了特定的機器實現)
宏參數沒有使用圓括號
符號的丟失
異常的表達式
變量沒有初始化
可疑的判斷語句(例如,if(x=0))
printf/scanf的格式檢查
現有的Lint程序主要有兩個版本:①PC-Lint,由GimpelSoftware提供的支持C/C++的商用程序。官方地址:http://www.gimpel.com/html/pcl.htm②Splint(原來的LCLint)是一個有GNU免費授權的Lint程序,只支持c,而不支持c++.官方地址:http://www.splint.org/
由于PC-lint是商業軟件,雖說功能強大,但是容易獲取。這里主要介紹GNU的splint工具。
開源的代碼靜態分析工具SP-lint
Splint是一個動態檢查C語言程序安全弱點和編寫錯誤的一個程序,會進行多種錯誤檢查:未使用變量,類型不一致,使用未定義的變量,無法執行的代碼,忽略返回值,執行路徑沒有返回,無限循環等錯誤。
Splint的安裝
1.在Linux下的安裝
1.1rpm安裝:
rpm-ivh splint.xxx.rpm
rpm安裝包是著名的Linux發行商Redhat推出的基于源代碼的軟家包方式。這種安裝方式的缺點是如軟件依賴項有很多并且你沒有安裝那些依賴項時,哈哈,恭喜你,你有事忙了,需要滿互聯網的找到那些依賴項并安裝好;如果依賴項還有依賴項并且你又沒有安裝,我只能說,哥們你中彩了。
1.2Ubuntu或者Debian下安裝
sudoapt-get install splint
這種安裝最省事,唯一的缺點是,安裝的軟件的版本可能不是最新的,以及總是按照默認的配置來安裝的軟件的---不夠靈活
1.3源代碼安裝(通用)
tar -zxvf splint-3.1.2.src.gz cd xxx/splint-3.1.2 ./configure make makeinstall |
2.在window下安裝
可以使用源代碼安裝的方式.最新的官網提供了window下的軟件安裝包(msi格式),地址是:https://github.com/maoserr/splint_win32/downloads
由于本人使用的是Ubuntu12.04LTS,splint安裝使用的是apt-get的安裝方式,splint的版本是3.1.2,以下的介紹都是以次為基礎的。
3.splint的應用
空引用錯誤:/*@null@*/--splint支持的注釋類型,表明其后跟隨的值可能為null
實例程序,null.c:
/*Program:null.c -- a example code for null reference error*/ //'/*@null@*/'is a special annotation the splint tool supported char firstChar1(/*@null@*/char*s){ return *s; } char firstChar2(/*@null@*/char*s){ if(s ==NULL) return '\0'; return *s; } |
splint命令: splint null.c
結果輸出:
Splint 3.1.2--- 03 May 2009 Spec filenot found: null.lcl null.c:2:65:Comment starts inside comment A commentopen sequence (/*) appears within a comment. This usually means an earliercomment was not closed. (Use -nestcomment to inhibit warning) null.c: (infunction firstChar1) null.c:4:10:Dereference of possibly null pointer s: *s A possiblynull pointer is dereferenced. Value is either the result of a functionwhich may return null (in which case, code should check it is not null), ora global, parameter or structure field declared with the null qualifier.(Use -nullderef to inhibit warning) null.c:3:33:Storage s may become null Finishedchecking --- 2 code warnings |
函數firstChar1和firstChar2都使用了null的說明,表示指針s可能是一個空指針,所以splint會對s的值使用情況進行檢查,由于firstChar2中對s的值進行了判斷,所以沒有對firstChar2函數中s輸出警告信息。
未定義變量錯誤:C中使用沒有定義變量會出錯,/*@in@*/說明的變量表示必須先進行定義./*@out*@/表明在執行過函數后,這個變量就進行了定義。--個人感覺/*@out*@/類似與C#中的out關鍵字。
實例1:usedef.c
/*Program:usedef.c -- use splint check the varible undefined error or warnings */ //out represent varible *x will be defined after execution of function extern void setVal(/*@out@*/int *x); //in represent varible *x has been defined before the execution. extern int getVal(/*@in@*/ int *x); extern int mysteryVal(int *x); int dumbfunc(/*@out@*/int *x,int i){ if(i>3) return *x; else if(i>1) return getVal(x); else if(i==0) return mysteryVal(x); else{ setVal(x); return *x; } } |
splint命令:splint usedef.c
splint執行的結果:
Splint 3.1.2--- 03 May 2009 usedef.c:(in function dumbfunc) usedef.c:8:17:Value *x used before definition #在一些執行路徑中一個右值的被使用的時候可能沒被初始化, An rvalueis used that may not be initialized to a value on some executionpath. (Use-usedef to inhibit warning) usedef.c:10:17:Passed storage x not completely defined (*x is undefined): getVal(x) Storagederivable from a parameter, return value or global is not defined. Use/*@out@*/ to denote passed or returned storage which need not bedefined. (Use-compdef to inhibit warning) usedef.c:12:21:Passed storage x not completely defined (*x is undefined): mysteryVal(x) Finishedchecking --- 3 code warnings |
錯誤原因:由于程序中沒有對X定義,所以報出未定義的錯誤.但是由于setVal()使用了/*@out*@/說明,所以在語句“setVal(x)”和“returnx”中,沒有報未定義錯誤。
類型錯誤:C語言中的變量類型比較多,彼此之間有些細微的差別,splint可以對變量的類型進行檢查:
實例1.typeerr.c
/*Program: typeerr.c -- use splint to check type varible error */ int foo(int i,char *s,bool b1,bool b2){ if(i=3) return b1; if(!i || s) return i; if(s) return 7; if(b1 == b2) return 3; return 2; } |
splint命令:splint typeerr.c
splint執行的結果:
Splint 3.1.2--- 03 May 2009
typeerr.c:(in function foo)
typeerr.c:3:5:Test expression for if is assignment expression: i = 3
Thecondition test is an assignment expression. Probably, you mean to use==
instead of=. If an assignment is intended, add an extra parentheses nesting
(e.g., if((a = b)) ...) to suppress this message. (Use -predassign to
inhibitwarning)
#錯誤類型:if語句中的條件表達式是一個賦值語句。
typeerr.c:3:5:Test expression for if not boolean, type int: i = 3
Testexpression type is not boolean or int. (Use -predboolint to inhibit
warning)
#錯誤類型:if語句中的條件表達式返回值不是bool類型而是int類型
typeerr.c:3:17:Return value type bool does not match declared type int: b1
Types areincompatible. (Use -type to inhibit warning)
#錯誤類型:!的操作數不是bool類型而是int類型的i
typeerr.c:4:6:Operand of ! is non-boolean (int): !i
Theoperand of a boolean operator is not a boolean. Use +ptrnegate toallow !
to be usedon pointers. (Use -boolops to inhibit warning)
#錯誤類型:||操作符的右操作數不是bool類型而是整型
typeerr.c:4:11:Right operand of || is non-boolean (char *): !i || s
##錯誤類型:不應該使用==對兩個bool類型進行比較,而應該使用&&
typeerr.c:6:5:Use of == with boolean variables (risks inconsistency because of
multipletrue values): b1 == b2
Two boolvalues are compared directly using a C primitive. This may produce
unexpectedresults since all non-zero values are considered true, so
differenttrue values may not be equal. The file bool.h (included in
splint/lib)provides bool_equal for safe bool comparisons. (Use -boolcompare
to inhibitwarning)
Finishedchecking --- 6 code warnings
實例2.malloc1.c
/*Program: malloc1.c -- check varible type */ #include<stdlib.h> #include<stdio.h> int main(){ char * some_mem; int size1 = 1048567; //size_t size1 = 1048567; some_mem = (char*) malloc(size1); printf("Malloced 1M Memory!\n"); free(some_mem); exit(EXIT_SUCCESS); } |
splint命令:splint malloc1.c
使用是splint檢查malloc1.c
Splint 3.1.2--- 03 May 2009
malloc1.c:(in function main)
malloc1.c:8:28:Function malloc expects arg 1 to be size_t gets int: size1
To allowarbitrary integral types to match any integral type, use
+matchanyintegral.
將size1的定義修改為:
size_t size1= 1048567;
再次使用splint將行檢查:splintmalloc1.c
Splint 3.1.2--- 03 May 2009
Finishedchecking --- no warnings
內存檢查:緩沖區溢出是一種非常危險的c語言錯誤,大部分安全漏洞都與它有關,splint可以對緩沖區的使用進行檢查,報告溢出或越界錯誤。
實例:overflow.c
/*Program: overflow -- splint check overflow error */ int main(){ int buf[10]; buf[10] = 3; return 0; } |
splint命令:splint overflow.c +bounds +showconstraintlocation
splint執行的結果:-
Splint 3.1.2--- 03 May 2009
CommandLine: Setting +showconstraintlocation redundant with current value
overflow.c:(in function main)
overflow.c:4:2:Likely out-of-bounds store: buf[10]
Unableto resolve constraint:
requires9 >= 10
neededto satisfy precondition:
requiresmaxSet(buf @ overflow.c:4:2) >= 10
A memorywrite may write to an address beyond the allocated buffer. (Use
-likelyboundswriteto inhibit warning)
Finishedchecking --- 1 code warning
錯誤類型:數組buf的大小是10字節,最大也可使用的buf[9],但是程序中使用了buf[10],數組越界了,所以報錯了。
實例程序2.bound.c
/*Program: bound.c -- use splint checking bound overflow error */ void updateEnvSafe(char * str, size_t strSize){ |
splint命令:splint bound.c +bounds +showconstraintlocation
splint執行的結果:
Splint3.1.2 --- 03 May 2009
CommandLine: Unrecognized option: +
A flag isnot recognized or used in an incorrect way (Use -badflag to inhibit
warning)
Spec filenot found: showconstraintlocation.lcl
Cannot openfile: showconstraintlocation.c
bound.c: (infunction updateEnv)
bound.c:5:18:Possible out-of-bounds store: strcpy(str, tmp)
Unableto resolve constraint:
requiresmaxSet(str @ bound.c:5:25) >= maxRead(getenv("MYENV") @
bound.c:4:8)
neededto satisfy precondition:
requiresmaxSet(str @ bound.c:5:25) >= maxRead(tmp @ bound.c:5:29)
derivedfrom strcpy precondition: requires maxSet(<parameter 1>) >=
maxRead(<parameter2>)
A memorywrite may write to an address beyond the allocated buffer. (Use
-boundswriteto inhibit warning)
bound.c: (infunction updateEnvSafe)
bound.c:13:3:Possible out-of-bounds store: str[strSize - 1]
Unableto resolve constraint:
requiresmaxSet(str @ bound.c:13:3) >= strSize @ bound.c:13:7 + -1
neededto satisfy precondition:
requiresmaxSet(str @ bound.c:13:3) >= strSize @ bound.c:13:7 - 1
Finishedchecking --- 2 code warnings
錯誤類型:由于使用strcpy函數,沒有指定復制字符串的長度,所以,可能導致緩沖區溢出。UpdateEnvSafe中使用strncpy進行字符串復制,從而避免了緩沖區溢出的錯誤。
4.小結
在命令行下使用的splint非常的強大,splint同樣可以可以集成到IDE 中.具體的要IDE的其他工具的設置。splint同樣也可以寫到在makefile文件中,然后使用make命令來預先檢查代碼中常見的靜態錯誤。
有了上面的這些簡單的實例的演示,我們可以感受到splint的強大之處,當然,這里的介紹僅僅是一個簡單拋磚引玉。更多的有關splint的內容可以參考參考文獻[4],更多關于splint的使用可以參考splint 的官方手冊[4].
除了C有靜態的代碼工具以外,java中也有一款開源的功能強大的靜態代碼檢查工具FindBugs。
相關文章:
posted on 2013-08-06 10:44 順其自然EVO 閱讀(788) 評論(0) 編輯 收藏 所屬分類: 測試學習專欄