SonarQube分析跟踪C语言项目代码质量

SonarQube安装及配置方法见 CentOS 8 安装 sonarqube 7.9.1 LTS

安装C语言插件

SonarQube的Community版本是不支持对C语言代码进行质量分析的,Developer及以上付费版本才支持。还好有一些开源的社区插件支持,比较著名的一个就是sonar-cxx。
在如下地址下载SonarQube开源C和C++的插件
C插件:sonar-c-plugin-1.3.1.1807.jar
https://github.com/SonarOpenCommunity/sonar-cxx/releases/download/cxx-1.3.1/sonar-c-plugin-1.3.1.1807.jar
C++插件:sonar-cxx-plugin-1.3.1.1807.jar
https://github.com/SonarOpenCommunity/sonar-cxx/releases/download/cxx-1.3.1/sonar-cxx-plugin-1.3.1.1807.jar
将下载下来的插件放到SonarQube的安装目录中的extensions/plugins中

1
2
3
4
5
6
7
8
9
[sonar@localhost plugins]$ pwd
# 写这个文档用的SonarQube环境的安装目录为/opt/sonarqube-7.9.1
/opt/sonarqube-7.9.1/extensions/plugins
[sonar@localhost plugins]$ ls -lh
total 103M
-rw-r--r--. 1 sonar sonar  224 Jul 10 12:21 README.txt
-rw-r--r--. 1 sonar sonar 8.1M Dec  9 17:45 sonar-c-plugin-1.3.1.1807.jar
-rw-r--r--. 1 sonar sonar 8.1M Dec  9 17:46 sonar-cxx-plugin-1.3.1.1807.jar
# 为了节省显示,省略掉了一些输出

安装完成后,使用安装目录下/bin/linux-x86-64/sonar.sh脚本重启SonarQube使安装生效。

1
2
3
4
5
6
7
8
9
10
[sonar@localhost linux-x86-64]$ pwd
/opt/sonarqube-7.9.1/bin/linux-x86-64
[sonar@localhost linux-x86-64]$ ./sonar.sh restart
Gracefully stopping SonarQube...
Waiting for SonarQube to exit...
Waiting for SonarQube to exit...
Stopped SonarQube.
Starting SonarQube...
Started SonarQube.
[sonar@localhost linux-x86-64]$

重启完成后,登录SonarQube页面,在“质量配置”页面中可以看到增加了关于C和C++的质量配置(Profile),如下所示:


需要注意,因Sonar way为内建不可更改的质量规则,即使安装了C和C++的插件,关于C和C++的代码规则都还是0,需要新建C和C++的质量规则。另外,从右侧的侧栏可以看到安装完C和C++的插件后,新增了13K的未激活的规则。

设置C语言配置

在"质量配置"页面,点击右上角的“创建”按钮,创建新的C语言的配置,在弹出的对话框中输出配置名称,如C-Profile, 语言选择C(Community)。


创建完成后,在"质量配置"页面,可以看到新创建的C语言配置和已有的Sonar way配置,默认配置为Sonar way, 两个配置的规则数都为0。先将新建的C-Profile设置为默认配置:


设置完成后,在“质量配置”页面可以看到C语言的配置规则为C-Profile:


接下来需为为C-Profile填加规则。C-Profile右侧的设置按钮有”激活更多规则“命令,点击它。


在弹出的"代码规则"页面,使用"批量修改"命令里面的"活动..."在弹出的对话框里选择新创建的C-Profile,点击“应用”。


完成后,在“质量配置”页面,可以看到C-Profile的规则数的变化:


到此C语言的质量配置已经完成,可以创建项目对C语言工程进行质量检查了。

创建项目

在"项目"页面右上角的"+"按钮中有“创建新的项目”命令,如果没有创建过项目,在页面中间也有“创建新项目”按键,使用任一方法进行项目创建的页面:


在"创建新项目"页面输入"项目标识"和”显示名“点击“设置”按钮进入项目页面。


在项目页面,按照步骤提示,第1步需要创建项目的令牌。项目令牌用于项目分析中的认证,可以避免使用用户名和密码。


令牌创建完成后,第2步需要设置被分析代码的语言,有"Java", "C# 或 VB.NET", "其它(JS, Python, PHP ...)"三个选项,C语言项目选择 "其它(JS, Python, PHP ...)"。
然后选择代码分析工具运行的操作系统。SonarQube不要求代码分析与SonarQube服务器在同一个机器或系统上,可以在其它机器上使用sonar-scanner进行代码分析,sonar-scanner会自动将分析结果传到SonarQube服务器中。
接下来,如果没有扫描器,即sonar-scanner工具,需要先下载他,使用页面中的下载按钮即可下载。在下载页面有相应的安装说明,参考它安装即可。
最后,需要将执行sonar-scanner的命令复制下来,以便在进行代码分析时使用。


使用SonarQube进行代码分析

创建需要进行质量分析的代码,如果已有代码可以跳过这步。本文使用如下代码进行验证:

1
2
3
4
5
6
[bfx@vFedora sonar-test]$ ls -lh
总用量 12K
-rw-rw-r-- 1 bfx bfx  341 12月  2 14:22 example.c
-rw-rw-r-- 1 bfx bfx 1.1K 12月  2 15:15 example-utest.c
-rw-rw-r-- 1 bfx bfx  482 12月  2 14:59 SConstruct
[bfx@vFedora sonar-test]$

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
/**
 * example.c
 * 测试示例代码
 * */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef UTEST
extern void utest();
#endif

/**
 * CUint单元测试被测示例函数
 */
int func1(int a){
    if (a > 200){
        return 1;
    }else{
        return 2;
    }
}

/**
 * SonarQube代码质量分析示例函数
 * 返回临时变量的地址,用来检查SonarQube是否正常工作
 */
char *func2(void){
    char *name[256];
    return name;
}

int main(){
    printf("Hello
");
#ifdef UTEST
    utest();
#endif
    return 0;
}
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**
 * example-utest.c
 * 单元测试示例代码
 * */
#include <stdio.h>
#include <stdlib.h>
#include <CUnit/CUnit.h>
#include <CUnit/Automated.h>

extern int func1(int a);

/**
 * CUnit单元测试用例
 */
void test_func1_case1(void){
    CU_ASSERT(func1(0) == 1);
}

/**
 * CUnit单元测试用例
 */
void test_func1_case2(void){
    CU_ASSERT(func1(100) == 2);
}


/**
 * CUnit单元测试套件初始化函数
 */
int suit_init(void){
    return 0;
}

/**
 * CUnit单元测试套件退出函数
 */
int suit_clean(void){
    return 0;
}

/**
 * 单元测试入口
 */
void utest(void){
    CU_ErrorCode ret;
    CU_pSuite suit;
    CU_pTest test;
   
    /* 注册CUnit单元测试组件*/
    ret = CU_initialize_registry();
    if (ret != CUE_SUCCESS){
        printf("Regist unit test failed!
");
        exit(-1);
    }
   
    /* 添加单元测试套件*/
    suit = CU_add_suite("Sonar-Test", suit_init, suit_clean);
    if (suit == NULL){
        printf("Add suit failed!
");
        exit(-1);
    }

    /* 添加单元测试用例*/
    test = CU_add_test(suit, "Test Case1", test_func1_case1);
    if (test == NULL){
        printf("Add test failed!
");
        exit(-1);
    }

    /* 添加单元测试用例*/
    test = CU_add_test(suit, "Test Case2", test_func1_case2);
    if (test == NULL){
        printf("Add test failed!
");
        exit(-1);
    }

    /* 设置单元测试用例结果输出到文件*/
    CU_set_output_filename("CUnit-example.xml");
    /* 将测试用例输出到文件*/
    CU_list_tests_to_file();
    /* 运行测试用例*/
    CU_automated_run_tests();

    return;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# SConstruct
# 基于Scons的编译脚本

# 编译Release版本,用于程序正常发布
release = Environment()
robj = release.Object("example-r", "example.c")
rbin = release.Program("example", robj)
# 默认编译Release版本
Default(rbin)

# 编译单元测试版本,用于测试目的。
utest = Environment(LIBS = ['cunit'],
                CCFLAGS = ['-fprofile-arcs', '-ftest-coverage','-DUTEST'],
                LINKFLAGS = ['-fprofile-arcs', '-ftest-coverage']
                )
utobj = utest.Object("example-ut", "example.c")
utobj += utest.Object("example-utest.c")
utbin = utest.Program("example-ut", utobj)

说明:本文使用CUint单元测试的示例代码及scons编译脚本,仅是因为手头正好有一套这样的代码,SonarQube与CUnit和scons无任何关系

开源的SonarQube C和C++插件本身没有代码分析的功能,仅对分析结果进行解析及显示,需要使用cppcheck进行代码分析。另外,需要在项目中设置cppcheck结果的保存位置:
在“项目”->项目名称->“配置”->"(2) Code analysis"->"Cppcheck report(s)"中输出cppcheck检查结果的文件, 如cppcheck.xml, 保存。


在源代码目录运行cppcheck并将结果保存到XML文件中,如下:

1
2
3
4
5
6
7
[bfx@vFedora sonar-test]$ cppcheck --enable=all --xml-version=2 ./ 2>cppcheck.xml
Checking example-utest.c ...
1/2 files checked 75% done
Checking example.c ...
Checking example.c: UTEST...
2/2 files checked 100% done
[bfx@vFedora sonar-test]$

在代码目录运行在项目创建时复制的sonar-scanner命令,如下:

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
[bfx@vFedora sonar-test]$ ls
example.c  example-utest.c  SConstruct
[bfx@vFedora sonar-test]$ sonar-scanner
>   -Dsonar.projectKey=C-Test
>   -Dsonar.sources=.
>   -Dsonar.host.url=http://172.17.7.166:9000
>   -Dsonar.login=65d0fadd90121b65a8448dd03b7ea6abccb801c2
INFO: Scanner configuration file: /opt/sonar-scanner-4.2.0.1873-linux/conf/sonar-scanner.properties
INFO: Project root configuration file: NONE

# 为了节省显示,省略掉了一些输出

INFO: Load active rules (done) | time=9449ms
WARN: SCM provider autodetection failed. Please use "sonar.scm.provider" to define SCM of your project, or disable the SCM Sensor in the project settings.
INFO: Indexing files...
INFO: Project configuration:
INFO: ------------------------------------------------------------------------
INFO: EXECUTION FAILURE
INFO: ------------------------------------------------------------------------
INFO: Total time: 13.845s
INFO: Final Memory: 5M/20M
INFO: ------------------------------------------------------------------------
ERROR: Error during SonarQube Scanner execution
ERROR: Language of file 'example-utest.c' can not be decided as the file matches patterns of both sonar.lang.patterns.c++ : **/*.cxx,**/*.cpp,**/*.cc,**/*.c,**/*.hxx,**/*.hpp,**/*.hh,**/*.h and sonar.lang.patterns.c : **/*.c,**/*.h
ERROR:
ERROR: Re-run SonarQube Scanner using the -X switch to enable full debug logging.
[bfx@vFedora sonar-test]$

运行到最后会报错,错误原因是不能决定使用sonar.lang.patterns.c++还是使用sonar.lang.patterns.c解析.c文件。这是因为前面安装的sonar-c-plugin和sonar-cxx-plugin插件都有对.c和.h扩展名的设置,因为我们分析的是C语言工程,可以将sonar-cxx-plugin的配置去掉,如下:


去掉后重新运行sonar-scanner命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[bfx@vFedora sonar-test]$ sonar-scanner
>   -Dsonar.projectKey=C-Test
>   -Dsonar.sources=.
>   -Dsonar.host.url=http://172.17.7.166:9000
>   -Dsonar.login=65d0fadd90121b65a8448dd03b7ea6abccb801c2
INFO: Scanner configuration file: /opt/sonar-scanner-4.2.0.1873-linux/conf/sonar-scanner.properties
INFO: Project root configuration file: NONE
INFO: SonarQube Scanner 4.2.0.1873

# 为了节省显示,省略掉了一些输出

INFO: Turn debug info on to get more details (sonar-scanner -X -Dsonar.verbose=true ...).
INFO: Analysis total time: 14.121 s
INFO: ------------------------------------------------------------------------
INFO: EXECUTION SUCCESS
INFO: ------------------------------------------------------------------------
INFO: Total time: 16.078s
INFO: Final Memory: 6M/30M
INFO: ------------------------------------------------------------------------
[bfx@vFedora sonar-test]$

运行成功后,可以在SonarQube的"项目"页面查看运行的结果:


SonarQube C语言的代码覆盖率

从上面的运行结果可以看到项目的覆盖率为0%. 其实可以借助lconv和gconvr使SonarQube跟踪代码运行的覆盖率。
要生产覆盖率的数据,需要要编译和链接时进行如下设置:
编译时:增加-fprofile-arcs -ftest-coverage或–coverage,
链接时:增加 -fprofile-arcs 或者 –lgcov。
打开–g3 选项,去掉-O2以上级别的代码优化选项;否则编译器会对代码做一些优化,例如行合并,从而影响行覆盖率结果;
如在我们前面的示例代码的编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[bfx@vFedora sonar-test]$ gcc -o example-ut -fprofile-arcs -ftest-coverage -DUTEST -g3 -lcunit example.c example-utest.c
example.c: 在函数‘func2’中:
example.c:30:12: 警告:returning ‘char **’ from a function with incompatible return type ‘char *’ [-Wincompatible-pointer-types]
   30 |     return name;
      |            ^~~~
example.c:30:12: 警告:函数返回局部变量的地址 [-Wreturn-local-addr]
[bfx@vFedora sonar-test]$ ls -lh
总用量 112K
-rw-rw-r-- 1 bfx bfx 1.4K 12月  9 20:43 cppcheck.xml
-rw-rw-r-- 1 bfx bfx  512 12月  9 20:13 example.c
-rw-rw-r-- 1 bfx bfx  952 12月 10 10:52 example.gcno
-rwxrwxr-x 1 bfx bfx  88K 12月 10 10:52 example-ut
-rw-rw-r-- 1 bfx bfx 1.6K 12月  9 20:20 example-utest.c
-rw-rw-r-- 1 bfx bfx 2.9K 12月 10 10:52 example-utest.gcno
-rw-rw-r-- 1 bfx bfx  496 12月  9 20:24 SConstruct
[bfx@vFedora sonar-test]$

可以看到编译生成的可执行文件及用户生成覆盖率数据的*.gcno文件。
运行./example-ut 测试程序,运行结束后,会针对所有的源代码文件产生相应的*.gcda文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[bfx@vFedora sonar-test]$ ./example-ut
Hello
[bfx@vFedora sonar-test]$ ls -lh
总用量 128K
-rw-rw-r-- 1 bfx bfx 1.4K 12月  9 20:43 cppcheck.xml
-rw-rw-r-- 1 bfx bfx 1.8K 12月 10 10:57 CUnit-example.xml-Listing.xml
-rw-rw-r-- 1 bfx bfx 1.8K 12月 10 10:57 CUnit-example.xml-Results.xml
-rw-rw-r-- 1 bfx bfx  512 12月  9 20:13 example.c
-rw-rw-r-- 1 bfx bfx  164 12月 10 10:57 example.gcda
-rw-rw-r-- 1 bfx bfx  952 12月 10 10:52 example.gcno
-rwxrwxr-x 1 bfx bfx  88K 12月 10 10:52 example-ut
-rw-rw-r-- 1 bfx bfx 1.6K 12月  9 20:20 example-utest.c
-rw-rw-r-- 1 bfx bfx  364 12月 10 10:57 example-utest.gcda
-rw-rw-r-- 1 bfx bfx 2.9K 12月 10 10:52 example-utest.gcno
-rw-rw-r-- 1 bfx bfx  496 12月  9 20:24 SConstruct
[bfx@vFedora sonar-test]$

使用gconvr生成XML格式的覆盖率报告,开源SonarQube C语言插件sonar-c-plugin需要使用XML格式的覆盖率报告,这可能与付费版本的不同。
sonar-c-plugin关于覆盖率跟踪的WIKI描述如下,链接:https://github.com/SonarOpenCommunity/sonar-cxx/wiki/Coverage-tracers

SonarQube官方CFamily的设置如下,链接
https://docs.sonarqube.org/latest/analysis/coverage/

生成覆盖率报告:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[bfx@vFedora sonar-test]$ gcovr -r ./ -x --object-directory=$(pwd) > report.xml
[bfx@vFedora sonar-test]$ ls -lh
总用量 132K
-rw-rw-r-- 1 bfx bfx 1.4K 12月  9 20:43 cppcheck.xml
-rw-rw-r-- 1 bfx bfx 1.8K 12月 10 10:57 CUnit-example.xml-Listing.xml
-rw-rw-r-- 1 bfx bfx 1.8K 12月 10 10:57 CUnit-example.xml-Results.xml
-rw-rw-r-- 1 bfx bfx  512 12月  9 20:13 example.c
-rw-rw-r-- 1 bfx bfx  164 12月 10 10:57 example.gcda
-rw-rw-r-- 1 bfx bfx  952 12月 10 10:52 example.gcno
-rwxrwxr-x 1 bfx bfx  88K 12月 10 10:52 example-ut
-rw-rw-r-- 1 bfx bfx 1.6K 12月  9 20:20 example-utest.c
-rw-rw-r-- 1 bfx bfx  364 12月 10 10:57 example-utest.gcda
-rw-rw-r-- 1 bfx bfx 2.9K 12月 10 10:52 example-utest.gcno
-rw-rw-r-- 1 bfx bfx 3.1K 12月 10 11:35 report.xml
-rw-rw-r-- 1 bfx bfx  496 12月  9 20:24 SConstruct
[bfx@vFedora sonar-test]$

设置SonarQube读取覆盖率报告:在"项目"->项目名称->"配置"->"C (Community)"->"(3) Testing & Coverage"->"Unit test coverage report(s)"中输入覆盖率报告的路径:


029 - sonar-coverage-setting.png

运行sonar-scanner工具进行代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[bfx@vFedora sonar-test]$ sonar-scanner
>   -Dsonar.projectKey=C-Test
>   -Dsonar.sources=.
>   -Dsonar.host.url=http://172.17.7.166:9000
>   -Dsonar.login=65d0fadd90121b65a8448dd03b7ea6abccb801c2
INFO: Scanner configuration file: /opt/sonar-scanner-4.2.0.1873-linux/conf/sonar-scanner.properties
INFO: Project root configuration file: NONE

# 为了节省显示,省略了部分输出

INFO: Turn debug info on to get more details (sonar-scanner -X -Dsonar.verbose=true ...).
INFO: Analysis total time: 14.160 s
INFO: ------------------------------------------------------------------------
INFO: EXECUTION SUCCESS
INFO: ------------------------------------------------------------------------
INFO: Total time: 16.597s
INFO: Final Memory: 6M/30M
INFO: ------------------------------------------------------------------------
[bfx@vFedora sonar-test]$

在SonarQube项目页面可以看到项目的覆盖率信息,也可以看到每行代码的覆盖率情况: