How to set up PVS-Studio in Travis CI using the example of PSP game console emulator

в 7:39, , рубрики: bugs, c++, devops, Gamedev, gamedevelopment, open source, pvs-studio, static code analysis, travis-ci, Блог компании PVS-Studio, облачные сервисы, разработка игр
PPSSPP

Travis CI is a distributed web service for building and testing software that uses GitHub as a source code hosting service. In addition to the above scripts, you can add your own, thanks to the extensive configuration options. In this article we will set up Travis CI for working with PVS-Studio by the example of PPSSPP code.

Introduction

Travis CI is a web service for building and testing software. It is usually used in combination with the practice of continuous integration.

PPSSPP is an emulator of PSP game console. The program is able to emulate the launch of any game with images of discs designed for Sony PSP. The program was released on November 1, 2012. PPSSPP is distributed under GPL v2 license. Anyone can make improvements to the source code of the project.

PVS-Studio — static code analyzer for searching errors and potential vulnerabilities in program code. In this article, we will launch PVS-Studio in the cloud instead of locally on the developer's computer for a variety of purposes and will search for errors in PPSSPP.

Travis CI set up

We will need a repository on GitHub where the project we need is located, as well as a key for PVS-Studio (you can get a trial key or a free one for Open Source projects).

Let's go to Travis CI site. After authorization with the help of GitHub account we will have a list of repositories:

How to set up PVS-Studio in Travis CI using the example of PSP game console emulator - 2

For the test, I made a PPSSPP fork.

We activate the repository which we want to build:

How to set up PVS-Studio in Travis CI using the example of PSP game console emulator - 3

At the moment, Travis CI can't build our project because there are no instructions for building it. That's why it's time for the configuration.

During the analysis we will need some variables, for example, the key for PVS-Studio, which would be undesirable to specify in the configuration file. So, let's add environment variables by configuring the build in Travis CI:

How to set up PVS-Studio in Travis CI using the example of PSP game console emulator - 4

We will need:

  • PVS_USERNAME – user name
  • PVS_KEY — key
  • MAIL_USER — email that will be used to send the report
  • MAIL_PASSWORD – email password

The last two are optional. They will be used to send the results by mail. If you want to send the report in another way, you do not need to specify them.

So, we have added the environment variables we need:

How to set up PVS-Studio in Travis CI using the example of PSP game console emulator - 5

Now let's create a .travis.yml file and put it in the root of the project. PPSSPP already had a configuration file for Travis CI, however, it was too large and not suitable for the example, so we had to simplify it and leave only the basic elements.

First, let's specify the programming language, the version of Ubuntu Linux that we want to use on the virtual machine, and the necessary packages for building:

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'

All added packages are only needed for PPSSPP.

Now specify the building matrix:

matrix:
  include:
    - os: linux
      compiler: "gcc"
      env: PPSSPP_BUILD_TYPE=Linux PVS_ANALYZE=Yes
    - os: linux
      compiler: "clang"
      env: PPSSPP_BUILD_TYPE=Linux

A bit more about the matrix section. In Travis CI there are two ways to create build variants: the first one is to specify compilers, types of operating systems, environment variables etc. with the list, after which the matrix of all possible combinations will be generated; the second one is an explicit indication of the matrix. Of course, you can combine these two approaches and add a unique case, or, on the contrary, exclude it by using the exclude section. You can read more about this in the Travis CI documentation.

The only thing left to do is to specify project-specific build instructions:

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 allows you to add your own commands for different stages of virtual machine life. The before_install section runs before installing the packages. Then install, which follows the installation of the packages from the addons.apt list that we have specified above. The build itself takes place in script. If everything has been successful, we get into after_success (this is where we will start static analysis). These are not all the steps you can modify, if you need more, you should look in the documentation on Travis CI.

For the convenience of reading the commands were put into a separate script .travis.sh, which is placed in the root of the project.

So, we have the following file .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

Before installing the packages, let's update the submodules. This is necessary to build PPSSPPs. Add the first function to .travis.sh (note the extension):

travis_before_install() {
  git submodule update --init --recursive
}

Now we've come directly to setting up the automatic launch of PVS-Studio in Travis CI. First, we need to install the PVS-Studio package into the system:

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
}

At the beginning of the travis_install function we install the compilers we need using environment variables. Then, if the $PVS_ANALYZE variable stores the value of Yes (we specified it in the env section when configuring the build matrix), we install the pvs-studio package. Besides it, there are also libio-socket-ssl-perl and libnet-ssleay-perl packages, but they are needed to send the results by mail, so they are not necessary if you have chosen another way of report delivery.

The download_extract function downloads and unpacks the specified archive:

download_extract() {
  aria2c -x 16 $1 -o $2
  tar -xf $2
}

It's time to build a project. This happens in the script section:

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
}

In fact, this is a simplified original configuration, except for these lines:

if [ "$PVS_ANALYZE" = "Yes" ]; then
  CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}"
fi

In this section of the code, we set the compilation command export flag for cmake. This is necessary for a static code analyzer. You may read more about it in the article "How to launch PVS-Studio in Linux and macOS".

If the build was successful, we will get to after_success where we will run static analysis:

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
}

Let's consider the following lines in detail:

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

The first line generates the license file from the user name and the key that we specified at the beginning of the configuration of the Travis CI environment variables.

The second line starts the analysis directly. The flag -j<N> sets the number of analysis threads, the flag -l <file> sets the license, the flag -o <file> sets the file to output the logs, and the flag -disableLicenseExpirationCheck is necessary for trial versions, because by default pvs-studio-analyzer will warn the user about the imminent expiration of the license. To prevent this from happening, you can specify this flag.

The log file contains an unprocessed output that cannot be read without conversion, so first you need to make the file readable. Let's run the logs through plog-converter and get an html file at the output.

In this example I decided to send reports by mail using the sendemail command.

The result was the following .travis.sh file:

#/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 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
}
set -e
set -x

$1;

It's time to add the changes to the git repository, and then Travis CI will automatically start the build. Click on «ppsspp» to go to build reports:

How to set up PVS-Studio in Travis CI using the example of PSP game console emulator - 6

We will see an overview of the current build:

How to set up PVS-Studio in Travis CI using the example of PSP game console emulator - 7

If the build is successfully completed, we will receive an e-mail with the results of static analysis. Of course, sending by mail is not the only way to get the report. You can choose any method of implementation. But it is important to remember that it will be impossible to get access to the files of the virtual machine after the build is finished.

Brief overview of errors

We have successfully completed the most difficult part. Let us now make sure that all our efforts have been justified. Let's consider some interesting points from the static analysis report that came to me by mail (it's not for nothing that I specified it).

Dangerous optimizations

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 ) );
}

The PVS-Studio warning: V597 The compiler could delete the 'memset' function call, which is used to flush 'sum' buffer. The RtlSecureZeroMemory() function should be used to erase the private data. sha1.cpp 325

This code fragment is located in the secure hashing module, but it contains a serious security defect (CWE-14). Let's consider the assembler listing which is generated when the Debug-version compiles:

; Line 355
  mov r8d, 20
  xor edx, edx
  lea rcx, QWORD PTR sum$[rsp]
  call memset
; Line 356

Everything is fine and the memset function is executed, thus wiping important data in RAM, but you shouldn't be glad yet. Let's consider assembler listing of the Release version with optimization:

; 354  :
; 355  :  memset( sum, 0, sizeof( sum ) );
; 356  :}

As you can see from the listing, the compiler ignored the call of memset. It is related to the fact that the sha1 function no longer calls the ctx structure after calling memset. That's why the compiler doesn't see any sense in wasting processor time on overwriting memory not being used in future. You may fix it by using the RtlSecureZeroMemory function or a similar function.

Right:

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 ) );
} 

Unnecessary comparison

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);
  }
}

The PVS-Studio warning: V547 Expression 'leftvol >= 0' is always true. sceAudio.cpp 120

Pay attention to the else branch for the first if. The code will be executed only if all the conditions leftvol > 0xFFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0 are false. Therefore, we get the following statements that will be true for the else branch: leftvol <= 0xFFFFF, rightvol <= 0xFFFFF, leftvol >= 0 and rightvol >= 0. Pay attention to the last two statements. Is it reasonable to check what is the necessary condition of execution of this code fragment?

So we can calmly delete these conditional operators:

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);
  }
}

Another scenario. Behind these redundant conditions there is some error. Perhaps we have checked what is not what we need…

Ctrl+C Ctrl+V strikes back

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 There are identical sub-expressions '!Memory::IsValidAddress(psmfData)' to the left and to the right of the '||' operator. scePsmf.cpp 703

Note the check inside if. Doesn't it seem strange to you that we are checking whether the psmfData address is valid twice as much? So I find it strange… Actually, we have a misprint before us, of course, and the idea was to check both input parameters.

The correct variant is:

static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData) {
  if (!Memory::IsValidAddress(psmfStruct) ||
      !Memory::IsValidAddress(psmfData)) {
    return hleReportError(ME, SCE_KERNEL_ERROR_ILLEGAL_ADDRESS, "bad address");
  }
  ....
}

Forgotten variable

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");
  }
  ....
}

The PVS-Studio warning: V547 Expression 'size == 8' is always false. syn-att.c 195

This error is located in the ext folder, so it doesn't really apply to the project, but the error was found before I noticed it, so I decided to keep it. Still, this article is not about error review but about integration with Travis CI and no analyzer configuration was performed.

The size variable is initialized with a constant, but it is not used at all in the code up to the if operator which, of course, generates false information while checking the condition because, as we remember, the size is equal to zero. Subsequent checks do not make sense either.

Apparently, the author of the code fragment forgot to overwrite the size variable before that.

Stop

That's where we're gonna stop with the errors. The purpose of this article is to demonstrate how PVS-Studio works with Travis CI and not to analyze the project as thoroughly as possible. If you want bigger and more beautiful errors, you can always see them here :).

Conclusion

Using web services for building projects together with incremental analysis practice allows you to detect many problems right after the code merge. However, one build may not be enough, so setting up testing together with static analysis will significantly improve the code quality.

Useful Links

Автор: MrDvorak

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js