Travis CI是用于构建和测试软件的分布式Web服务,该服务使用GitHub作为源代码托管。 除了上述方案,您还可以添加自己的方案,这要归功于广泛的配置选项。 在本文中,我们将使用示例PPSSPP代码将Travis CI配置为与PVS-Studio一起使用。
引言
Travis CI是用于构建和测试软件的Web服务。 它通常与持续集成的实践结合使用。
PPSSPP -PSP游戏机模拟器。 该程序能够从为Sony PSP设计的磁盘映像中模拟任何游戏的启动。 该程序于2012年11月1日发布。 PPSSPP已获得GPL v2的许可。 任何人都可以
对项目的
源代码进行改进。
PVS-Studio是静态代码分析器,用于搜索程序代码中的错误和潜在漏洞。 在本文中,为了进行更改,我们将不在本地开发人员的计算机上而是在云中启动PVS-Studio,并在PPSSPP中查找错误。
配置Travis CI
我们将需要在GitHub上有一个需要我们所在项目的存储库,以及PVS-Studio的密钥(您可以获取
试用密钥或
免费获得Open Source项目 )。
让我们转到
Travis CI网站。 使用GitHub帐户授权后,我们将获得一个存储库列表:
为了测试,我分叉了PPSSPP。
我们激活我们要收集的存储库:
目前,Travis CI无法组装我们的项目,因为没有组装说明。 因此,是时候进行配置了。
在分析过程中,一些变量对我们有用,例如,PVS-Studio的密钥,这在配置文件中无法指定。 因此,使用Travis CI中的构建设置添加环境变量:
我们将需要:
- PVS_USERNAME-用户名
- PVS_KEY-键
- MAIL_USER-将用于发送报告的电子邮件
- MAIL_PASSWORD-电子邮件密码
最后两个是可选的。 它们将用于通过邮件发送结果。 如果要以其他方式发送报告,则无需指定它们。
因此,我们添加了所需的环境变量:
现在创建一个
.travis.yml文件并将其放在项目的根目录中。 Travis CI的配置文件已经存在于PPSSPP中,但是它太大了,对于示例来说是完全不合适的,因此我不得不对其进行大幅简化,只保留基本元素。
首先,我们说明语言,我们要在虚拟机中使用的Ubuntu Linux版本以及程序集所需的软件包:
language: cpp dist: xenial addons: apt: update: true packages: - ant - aria2 - build-essential - cmake - libgl1-mesa-dev - libglu1-mesa-dev - libsdl2-dev - pv - sendemail - software-properties-common sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' - sourceline: 'ppa:ubuntu-sdk-team/ppa'
列出的所有软件包仅适用于PPSSPP。
现在指定组装矩阵:
matrix: include: - os: linux compiler: "gcc" env: PPSSPP_BUILD_TYPE=Linux PVS_ANALYZE=Yes - os: linux compiler: "clang" env: PPSSPP_BUILD_TYPE=Linux
有关
矩阵部分的更多信息。 在Travis CI中,有两种创建构建选项的方法:第一种是列出编译器,操作系统类型,环境变量等,然后生成所有可能组合的矩阵。 第二个是矩阵的明确指示。 当然,您可以将这两种方法结合起来并添加唯一的大小写,或者使用
排除部分来
排除 。
在Travis CI文档中了解有关此内容的更多信息。
仍需指定特定于项目的组装说明:
before_install: - travis_retry bash .travis.sh travis_before_install install: - travis_retry bash .travis.sh travis_install script: - bash .travis.sh travis_script after_success: - bash .travis.sh travis_after_success
Travis CI允许您为虚拟机生命的各个阶段添加自己的团队。 在安装软件包之前执行
before_install节。 然后
安装 ,该安装将在上面指示的
addons.apt列表中安装软件包之后进行。 程序集本身在
脚本中进行 。 如果一切顺利,那么我们进入
after_success (将在本节中运行静态分析)。 这些不是所有可以修改的步骤;如果需要更多步骤,则应查看
Travis CI文档 。
为了便于阅读,将命令移至单独的脚本
.travis.sh ,该脚本位于项目的根目录中。
因此,我们有了以下
.travis.yml文件:
language: cpp dist: xenial addons: apt: update: true packages: - ant - aria2 - build-essential - cmake - libgl1-mesa-dev - libglu1-mesa-dev - libsdl2-dev - pv - sendemail - software-properties-common sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' - sourceline: 'ppa:ubuntu-sdk-team/ppa' matrix: include: - os: linux compiler: "gcc" env: PVS_ANALYZE=Yes - os: linux compiler: "clang" before_install: - travis_retry bash .travis.sh travis_before_install install: - travis_retry bash .travis.sh travis_install script: - bash .travis.sh travis_script after_success: - bash .travis.sh travis_after_success
在安装软件包之前,请更新子模块。 这是构建PPSSPP所必需的。 在
.travis.sh中添加第一个函数(注意扩展名):
travis_before_install() { git submodule update --init --recursive }
现在,我们直接设置PVS-Studio,使其在Travis CI中自动启动。 首先,我们需要在系统上安装PVS-Studio软件包:
travis_install() { if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-4.8 fi if [ "$PVS_ANALYZE" = "Yes" ]; then wget -q -O - https:
在
travis_install函数的开头
,我们使用环境变量安装所需的编译器。 然后,如果变量
$ PVS_ANALYZE存储值“
是” (在组装矩阵的配置过程中在
env节中指定了值),则将安装
pvs-studio软件包。 除此之外,还指示软件包
libio-socket-ssl-perl和
libnet-ssleay-perl ,但是它们是通过邮件发送结果所必需的,因此,如果您选择其他报告发送方法,则不需要它们。
download_extract函数下载并解压缩指定的归档文件:
download_extract() { aria2c -x 16 $1 -o $2 tar -xf $2 }
现在是时候整理一个项目了。 这发生在
脚本部分:
travis_script() { if [ -d cmake-3.6.2-Linux-x86_64 ]; then export PATH=$(pwd)/cmake-3.6.2-Linux-x86_64/bin:$PATH fi CMAKE_ARGS="-DHEADLESS=ON ${CMAKE_ARGS}" if [ "$PVS_ANALYZE" = "Yes" ]; then CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}" fi cmake $CMAKE_ARGS CMakeLists.txt make }
实际上,这是简化的原始配置,但以下几行除外:
if [ "$PVS_ANALYZE" = "Yes" ]; then CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}" fi
在此部分代码中,我们将编译命令的export标志设置为
cmake 。 这对于静态代码分析器是必需的。 您可以在文章“
如何在Linux和macOS上运行PVS-Studio ”中阅读有关此内容的更多信息。
如果汇编成功,那么我们将在
after_success中进行静态分析:
travis_after_success() { if [ "$PVS_ANALYZE" = "Yes" ]; then pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic \ -o PVS-Studio-${CC}.log \ --disableLicenseExpirationCheck plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html 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 fi }
让我们更详细地考虑以下几行:
pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic \ -o PVS-Studio-${CC}.log \ --disableLicenseExpirationCheck plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html
第一行从设置Travis CI环境变量时一开始就指定的用户名和密钥生成许可证文件。
第二行直接开始分析。
-j <N>标志设置要分析的流的数量,
-l <file>标志指示许可证,
-o <file>标志定义用于日志输出的文件,并且
-disableLicenseExpirationCheck标志对于试用版
是必需的,因为默认情况下
pvs- studio-analyzer会警告用户许可证已到期。 为防止这种情况,可以指定此标志。
日志文件包含未经转换就无法读取的原始输出,因此必须首先使文件可读。
我们通过
plog-converter跳过日志,并且输出是html文件。
在此示例中,我决定使用
sendemail命令通过邮件发送报告。
结果,我们得到了以下
.travis.sh文件:
#/bin/bash travis_before_install() { git submodule update --init --recursive } download_extract() { aria2c -x 16 $1 -o $2 tar -xf $2 } travis_install() { if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-4.8 fi if [ "$PVS_ANALYZE" = "Yes" ]; then wget -q -O - https:
现在是将更改添加到git存储库的时候了,之后Travis CI将自动开始构建。 点击“ ppsspp”进入组装报告:
我们将看到当前程序集的概述:
如果组装成功完成,我们将收到一封包含静态分析结果的电子邮件。 当然,邮寄并不是获得报告的唯一方法。 您可以选择任何实现方法。 但是重要的是要记住,组装完成后将无法访问虚拟机的文件。
错误摘要
我们已经成功完成了最困难的部分。 现在,让我们确保我们所有的努力都是合理的。 考虑一下有关静态分析的报告中的一些有趣的观点,该报告是通过邮件发送给我的(我没有指出任何内容)。
危险的优化
void sha1( unsigned char *input, int ilen, unsigned char output[20] ) { sha1_context ctx; sha1_starts( &ctx ); sha1_update( &ctx, input, ilen ); sha1_finish( &ctx, output ); memset( &ctx, 0, sizeof( sha1_context ) ); }
PVS-Studio警告:
V597编译器可以删除“
内存集 ”函数调用,该函数调用用于刷新“求和”缓冲区。 RtlSecureZeroMemory()函数应用于擦除私有数据。 sha1.cpp 325
该代码段位于安全哈希模块中,但是,它包含一个严重的安全漏洞(
CWE-14 )。 考虑编译Debug版本时生成的汇编程序列表:
; Line 355 mov r8d, 20 xor edx, edx lea rcx, QWORD PTR sum$[rsp] call memset ; Line 356
一切都井井有条,并且执行了
memset函数,从而覆盖了RAM中的重要数据,但是到目前为止,还不高兴。 考虑经过优化的发行版的汇编程序列表:
; 354 : ; 355 : memset( sum, 0, sizeof( sum ) ); ; 356 :}
从清单中可以看出,编译器忽略了
memset调用。 这是因为
sha1函数在调用
memset之后不再调用
ctx结构。 因此,编译器看不到将来浪费处理器时间来覆盖未使用的内存的意义。 您可以使用
RtlSecureZeroMemory函数或
类似功能修复此问题。
正确地:
void sha1( unsigned char *input, int ilen, unsigned char output[20] ) { sha1_context ctx; sha1_starts( &ctx ); sha1_update( &ctx, input, ilen ); sha1_finish( &ctx, output ); RtlSecureZeroMemory(&ctx, sizeof( sha1_context ) ); }
不必要的比较
static u32 sceAudioOutputPannedBlocking (u32 chan, int leftvol, int rightvol, u32 samplePtr) { int result = 0;
PVS-Studio
警告 :
V547表达式'leftvol> = 0'始终为true。 sceAudio.cpp 120
注意第一个
if的else分支。 仅当所有条件都为
leftvol> 0xFFFF ||时 ,代码才会执行。
rightvol> 0xFFFF || leftvol <0 || rightvol <0将被证明是错误的。 因此,我们得到以下对于else分支都是正确的语句:
leftvol <= 0xFFFF ,
rightvol <= 0xFFFF ,
leftvol> = 0和
rightvol> = 0 。 注意最后两个语句。 检查执行这段代码的先决条件是否有意义?
因此,我们可以冷静地删除以下条件语句:
static u32 sceAudioOutputPannedBlocking (u32 chan, int leftvol, int rightvol, u32 samplePtr) { int result = 0;
另一种情况。 这些冗余条件的背后是某种错误。 也许他们没有检查要求什么。
Ctrl + C Ctrl + V反击
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) { if (!Memory::IsValidAddress(psmfData) || !Memory::IsValidAddress(psmfData)) { return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address"); } .... }
V501在“ ||”的左侧和右侧有相同的子表达式“!Memory :: IsValidAddress(psmfData)” 操作员。 scePsmf.cpp 703
注意是否检查内部。 您检查地址
psmfData是否有效
两倍是否对您来说并不奇怪? 因此,对我来说似乎很奇怪……实际上,我们当然有一个错字,其想法是检查两个输入参数。
正确的选项:
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) { if (!Memory::IsValidAddress(psmfStruct) || !Memory::IsValidAddress(psmfData)) { return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address"); } .... }
被遗忘的变量
extern void ud_translate_att( int size = 0; .... if (size == 8) { ud_asmprintf(u, "b"); } else if (size == 16) { ud_asmprintf(u, "w"); } else if (size == 64) { ud_asmprintf(u, "q"); } .... }
PVS-Studio
警告 :
V547表达式'size == 8'始终为false。 195
该错误位于
ext文件夹中,因此不适用于该项目,但是在我注意到此错误之前就已发现该错误,因此我决定将其保留。 仍然,本文不是关于错误的回顾,而是关于与Travis CI的集成,并且没有进行分析仪的配置。
变量的
大小由一个常量初始化,但是,直到
if语句为止,它在代码中根本没有使用过,它在检查条件时当然会返回
false ,因为我们记得,
大小为零。 后续检查也没有意义。
显然,代码片段的作者忘记了之前覆盖变量
大小 。
停下
在此上,也许我们最终会出错。 本文的目的是演示与Travis CI结合使用PVS-Studio的工作,而不是尽可能全面地分析项目。 如果您想要越来越多的美丽错误,那么可以随时
在这里欣赏它们:)。
结论
结合增量分析的实践,使用Web服务来构建项目可以在代码合并后立即检测到许多问题。 的确,单个程序集可能还不够,因此将测试与静态分析结合起来可以显着提高代码质量。
有用的链接

如果您想与讲英语的读者分享这篇文章,请使用以下链接:Maxim Zvyagintsev。
如何以PSP游戏机模拟器为例在Travis CI中设置PVS-Studio 。