如何使用 PSP 游戏控制台模拟器的示例在 Travis CI 中配置 PVS-Studio

如何使用 PSP 游戏控制台模拟器的示例在 Travis CI 中配置 PVS-Studio
Travis CI 是一种分布式 Web 服务,用于构建和测试使用 GitHub 作为源代码托管的软件。 除了上述操作场景之外,您还可以通过广泛的配置选项添加自己的操作场景。 在本文中,我们将使用 PPSSPP 代码示例配置 Travis CI 以与 PVS-Studio 配合使用。

介绍

特拉维斯CI 是用于构建和测试软件的 Web 服务。 它通常与持续集成实践结合使用。

PPSSPP — PSP 游戏机模拟器。 该程序能够模拟从适用于索尼 PSP 的磁盘映像启动任何游戏。 该计划于1年2012月2日发布。 PPSSPP 根据 GPL vXNUMX 获得许可。 任何人都可以做出改进 项目源代码.

PVS工作室 — 静态代码分析器,用于搜索程序代码中的错误和潜在漏洞。 在本文中,为了进行更改,我们将不在开发人员的计算机本地启动 PVS-Studio,而是在云端启动,并在 PPSSPP 中查找错误。

设置 Travis CI

我们需要 GitHub 上的存储库(我们需要的项目所在的位置),以及 PVS-Studio 的密钥(您可以获取 试用密钥 или 免费用于开源项目).

我们去网站看看吧 特拉维斯CI。 使用您的 GitHub 帐户授权后,我们将看到存储库列表:

如何使用 PSP 游戏控制台模拟器的示例在 Travis CI 中配置 PVS-Studio
为了测试,我分叉了 PPSSPP。

我们激活我们想要收集的存储库:

如何使用 PSP 游戏控制台模拟器的示例在 Travis CI 中配置 PVS-Studio
目前,Travis CI 无法构建我们的项目,因为没有构建说明。 所以是时候进行配置了。

在分析过程中,有些变量对我们来说是有用的,例如PVS-Studio的key,我们不希望在配置文件中指定这些变量。 因此,让我们使用 Travis CI 中的构建设置添加环境变量:

如何使用 PSP 游戏控制台模拟器的示例在 Travis CI 中配置 PVS-Studio
我们需要:

  • PVS_USERNAME - 用户名
  • PVS_KEY - 键
  • MAIL_USER - 将用于发送报告的电子邮件
  • MAIL_PASSWORD - 电子邮件密码

最后两项是可选的。 这些将用于通过邮件发送结果。 如果您想以其他方式分发报告,则无需指明。

所以,我们添加了我们需要的环境变量:

如何使用 PSP 游戏控制台模拟器的示例在 Travis CI 中配置 PVS-Studio
现在让我们创建一个文件 .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中,有两种创建构建选项的方法:第一种是指定编译器、操作系统类型、环境变量等列表,之后生成所有可能组合的矩阵; 第二个是矩阵的明确指示。 当然,您可以结合这两种方法并添加一个独特的案例,或者相反,使用该部分排除它 排除。 您可以阅读有关此内容的更多信息 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 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
}

现在我们直接在 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 用于导出编译命令的标志。 这对于静态代码分析器是必需的。 您可以在文章“如何在 Linux 和 macOS 上运行 PVS-Studio«。

如果组装成功,那么我们就可以 成功后,我们进行静态分析:

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”转到构建报告:

如何使用 PSP 游戏控制台模拟器的示例在 Travis CI 中配置 PVS-Studio
我们将看到当前构建的概述:

如何使用 PSP 游戏控制台模拟器的示例在 Travis CI 中配置 PVS-Studio
如果构建成功完成,我们将收到一封包含静态分析结果的电子邮件。 当然,邮寄并不是接收报告的唯一方式。 您可以选择任何实施方法。 但重要的是要记住,构建完成后,将无法访问虚拟机文件。

错误总结

我们已经成功地完成了最困难的部分。 现在让我们确保我们所有的努力都是值得的。 让我们看一下通过邮件发送给我的静态分析报告中的一些有趣的点(我指出它并非毫无意义)。

危险的优化

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 编译器可以删除“memset”函数调用,该函数调用用于刷新“sum”缓冲区。 应使用 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

一切都井然有序且功能齐全 记忆集 被执行,从而覆盖 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 警告: V547 表达式“leftvol >= 0”始终为真。 sceAudio.cpp 120

首先注意 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");
  }
  ....
}

V501 “||”的左侧和右侧有相同的子表达式“!Memory::IsValidAddress(psmfData)” 操作员。 scePsmf.cpp 703

注意里面的检查 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 警告: V547 表达式“size == 8”始终为 false。 同步-att.c 195

该错误位于文件夹中 分机,所以与项目不太相关,但在我注意到之前就发现了这个错误,所以我决定离开它。 毕竟本文不是要审查错误,而是要与 Travis CI 集成,并且没有进行分析器的配置。

变量 尺寸 由常量初始化,但是,在代码中根本没有使用它,直到运算符 if,这当然给出了 false 在检查条件时,因为,正如我们所记得的, 尺寸 等于零。 后续的检查也没有任何意义。

显然,代码片段的作者忘记覆盖变量 尺寸 在那之前。

Stop 停止

这就是我们可能会犯错误的地方。 本文的目的是演示 PVS-Studio 与 Travis CI 一起的工作,而不是尽可能彻底地分析该项目。 如果你想要更大更漂亮的错误,你总是可以欣赏它们 这里 :)。

结论

使用Web服务构建项目,再加上增量分析的实践,可以让你在合并代码后立即发现很多问题。 然而,一次构建可能还不够,因此将测试与静态分析一起设置将显着提高代码的质量。

有用的链接

如何使用 PSP 游戏控制台模拟器的示例在 Travis CI 中配置 PVS-Studio

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

来源: habr.com

添加评论