本文主要介绍了我在开发中引入代码静态扫描的实践

写在前面(个人现状)

与单元测试一样,在我从事的嵌入式行业,代码扫描的理念并深入人心,或者说在日常开发中其实已经使用了代码扫面工具但是不自知,首先,静态的代码扫描是发生在编译之前的,在日常使用IDE进行代码编辑时,有部分工作IDE其实已经在做了,例如:

  • 代码提示,上下文,头文件,定义缺失提示
  • 编码规则检查并提示,语法错误(当然有的语法错误是编译不通过的)

除了以上属于代码扫面的范围外,代码扫描还包括:

  • 编译可以通过的错误,如数组(指针)溢出,指针指向异常地址,除数为0等错误
  • 编码风格的检查,变量、函数等的命名规则
  • 适用于特定规则的检查,例如某个规则规定不允许使用switch分支,只能用if分支,可以进行约束

编码的缺陷

先举几个例子,在日常手写代码经常遇到的

经典案例1:

1
2
3
for (uint8_t i = 0; i < 256;i++) {
...
}

这段代码没有任何问题,然是运行的结果就是永远出不来,成了死循环;如果不预先静态检查,依赖于debug和运行调试才能发现。

经典案例2:

1
2
3
for (uint8_t i = 50; i >= 0;i--) {
...
}

这段代码好像也没有任何问题,但其实也是死循环;

案例1和案例2都可以通过静态扫面检出,在编写代码的时候就能发现;案例2也可以通过规则约束,如果制定了for循环条件中只能正向递增的规则,就能约束了。那么问题来了,为什么要制定约束呢?

还不是因为,在开发中经常容易写错,但是又不能保证人人都会去静态检测一遍,或者检查了忘记更改。干脆直接约束,禁用此写法

MISRA-C和CERT-C编码规范

在工业以及如今的汽车制造业,流行并且加快普及着这两种编码规范。MISRA-C规范侧重于从代码书写规则来避免可能出现的Bug,而CERT-C侧重于代码的安全。

本章,我不打算展开讲这两个规范,而是介绍如何使用开源工具Cppcheck来进行C代码的检查,以及加载MISRA和CERT的检查插件。

使用Cppcheck进行MISRA规则检查

开源版本的Cppckeck支持MISRA-2012大部分规则的检查。这里主要介绍Windows下命令方式使用Cppcheck进行静态检查;当然Cppcheck本身带一个图形化的GUI,由于笔者使用不多,这里就不介绍了。

安装Cppcheck

在sourceforge上下载Cppcheck,傻瓜安装到自己的电脑上。
链接如下: https://sourceforge.net/projects/cppcheck/files/

安装Python

如果你的电脑已经安装了Python并配好了环境变量可以不用再安装了。CppCheck中插件是基于Python来执行的,所以Python环境是必不可少的。

下载htmlreport脚本

这个脚本主要是用来讲扫描的结果导出成html报告方便我们审查。
链接如下:
https://github.com/danmar/cppcheck/blob/main/htmlreport/cppcheck-htmlreport

实际我们只是用它的Python脚本。我们可以将它放到Cppcheck的安装包目录下:

准备需要扫描的代码(工程)

完整的工程如下:

在示例中,我们以一个github上的开源代码为例,在导入项目中时,对该代码进行扫描。这里以笔者近期使用的SHA256哈希的C语言实现为例子。

准备一个命令行或者Shell环境

如果是在Windows下,是可以直接使用命令行的。但是为了项目开发的一致,我推荐使用ConEmu作为终端工具统一管理环境变量。

新建一个Console.bat脚本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@echo off

set SPCANPATH=%~dp0
set astmp=%ASPATH%
set ASDISK=%astmp:~1,2%

%ASDISK%
cd %SPCANPATH%

REM base env PATH
set PATH=C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0
set PATH=D:\OpenSourceSw\cppcheck;%PATH%
set PATH=D:\Anaconda3;D:\Anaconda3\Scripts;%PATH%

set ConEmu=E:\MyWorkLog\02_dev_tools\ConEmu\ConEmu64.exe

start %ConEmu% -title cppcheck ^
-runlist -new_console:d:"%SPCANPATH%":t:misra-2012

上面这段脚本主要时导入Python环境,笔者使用Anaconda进行Python环境管理,所以导入了Anaconda的路径,另外导入Cppcheck的安装路径,并启动ConEmu新建一个新的控制台。
记住,这个脚本只是用来启动可以执行代码扫描的环境终端。

准备Cppcheck检查的规则文件

Cppcheck支持文件过滤,引用文件路径,使用的插件等相关配置,这些配置我们都用独立的文件实现:

  • 代码源文件清单code_filelist.txt
  • 不好含文件夹/文件配置(支持正则表达)config_excludes.txt
  • 包含头文件路径配置 includes.txt
  • 启动MISRA插件配置misra-2021.json以及规则说明misra-2012.txt
  • 抑制配置,这个主要配需要屏蔽的错误suppressions.txt

尽管需要配置的文件很多,但是作为入门,我想config_excludes、supressions就啥都不用配了,保持有一个空文件即可

code_filelist.txt 代码文件主要填源码c文件:

1
E:\MyWorkLog\MainLine2\Autosar\AutosarZero\i01_source_code\Bsw\crypto\sha256.c 

includes.txt 头文件目录填用到的.h文件,像通用的,例如stdlib、stddef这些可以不用管,只需要关注用户的头文件:

1
2
3
E:\MyWorkLog\MainLine2\Autosar\AutosarZero\i01_source_code\BSW\common_includes
E:\MyWorkLog\MainLine2\Autosar\AutosarZero\i03_intergration_test
E:\MyWorkLog\MainLine2\Autosar\AutosarZero\i01_source_code\BSW\crypto

misra-2012.json:

1
2
3
4
5
6
{
"script": "misra.py",
"args": [
"--rule-texts=misra-2012.txt"
]
}

由于misra-2012规则版权限制,misra-2012.txt文件需利用互联网资源找一找,这里我就我贴出来。

准备好一切后,新建temp临时文件夹和report文件夹,然后编写cppcheck脚本,CppcheckReport.bat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@echo off
setlocal

@REM REM 获取当前日期和时间
set DATETIME=%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2%

@REM REM 设置 SRC_CC_REPORT 目录路径
set SRC_CC_REPORT="report"

@REM REM 检查 SRC_CC_REPORT 目录是否存在,如果不存在则创建
if not exist %SRC_CC_REPORT% (
mkdir %SRC_CC_REPORT%
)

@REM 在 SRC_CC_REPORT 目录下创建 REPORT_YYYYMMDD_HHMMSS 子目录
@REM set REPORT_SUB_DIR=%SRC_CC_REPORT%\RPT_%DATETIME%
set REPORT_SUB_DIR=%SRC_CC_REPORT%
mkdir %REPORT_SUB_DIR%
del /f /s /q %REPORT_SUB_DIR%\*.*

@REM set CPPCHECK_PATH="C:\Program Files\Cppcheck\cppcheck.exe"
@REM set ADDON_PATH="C:\opt\cppcheck\configurations\misra.json"

set SRC_FILELIST="code_filelist.txt"

set OUTPUT_XML="static_analysis_output.xml"
set CPPCHECK_HTMLREPORT_PATH="D:\OpenSourceSw\cppcheck\htmlreport\cppcheck-htmlreport"
set CPPCHECK_HTMLREPORT_TITLE="Demo"

set CPPCHECK_INCLUDES="includes.txt"
set CPPCHECK_SUPPRESSIONS_LIST="suppressions.txt"
set CPPCHECK_CONFIG_EXCLUDES_LIST="config_excludes.txt"

cppcheck --cppcheck-build-dir=temp --file-list=%SRC_FILELIST% --xml --config-excludes-file=%CPPCHECK_CONFIG_EXCLUDES_LIST% --suppressions-list=%CPPCHECK_SUPPRESSIONS_LIST% --platform=win64 -D__GNUC__ -DCPPCHECK --enable=all --includes-file=%CPPCHECK_INCLUDES% --addon=misra-2012.json 2>%OUTPUT_XML%

python %CPPCHECK_HTMLREPORT_PATH% --file=%OUTPUT_XML% --title=%CPPCHECK_HTMLREPORT_TITLE% --report-dir=%REPORT_SUB_DIR% --source-dir=.
@REM REM 删除xml文件
del %OUTPUT_XML%

endlocal

这个脚本所做的事情就是执行代码扫描,临时文件存放到temp,报告信息输出到report,记得替换掉相关文件路径,主要是CPPCHECK_HTMLREPORT_PATH和CPPCHECK_HTMLREPORT_TITLE
可以看到,相关配置文件都被加载进来了。

运行静态扫描

双击运行Console.bat,启动命令行,然后再命令行中执行CppcheckReport.bat

运行之后,从report中打开index.html文件会看到相关扫描结果出现了,会详细罗列不满足项,

甚至我们可以点进入查看具体代码信息:

写在后面

由于时间有限,文章更新没那么频繁,如果有相关疑问的,可以关注公号后回复“聪拌面技术交流”,推荐入群交流。