代码静态分析工具splint

By | 05月25日
Advertisement

静态程序分析

先来说说什么是“静态程序分析(Static program analysis)”,静态程序分析是指使用自动化工具软件对程序源代码进行检查,以分析程序行为的技术,应用于程序的正确性检查、安全缺陷检测、程序优 化等。它的特点就是不执行程序,相反,通过在真实或模拟环境中执行程序进行分析的方法称为“动态程序分析(Dynamic program analysis)”。

那在什么情况下需要进行静态程序分析呢?静态程序分析往往作为一个多人参与的项目中代码审查过程的一个阶段,因编写完一部分代码之后就可以进行静态 分析,分析过程不需要执行整个程序,这有助于在项目早期发现以下问题:变量声明了但未使用、变量类型不匹配、变量在使用前未定义、不可达代码、死循环、数 组越界、内存泄漏等。

静态分析工具在代码通过编译之后再对代码进行分析。我们会问:静态分析工具与编译器相比,所做的工作有什么不同?静态分析工具相比 编译器,对代码进行了更加严格的检查,像数组越界访问、内存泄漏、使用不当的类型转换等问题,都可以通过静态分析工具检查出来,我们甚至可以在分析工具的 分析标准里定义代码的编写规范,在检测到不符合编写规范的代码时抛出告警,这些功能都是编译器没有的。

既然静态分析工具发挥了不小的作用,何不在编译器里兼备静态分析的功能?对于这个问题,S. C. Johnson(他是最古老的静态分析工具Lint的作者)在其1978年发表的论文《Lint, a C Program Checker》中 给出了他的答案:“Lint与C编译器在功能上的分离既有历史原因,也有现实的意义。编译器负责把C源程序快速、高效地转变为可执行文件,不对代码做类型 检查(特别是对分别编译的程序),有益于做到快速与高效。而Lint没有“高效”的要求,可以花更多时间对代码进行更深入、仔细的检查。”

针对空指针提取、未定义变量使用、类型转换、内存管理、函数接口定义等,我们可以在静态分析工具里制定不同的检测标准,以下曲线图说明了在使用splint进行分析时,检测标准与splint运行的开销所对应的关系,从另一个角度看,也说明了静态分析工具与编译器的关系:

代码静态分析工具splint

splint

掌握了“静态分析”等概念之后,我们再来看splint。

在Linux命令行下,splint的使用很简单,检测文件*.c,只要这样使用就可以了:

splint *.c

1.splint消息

我们通过以下例子来认识典型的splint告警信息:

 1 //splint_msg.c
 2 int func_splint_msg1(void)
 3 {
 4  int a;
 5  return 0;
 6 }
 7 int func_splint_msg2(void)
 8 {
 9  int* a = (int*)malloc(sizeof(int));
10     a = NULL;
11  return 0;
12 }

运行splint splint_msg.c之后,我们来看输出的告警信息:

splint_msg.c: (in function func_splint_msg1)
splint_msg.c:4:6: Variable a declared but not used
  A variable is declared but never used. Use /*@[email protected]*/ in front of
  declaration to suppress message. (Use -varuse to inhibit warning)

splint_msg.c: (in function func_splint_msg2)
splint_msg.c:10:2: Fresh storage a (type int *) not released before assignment:
                      a = NULL
  A memory leak has been detected. Storage allocated locally is not released
  before the last reference to it is lost. (Use -mustfreefresh to inhibit
  warning)
   splint_msg.c:9:37: Fresh storage a created

Finished checking --- 2 code warnings

蓝色字体部分:给出告警所在函数名,在函数的第一个警告消息报告前打印;

红色字体部分:消息的正文,文件名、行号、列号显示在的警告的正文前;

黑色字体部分:是有关该可疑错误的详细信息,包含一些怎样去掉这个消息的信息;

绿色字体部分:给出格外的位置信息,这里消息给出了是在哪里申请了这个可能泄露的内存。

2.检查控制

splint提供了三种方式可进行检查的控制,分别是.splintrc配置文件、flags标志和格式化注释。

flags:splint支持几百个标志用来控制检查和消息报告,使用时标志前加’+‘或’-’,'+'标志开启这个标志,'-'表示关闭此标志,下面例子展示了flags标志的用法:

splint -showcol a.c   //在检测a.c时,告警消息中列数不被打印
splint -varuse  a.c   //在检测a.c时,告警消息中未使用变量告警不被打印

.splintrc配置文件:在使用源码安装splint之后,.splintrc文件将被安装在主目录下,.splintrc文件中对一些标志作了默认的设定,命令行中指定的flags标志会覆盖.splintrc文件中的标志。

格式化注释:格式化注释提供一个类型、变量或函数的格外的信息,可以控制标志设置,增加检查效果,所有格式化注释都以/*@开始,@*/结束,比如在函数参数前加/*@[email protected]*/,表示该参数可能是NULL,做检测时,splint会加强对该参数的值的检测。

3.检测分析内容

1.解引用空指针(Null Dereferences)

在Unix操作系统中,解引用空指针将导致我们在程序运行时产生段错误(Segmentation fault),一个简单的解引用空指针例子如下:

1 //null_dereferences.c
2 int func_null_dereferences(void)
3 {
4  int* a = NULL;
5  return *a;
6 }

执行splint null_dereference.c命令,将产生以下告警消息:

null_dereference.c: (in function func_null_dereferences)
null_dereference.c:5:10: Dereference of null pointer a: *a
  A possibly null pointer is dereferenced.  Value is either the result of a
  function which may return null (in which case, code should check it is not
  null), or a global, parameter or structure field declared with the null
  qualifier. (Use -nullderef to inhibit warning)
   null_dereference.c:4:11: Storage a becomes null

Finished checking --- 1 code warnin

2.类型(Types)

我们在编程中经常用到强制类型转换,将有符号值转换为无符号值、大范围类型值赋值给小范围类型,程序运行的结果会出无我们的预料。

1 //types.c
2 void splint_types(void)
3 {
4  short a = 0;
5  long b = 32768;
6     a = b;
7  return;
8 }

执行splint types.c命令,将产生以下告警消息:

types.c: (in function splint_types)
types.c:6:2: Assignment of long int to short int: a = b
  To ignore type qualifiers in type comparisons use +ignorequals.

Finished checking --- 1 code warning

3.内存管理(Memory Management)

C语言程序中,将近半数的bug归功于内存管理问题,关乎内存的bug难以发现并且会给程序带来致命的破坏。由内存释放所产生的问题,我们可以将其分为两种:

  • 当尚有其他指针引用的时候,释放一块空间
1 //memory_management1.c
2 void memory_management1(void)
3 {
4  int* a = (int*)malloc(sizeof(int));
5  int* b = a;
6     free(a);
7  *b = 0;
8  return;
9 }

在上面这个例子中,指针a与b指向同一块内存,但在内存释放之后仍对b指向的内容进行赋值操作,我们来看splint
memory_management1.c的结果:

memory_management1.c: (in function memory_management1)
memory_management1.c:7:3: Variable b used after being released
  Memory is used after it has been released (either by passing as an only param
  or assigning to an only global). (Use -usereleased to inhibit warning)
   memory_management1.c:6:7: Storage b released
memory_management1.c:7:3: Dereference of possibly null pointer b: *b
  A possibly null pointer is dereferenced.  Value is either the result of a
  function which may return null (in which case, code should check it is not
  null), or a global, parameter or structure field declared with the null
  qualifier. (Use -nullderef to inhibit warning)
   memory_management1.c:5:11: Storage b may become null

Finished checking --- 2 code warnings

检查结果中包含了两个告警,第一个指出我们使用了b指针,而它所指向的内存已被释放;第二个是对解引用空指针的告警。

  • 当最后一个指针引用丢失的时候,其指向的空间尚未释放
1 //memory_management2.c
2 void memory_management2(void)
3 {
4  int* a = (int*)malloc(sizeof(int));
5      a = NULL;
6  return;
7 }

这个例子中内存尚未释放,就将指向它的唯一指针赋值为NULL,我们来看splint memory_management2.c的检测结果:

memory_management2.c: (in function memory_management2)
memory_management2.c:5:2: Fresh storage a (type int *) not released before assignment:
                 a = NULL
  A memory leak has been detected. Storage allocated locally is not released
  before the last reference to it is lost. (Use -mustfreefresh to inhibit
  warning)
   memory_management2.c:4:37: Fresh storage a created

Finished checking --- 1 code warning

splint抛出一个告警:类型为int*的a在进行a = NULL赋值前没有释放新分配的空间。

4.缓存边界(Buffer Sizes)

splint会对数组边界、字符串边界作检测,使用时需要加上+bounds的标志,我们来看下面的例子:

1 //bounds1.c
2 void bounds1(void)
3 {
4  int a[10];
5     a[10] = 0;
6  return;
7 }

使用splint +bounds bounds1.c命令对其进行检测,结果如下:

bounds1.c: (in function bounds1)
bounds1.c:5:2: Likely out-of-bounds store: a[10]
    Unable to resolve constraint:
    requires 9 >= 10
     needed to satisfy precondition:
    requires maxSet(a @ bounds1.c:5:2) >= 10
  A memory write may write to an address beyond the allocated buffer. (Use
  -likelyboundswrite to inhibit warning)

Finished checking --- 1 code warning

告警消息提示数组越界,访问超出我们申请的buffer大小范围。再看一个例子:

 1 //bounds2.c
 2 void bounds2(char* str)
 3 {
 4  char* tmp = getenv("HOME");
 5  if(tmp != NULL)
 6     {
 7         strcpy(str, tmp);
 8     }
 9  return;
10 }

不对这个例子进行详细检查,可能我们不能发现其中隐含的问题,执行splint +bounds bounds2.c之后,会抛出如下告警:

bounds2.c: (in function bounds2)
bounds2.c:7:3: Possible out-of-bounds store: strcpy(str, tmp)
    Unable to resolve constraint:
    requires maxSet(str @ bounds2.c:7:10) >= maxRead(getenv("HOME") @
    bounds2.c:4:14)
     needed to satisfy precondition:
    requires maxSet(str @ bounds2.c:7:10) >= maxRead(tmp @ bounds2.c:7:15)
     derived from strcpy precondition: requires maxSet(<parameter 1>) >=
    maxRead(<parameter 2>)
  A memory write may write to an address beyond the allocated buffer. (Use
  -boundswrite to inhibit warning)

Finished checking --- 1 code warning

告警消息提示我们:在使用strcpy(str, tmp)进行字符串复制时,可能出现越界错误,因为str的大小可能不足以容纳环境变量“HOME”对应的字符串。绿色字体的内容指示了如何消除告警消息。

小结

这里仅给出了splint检查的4种检测:解引用空指针、类型、内存管理、缓存边界,除此之外,splint还对宏(Macros)、函数接口 (Function Interfaces)、控制流(Control Flow)等内容作检测,很多检测标志和格式化注释都未在本文中提到,更详细的内容请查看splint使用手册

不管pc-lint、splint等静态程序分析工具的功能多么强大,它们对程序的检查也有疏漏的地方,工具的使用并不能提高我们的编程能力,我们更应该通过它们学习各种编码错误和代码隐患,凭积累的编码知识把程序隐患扼杀在摇篮里。

你在项目实施中是否遇到过隐藏的bug,导致返工呢?在你的项目中是否使用了静态程序分析工具,它起到多大的作用?说出来与大家一块分享吧~

Reference:

《Splint Manual》

《Lint, a C Program Checker》

感谢,Thanks!

作者:iTech
出处:http://itech.cnblogs.com/
本文版权归作者iTech所有,转载请包含作者签名和出处,不得用于商业用途,非则追究法律责任!

Similar Posts:

  • 代码静态分析工具PC-LINT安装配置--step by step

    代码静态分析工具PC-LINT安装配置--step by step 作者:ehui928 2006-5-20 PC-Lint是C/C++软件代码静态分析工具,你可以把它看作是一种更加严格的编译器.它不仅可以检查出一般的语法错误,还可以检查出那些虽然符合语法要求但不易发现的潜在错误. C语言的灵活性带来了代码效率的提升,但相应带来了代码编写的随意性,另外C编译器不进行强制类型检查,也带来了代码编写的隐患.PCLint识别并报告C语言中的编程陷阱和格式缺陷的发生.它进行程序的全局分析,能识别没有被适

  • 在Eclipse环境下的Java代码静态分析工具介绍

    在Eclipse环境下有很多插件可以帮助我们做代码的静态分析工作,这样可以有助于我们尽早的发现代码的Bug.下面我介绍几种常用的插件: 1. PMD 我们可以通过http://pmd.sourceforge.net/eclipse来为Eclipse安装PMD插件. PMD是一种基于静态规则的Java源代码分析工具.它附带了一些可以直接使用的规则,用户也可以自己定义规则,检查Java代码是否存在潜在的问题: 可能的Bug - 比如空的try/catch/finally/switch语句块 无用的代

  • C++代码静态分析工具-Prefast 和 Fxcop

    这是2个使用比较广泛的工具.

  • 静态代码分析工具汇总(转)

    静态代码扫描,借用一段网上的原文解释一下(这里叫静态检查):"静态测试包括代码检查.静态结构分析.代码质量度量等.它可以由人工进行,充分发挥人的逻辑思维优势,也可以借助软件工具自动进行.代码检查代码检查包括代码走查.桌面检查.代码审查等,主要检查代码和设计的一致性,代码对标准的遵循.可读性,代码的逻辑表达的正确性,代码结构的合理性等方面:可以发现违背程序编写标准的问题,程序中不安全.不明确和模糊的部分,找出程序中不可移植部分.违背程序编程风格的问题,包括变量检查.命名和类型审查.程序逻辑审查.程

  • 开源静态分析工具androguard体验(一)

    虽然在windows端免费版的IDA.VTS等工具都可用来静态分析,但相对来说这些工具用来人工分析在合适不过,但对于项目开发,相对来说都忽视很好扩展.而在androguard却很适合进行扩展或者移植成为自己项目的某一模块,虽然早有耳闻,但也最近才动手体验几把.当然,androguard也被很多人再次开发过了,包括一些提供上传入口的检测网站,静态分析部分感觉都有使用该模块,很多静态分析工具都基于它,其中还有一款ApkInspector也很出名. androguard主要用来进行静态分析,其默认采用

  • 源代码静态分析工具

    代码写的不严谨,常常会让你的软件出现很多不可预测的错误,而且这种错误有时会表现的很随机性, 这会给你分析和定位问题带来难度. 我们可以利用一些优秀的源代码静态分析工具帮助我们检查很多诸如变量未初始化.内存泄漏.空指针引用. 缓冲区溢出以及数组越界等很多编程时可能会不经意犯下的错误,让软件更加稳定和健壮. 比较有名的工具有klocwork.lint等. 案例一内存泄漏之检查: p1 = malloc(10); if (p1 == null) { return; } p2 = malloc(20);

  • C# 静态代码检查工具StyleCop

    stylecop是一个分析C#源代码的程序,它执行一套统一风格和一致性规则,可以运行在Visual Studio或集成到MSBuild项目.stylecop也被集成到许多第三方开发工具中,应用范围适中. 官方网站:http://stylecop.codeplex.com/ 目前最新版本 4.7 "代码审查"或是"代码评审"(Code Review),这是一个流程,当开发人员写好代码后,需要让别人来review一下他的代码,这是一种有效发现BUG的方法.由此,我们可以

  • 静态分析工具可以突出更深层次的问题

    由FindBugs.PMD.CheckStyle以及IntelliJ IDEA等等提供的静态代码分析(Static code analysis,SCA)工具可以帮助开发团队捕捉到代码中的问题,来保证程序的高质量.但是当SCA工具标记了一个问题之后,团队应该如何作出反应呢?Vikas Hazrati在"静态代码分析仅仅是冰山一角"一文中建议:深入探索. 如果团队对SCA标记的问题达成了共识,那么他们会修复这个问题.然而,在很多情况下,被标记的问题可以突出一些更深层次的问题,而且这些问题隐

  • 更新整理本人所有博文中提供的代码与工具(C++,2013.11)

    为了更方便地管理博文中涉及的各种代码与工具资源,现在把这些资源迁移到 Google Code 中,有兴趣者可前往下载. C++ 1.<通用高性能 Windows Socket 组件 HP-Socket v3.0.2 正式发布> <基于 IOCP 的通用异步 Windows Socket TCP 高性能服务端组件的设计与实现> <通用异步 Windows Socket TCP 客户端组件的设计与实现> 摘要:编写 Windows Socket TCP 客户端其实并不困难,

  • 重复代码检查工具simian的基本用法

    simian是一个检查重复代码的工具,支持通过命令行和UI方式来检查代码,可以检查多种语言(比如C\C++, java, c#等)的代码,常见的编程语言都支持,下面先来看看如何使用命令行来检查c++重复代码的. E:\temp\simian-2.3.33\bin>simian-2.3.33.exe -includes="D:\code\test\**\*.hpp" -threshold=3 -formatter=xml:e:\temp\simian1.xml "*.rb

Tags: