Travis CI 是一种分布式 Web 服务,用于构建和测试使用 GitHub 作为源代码托管的软件。 除了上述操作场景之外,您还可以通过广泛的配置选项添加自己的操作场景。 在本文中,我们将使用 PPSSPP 代码示例配置 Travis CI 以与 PVS-Studio 配合使用。
介绍
设置 Travis CI
我们需要 GitHub 上的存储库(我们需要的项目所在的位置),以及 PVS-Studio 的密钥(您可以获取
我们去网站看看吧
为了测试,我分叉了 PPSSPP。
我们激活我们想要收集的存储库:
目前,Travis CI 无法构建我们的项目,因为没有构建说明。 所以是时候进行配置了。
在分析过程中,有些变量对我们来说是有用的,例如PVS-Studio的key,我们不希望在配置文件中指定这些变量。 因此,让我们使用 Travis CI 中的构建设置添加环境变量:
我们需要:
- PVS_USERNAME - 用户名
- PVS_KEY - 键
- MAIL_USER - 将用于发送报告的电子邮件
- MAIL_PASSWORD - 电子邮件密码
最后两项是可选的。 这些将用于通过邮件发送结果。 如果您想以其他方式分发报告,则无需指明。
所以,我们添加了我们需要的环境变量:
现在让我们创建一个文件 .travis.yml 并将其放置在项目的根目录中。 PPSSPP 已经有一个 Travis CI 的配置文件,但是它太大了,完全不适合这个例子,所以我们必须大大简化它,只留下基本元素。
首先,我们指定要在虚拟机中使用的语言、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中,有两种创建构建选项的方法:第一种是指定编译器、操作系统类型、环境变量等列表,之后生成所有可能组合的矩阵; 第二个是矩阵的明确指示。 当然,您可以结合这两种方法并添加一个独特的案例,或者相反,使用该部分排除它 排除。 您可以阅读有关此内容的更多信息
剩下的就是提供特定于项目的组装说明:
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 允许您为虚拟机生命周期的各个阶段添加自己的命令。 部分 安装前 在安装软件包之前执行。 然后 安装,它遵循列表中的软件包的安装 插件.apt我们在上面指出了这一点。 组装本身发生在 脚本。 如果一切顺利的话,我们就会发现自己处于 成功后 (我们将在本节中运行静态分析)。 这些并不是所有可以修改的步骤,如果您需要更多,那么您应该查看
为了便于阅读,命令被放置在单独的脚本中 .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
}
现在我们直接在 Travis CI 中设置 PVS-Studio 自动启动。 首先我们需要在系统上安装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://files.viva64.com/etc/pubkey.txt
| sudo apt-key add -
sudo wget -O /etc/apt/sources.list.d/viva64.list
https://files.viva64.com/etc/viva64.list
sudo apt-get update -qq
sudo apt-get install -qq pvs-studio
libio-socket-ssl-perl
libnet-ssleay-perl
fi
download_extract
"https://cmake.org/files/v3.6/cmake-3.6.2-Linux-x86_64.tar.gz"
cmake-3.6.2-Linux-x86_64.tar.gz
}
在函数的开头 特拉维斯安装 我们使用环境变量安装所需的编译器。 那么如果变量 $PVS_ANALYZE 储存价值 Yes (我们在章节中指出了 ENV 在构建矩阵配置期间),我们安装该包 pvs工作室。 除此之外,还标明了套餐 libio-socket-ssl-perl и libnet-ssleay-perl但是,它们是邮寄结果所必需的,因此如果您选择了其他方法来发送报告,则不需要它们。
功能 下载解压 下载并解压指定的存档:
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
在这部分代码中我们设置为 cmake 用于导出编译命令的标志。 这对于静态代码分析器是必需的。 您可以在文章“
如果组装成功,那么我们就可以 成功后,我们进行静态分析:
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 [email protected]
-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 设置用于分析的线程数、标志 -l 表示许可证、标志 -o 定义输出日志的文件,以及标志 -禁用许可证过期检查 试用版需要,因为默认情况下 pvs-studio-分析仪 将警告用户许可证即将过期。 为了防止这种情况发生,您可以指定此标志。
日志文件包含未经转换就无法读取的原始输出,因此您必须首先使该文件可读。 让我们传递日志 plog转换器,输出是一个 html 文件。
在此示例中,我决定使用以下命令通过邮件发送报告 发电子邮件.
结果我们得到了如下文件 .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://files.viva64.com/etc/pubkey.txt
| sudo apt-key add -
sudo wget -O /etc/apt/sources.list.d/viva64.list
https://files.viva64.com/etc/viva64.list
sudo apt-get update -qq
sudo apt-get install -qq pvs-studio
libio-socket-ssl-perl
libnet-ssleay-perl
fi
download_extract
"https://cmake.org/files/v3.6/cmake-3.6.2-Linux-x86_64.tar.gz"
cmake-3.6.2-Linux-x86_64.tar.gz
}
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
}
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 [email protected]
-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
}
set -e
set -x
$1;
现在是时候将更改推送到 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 警告:
这段代码位于安全哈希模块中,但是,它包含一个严重的安全漏洞(
; Line 355
mov r8d, 20
xor edx, edx
lea rcx, QWORD PTR sum$[rsp]
call memset
; Line 356
一切都井然有序且功能齐全 记忆集 被执行,从而覆盖 RAM 中的重要数据,但是,现在还不要高兴。 我们看一下Release版本优化后的程序集清单:
; 354 :
; 355 : memset( sum, 0, sizeof( sum ) );
; 356 :}
从清单中可以看出,编译器忽略了调用 记忆集。 这是因为在函数中 sha1 通话后 记忆集 不再参考结构 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;
// For some reason, this is the only one that checks for negative.
if (leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0) {
....
} else {
if (leftvol >= 0) {
chans[chan].leftVolume = leftvol;
}
if (rightvol >= 0) {
chans[chan].rightVolume = rightvol;
}
chans[chan].sampleAddress = samplePtr;
result = __AudioEnqueue(chans[chan], chan, true);
}
}
PVS-Studio 警告:
首先注意 else 分支 if。 仅当满足所有条件时才会执行代码 左卷 > 0xFFFF || 右卷 > 0xFFFF || 左体积 < 0 || 右体积 < 0 将会被证明是错误的。 因此,我们得到以下语句,这对于 else 分支来说也是正确的: 左体积 <= 0xFFFF, 右体积 <= 0xFFFF, 左体积 >= 0 и 右体积 >= 0。 注意最后两条语句。 检查执行这段代码的必要条件是什么有意义吗?
所以我们可以安全地删除这些条件语句:
static u32 sceAudioOutputPannedBlocking
(u32 chan, int leftvol, int rightvol, u32 samplePtr) {
int result = 0;
// For some reason, this is the only one that checks for negative.
if (leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0) {
....
} else {
chans[chan].leftVolume = leftvol;
chans[chan].rightVolume = rightvol;
chans[chan].sampleAddress = samplePtr;
result = __AudioEnqueue(chans[chan], chan, true);
}
}
另一种情况。 这些多余的条件背后隐藏着某种错误。 也许他们没有检查需要什么。
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");
}
....
}
注意里面的检查 if。 您不觉得我们检查地址是否有效很奇怪吗? psmf数据, 两倍? 所以这对我来说似乎很奇怪......事实上,这当然是一个拼写错误,其想法是检查两个输入参数。
正确选项:
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 警告:
该错误位于文件夹中 分机,所以与项目不太相关,但在我注意到之前就发现了这个错误,所以我决定离开它。 毕竟本文不是要审查错误,而是要与 Travis CI 集成,并且没有进行分析器的配置。
变量 尺寸 由常量初始化,但是,在代码中根本没有使用它,直到运算符 if,这当然给出了 false 在检查条件时,因为,正如我们所记得的, 尺寸 等于零。 后续的检查也没有任何意义。
显然,代码片段的作者忘记覆盖变量 尺寸 在那之前。
Stop 停止
这就是我们可能会犯错误的地方。 本文的目的是演示 PVS-Studio 与 Travis CI 一起的工作,而不是尽可能彻底地分析该项目。 如果你想要更大更漂亮的错误,你总是可以欣赏它们
结论
使用Web服务构建项目,再加上增量分析的实践,可以让你在合并代码后立即发现很多问题。 然而,一次构建可能还不够,因此将测试与静态分析一起设置将显着提高代码的质量。
有用的链接
如果您想与英语读者分享这篇文章,请使用翻译链接:Maxim Zvyagintsev。
来源: habr.com