软件测试实验学习笔记系列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。
相关文章:
软件测试实验学习笔记系列1
软件测试实验学习笔记系列3--单元测试