目前,云CI系统是一项需求很高的服务。 在本文中,我们将告诉您如何使用PVS-Studio中已经提供的工具将源代码分析集成到CI云平台中。 作为示例,我们将使用Travis CI服务。
为什么我们考虑使用第三方云而不自己做? 原因有很多,主要的原因是SaaS实施是一个昂贵且困难的过程。 实际上,将PVS-Studio分析直接集成到第三方云平台中是简单而琐碎的任务-无论是CircleCI,Travis CI,GitLab之类的开放平台,还是仅在某家公司中使用的特定企业解决方案。 因此,可以说PVS-Studio已经“在云端”可用。 另一个问题是实施和确保对基础设施24/7的访问。 这是一个更复杂的任务。 PVS-Studio不会直接提供自己的云平台来在其上运行分析。
有关二手软件的一些信息
Travis CI是用于构建和测试使用GitHub作为存储的软件的服务。 Travis CI不需要更改程序代码即可使用该服务。 所有设置都在存储库根目录中的文件
.travis.yml中进行。
我们将把
LXC (Linux容器)作为PVS-Studio的测试项目。 它是操作系统级别的虚拟化系统,用于在一个节点上启动Linux OS的多个实例。
该项目很小,但足以进行演示。 cloc命令的输出:
注意: LXC开发人员已经在使用Travis CI,因此我们将以他们的配置文件为基础并对其进行编辑。
构型
要开始使用Travis CI,请点击
链接并使用GitHub帐户登录。
在打开的窗口中,我们需要登录Travis CI。
授权后,它将重定向到欢迎页面“第一次来这里? 让您开始吧!”
,我们在其中提供了简短的说明,然后才能开始进行操作:
- 启用存储库;
- 在存储库中添加.travis.yml文件;
- 开始第一个构建。
让我们开始执行这些操作。
要将我们的存储库添加到Travis CI中,请通过
链接转到配置文件设置,然后按“激活”。
单击后,将打开一个窗口,以选择可以访问Travis CI应用程序的存储库。
注意:要提供对存储库的访问权限,您的帐户必须具有管理员权限。
之后,我们选择正确的存储库,单击“批准并安装”按钮确认选择,然后我们将被重定向回配置文件设置页面。
让我们添加一些变量,这些变量将用于创建分析器的许可证文件并发送其报告。 为此,我们将转到设置页面-所需存储库右侧的“设置”按钮。
设置窗口将打开。
设置的简要说明;
- “常规”部分-配置自动启动任务触发器;
- “自动取消”部分允许配置构建自动取消;
- “环境变量”部分允许定义既包含开放信息又包含机密信息的环境变量,例如登录信息,ssh-key;
- “ Cron Jobs”部分是任务运行计划的配置。
在“环境变量”部分,我们将创建变量
PVS_USERNAME和
PVS_KEY,分别包含静态分析器的用户名和许可证密钥。 如果您没有永久性的PVS-Studio许可证,则可以
申请试用许可证 。
在这里,我们将创建变量
MAIL_USER和
MAIL_PASSWORD ,其中包含用户名和电子邮件密码,将用于发送报告。
运行任务时,Travis CI从存储库根目录中的.travis.yml文件中获取指令。
通过使用Travis CI,我们既可以直接在虚拟机上运行静态分析,也可以使用预配置的容器来执行此操作。 这些方法的结果互不相同。 但是,使用预先配置的容器可能会很有用。 例如,如果我们已经有一个带有特定环境的容器,在其中构建并测试了软件产品,而我们又不想在Travis CI中恢复该环境。
让我们创建一个配置以在虚拟机上运行分析器。
为了构建和测试,我们将在Ubuntu Trusty上使用虚拟机,其描述可通过
链接获得 。
首先,我们指定该项目是用C编写的,并列出了将用于构建的编译器:
language: c compiler: - gcc - clang
注意:如果指定多个编译器,则每个任务将同时运行。
在这里阅读更多。
在构建之前,我们需要添加分析器存储库,设置依赖项和其他软件包:
before_install: - sudo add-apt-repository ppa:ubuntu-lxc/daily -y - wget -q -O - https:
在构建项目之前,我们需要准备您的环境:
script: - ./coccinelle/run-coccinelle.sh -i - git diff --exit-code - export CFLAGS="-Wall -Werror" - export LDFLAGS="-pthread -lpthread" - ./autogen.sh - rm -Rf build - mkdir build - cd build - ../configure --enable-tests --with-distro=unknown
接下来,我们需要创建一个许可证文件并开始分析项目。
然后,我们通过第一个命令为分析仪创建一个许可证文件。
$ PVS_USERNAME和
$ PVS_KEY变量的数据来自项目设置。
- pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic
通过下一条命令,我们开始跟踪项目构建。
- pvs-studio-analyzer trace -- make -j4
之后,我们进行静态分析。
注意:使用试用许可证时,需要指定参数
--disableLicenseExpirationCheck 。
- pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic -o PVS-Studio-${CC}.log --disableLicenseExpirationCheck
包含分析结果的文件由最后一条命令转换为html-report。
- plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html
由于TravisCI不允许您更改电子邮件通知的格式,因此在最后一步,我们将使用sendemail包发送报告:
- sendemail -t mail@domain.com -u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" -m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" -s smtp.gmail.com:587 -xu $MAIL_USER -xp $MAIL_PASSWORD -o tls=yes -f $MAIL_USER -a PVS-Studio-${CC}.log PVS-Studio-${CC}.html
这是用于在虚拟机上运行分析器的配置文件的全文:
language: c compiler: - gcc - clang before_install: - sudo add-apt-repository ppa:ubuntu-lxc/daily -y - wget -q -O - https:
要在容器中运行PVS-Studio,请使用以下Dockerfile预先创建它:
FROM docker.io/ubuntu:trusty ENV CFLAGS="-Wall -Werror" ENV LDFLAGS="-pthread -lpthread" RUN apt-get update && apt-get install -y software-properties-common wget \ && wget -q -O - https:
在这种情况下,配置文件可能如下所示:
before_install: - docker pull docker.io/oandreev/lxc env: - CC=gcc - CC=clang script: - docker run --rm --cap-add SYS_PTRACE -v $(pwd):/pvs -w /pvs docker.io/oandreev/lxc /bin/bash -c " ./coccinelle/run-coccinelle.sh -i && git diff --exit-code && ./autogen.sh && mkdir build && cd build && ../configure CC=$CC && pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic && pvs-studio-analyzer trace -- make -j4 && pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic -o PVS-Studio-$CC.log --disableLicenseExpirationCheck && plog-converter -t html -o PVS-Studio-$CC.html PVS-Studio-$CC.log && sendemail -t mail@domain.com -u 'PVS-Studio $CC report, commit:$TRAVIS_COMMIT' -m 'PVS-Studio $CC report, commit:$TRAVIS_COMMIT' -s smtp.gmail.com:587 -xu $MAIL_USER -xp $MAIL_PASSWORD -o tls=yes -f $MAIL_USER -a PVS-Studio-${CC}.log PVS-Studio-${CC}.html"
如您所见,在这种情况下,我们在虚拟机内部不执行任何操作,并且构建和测试项目的所有操作都在容器内进行。
注意 :启动容器时,需要指定参数
--cap-add SYS_PTRACE或
--security-opt seccomp:unconfined ,因为ptrace系统调用用于编译器跟踪。
接下来,我们将配置文件加载到存储库的根目录中,并看到Travis CI已收到有关项目更改的通知,并已自动开始构建。
可以在控制台中查看构建进度和分析器检查的详细信息。
测试结束后,我们将收到两封电子邮件:第一封-带有使用gcc构建项目的静态分析结果,第二封-分别针对clang。
简要介绍检查结果
总的来说,该项目非常干净,分析仪仅发出24个高不确定性警告和46个中等不确定性警告。 让我们看几个有趣的通知:
if中的冗余条件
V590考虑检查'ret!=(-1)&& ret == 1'表达式。 表达式过多或打印错误。 附件107
#define EOF -1 static struct lxc_proc_context_info *lxc_proc_get_context_info(pid_t pid) { .... while (getline(&line, &line_bufsz, proc_file) != -1) { ret = sscanf(line, "CapBnd: %llx", &info->capability_mask); if (ret != EOF && ret == 1)
如果
ret == 1 ,则绝对不等于-1(EOF)。 冗余检查,
ret!= EOF可以删除。
已发出两个类似的警告:
- V590考虑检查'ret!=(-1)&& ret == 1'表达式。 表达式过多或打印错误。 附件579
- V590考虑检查'ret!=(-1)&& ret == 1'表达式。 表达式过多或打印错误。 附件c.583
高位丢失
V784位掩码的大小小于第一个操作数的大小。 这将导致丢失更高的位。 1879年的会议
struct mount_opt { char *name; int clear; int flag; }; static void parse_mntopt(char *opt, unsigned long *flags, char **data, size_t size) { struct mount_opt *mo; for (mo = &mount_opt[0]; mo->name != NULL; mo++) { if (strncmp(opt, mo->name, strlen(mo->name)) == 0) { if (mo->clear) { *flags &= ~mo->flag;
在Linux下,
long是64位整数变量,
mo->标志是32位整数变量。 使用
mo->标志作为位掩码会导致丢失32个高位。 在按位求反后,将位掩码隐式转换为64位整数变量。 此掩码的高位可能会丢失。
我将通过一个示例展示它:
unsigned long long x; unsigned y; .... x &= ~y;
这是正确的代码版本:
*flags &= ~(unsigned long)(mo->flag);
分析仪发出了另一个类似的警告:
- V784位掩码的大小小于第一个操作数的大小。 这将导致丢失更高的位。 1933年
可疑循环
V612循环内无条件的“返回”。 3477号会议
#define lxc_list_for_each(__iterator, __list) \ for (__iterator = (__list)->next; __iterator != __list; \ __iterator = __iterator->next) static bool verify_start_hooks(struct lxc_conf *conf) { char path[PATH_MAX]; struct lxc_list *it; lxc_list_for_each (it, &conf->hooks[LXCHOOK_START]) { int ret; char *hookname = it->elem; ret = snprintf(path, PATH_MAX, "%s%s", conf->rootfs.path ? conf->rootfs.mount : "", hookname); if (ret < 0 || ret >= PATH_MAX) return false; ret = access(path, X_OK); if (ret < 0) { SYSERROR("Start hook \"%s\" not found in container", hookname); return false; } return true; // <= } return true; }
循环在第一次迭代时开始并中断。 这可能是有意做的,但是在这种情况下,可以省略循环。
数组索引超出范围
V557阵列欠载是可能的。 “字节-1”索引的值可能达到-1。 网络.c 2570
static int lxc_create_network_unpriv_exec(const char *lxcpath, const char *lxcname, struct lxc_netdev *netdev, pid_t pid, unsigned int hooks_version) { int bytes; char buffer[PATH_MAX] = {0}; .... bytes = lxc_read_nointr(pipefd[0], &buffer, PATH_MAX); if (bytes < 0) { SYSERROR("Failed to read from pipe file descriptor"); close(pipefd[0]); } else { buffer[bytes - 1] = '\0'; } .... }
从管道读取缓冲区中的字节。 如果发生错误,
lxc_read_nointr函数将返回负值。 如果一切顺利,最后一个元素将写入一个终端null。 但是,如果读取0个字节,则索引将超出缓冲区范围,从而导致未定义的行为。
分析仪发出了另一个类似的警告:
- V557阵列欠载是可能的。 “字节-1”索引的值可能达到-1。 网络.c 2725
缓冲区溢出
V576格式错误。 考虑检查“ sscanf”函数的第三个实际参数。 使用不带宽度说明的字符串说明符很危险。 缓冲区溢出是可能的。 lxc_unshare.c 205
static bool lookup_user(const char *oparg, uid_t *uid) { char name[PATH_MAX]; .... if (sscanf(oparg, "%u", uid) < 1) { if (sscanf(oparg, "%s", name) < 1)
在这种情况下,使用
sscanf可能很危险,因为如果
oparq缓冲区大于
名称缓冲区,则在形成
名称缓冲区时索引将超出范围。
结论
如我们所见,在云中配置静态代码分析器检查是一个非常简单的任务。 为此,我们只需要在存储库中添加一个文件,而花费很少的时间来设置CI系统。 结果,我们将获得在编写代码阶段检测问题的工具。 该工具使我们可以防止错误进入测试的下一阶段,在该阶段进行修正将需要大量时间和精力。
当然,PVS-Studio在云平台上的使用不仅限于Travis CI。 与本文中描述的方法类似,只有一点点不同,PVS-Studio分析可以集成到其他流行的云CI解决方案中,例如CircleCI,GitLab等。
有用的链接
- 有关在Linux和MacOS上运行PVS-Studio的更多信息,请单击链接 。
- 您还可以通过链接阅读有关使用已安装的PVS-Studio静态代码分析器创建,设置和使用容器的信息。
- TravisCI文档 。