Kiel agordi PVS-Studio en Travis CI uzante la ekzemplon de PSP-ludkonzola emulilo

Kiel agordi PVS-Studio en Travis CI uzante la ekzemplon de PSP-ludkonzola emulilo
Travis CI estas distribuita retservo por konstrui kaj testi programaron, kiu uzas GitHub kiel fontkodan gastigadon. Krom ĉi-supraj operaciaj scenaroj, vi povas aldoni viajn proprajn danke al la ampleksaj agordaj elektoj. En ĉi tiu artikolo ni agordos Travis CI por labori kun PVS-Studio uzante la PPSSPP-kodekzemplon.

Enkonduko

Travis C.I. estas retservo por konstrui kaj testi programaron. Ĝi estas kutime uzita lige kun kontinuaj integriĝpraktikoj.

PPSSPP - Emulilo de ludkonzolo de PSP. La programo kapablas kopii la lanĉon de iuj ludoj el diskobildoj destinitaj por Sony PSP. La programo estis publikigita la 1-an de novembro 2012. PPSSPP estas licencita laŭ GPL v2. Ĉiu povas fari plibonigojn al projekta fontkodo.

PVS Studio — statika kodanalizilo por serĉi erarojn kaj eblajn vundeblecojn en programkodo. En ĉi tiu artikolo, por ŝanĝi, ni lanĉos PVS-Studio ne loke sur la maŝino de la programisto, sed en la nubo, kaj serĉos erarojn en PPSSPP.

Starigante Travis CI

Ni bezonos deponejon en GitHub, kie troviĝas la projekto, kiun ni bezonas, kaj ankaŭ ŝlosilon por PVS-Studio (vi povas akiri prova ŝlosilosenpage por Malferma Kodaj projektoj).

Ni iru al la retejo Travis C.I.. Post rajtigo uzante vian GitHub-konton, ni vidos liston de deponejoj:

Kiel agordi PVS-Studio en Travis CI uzante la ekzemplon de PSP-ludkonzola emulilo
Por la testo, mi forkigis PPSSPP.

Ni aktivigas la deponejon, kiun ni volas kolekti:

Kiel agordi PVS-Studio en Travis CI uzante la ekzemplon de PSP-ludkonzola emulilo
Nuntempe, Travis CI ne povas konstrui nian projekton ĉar ne ekzistas instrukcioj por konstrui. Do estas tempo por agordo.

Dum la analizo, iuj variabloj estos utilaj al ni, ekzemple, la ŝlosilo por PVS-Studio, kiu estus nedezirinda specifi en la agorda dosiero. Do ni aldonu mediajn variablojn uzante la konstruajn agordojn en Travis CI:

Kiel agordi PVS-Studio en Travis CI uzante la ekzemplon de PSP-ludkonzola emulilo
Ni bezonos:

  • PVS_USERNAME - uzantnomo
  • PVS_KEY - ŝlosilo
  • MAIL_USER - retpoŝto, kiu estos uzata por sendi la raporton
  • MAIL_PASSWORD - retpoŝta pasvorto

La lastaj du estas laŭvolaj. Ĉi tiuj estos uzataj por sendi rezultojn per poŝto. Se vi volas disdoni la raporton alimaniere, vi ne bezonas indiki ilin.

Do, ni aldonis la mediajn variablojn, kiujn ni bezonas:

Kiel agordi PVS-Studio en Travis CI uzante la ekzemplon de PSP-ludkonzola emulilo
Nun ni kreu dosieron .travis.yml kaj metu ĝin ĉe la radiko de la projekto. PPSSPP jam havis agordan dosieron por Travis CI, tamen ĝi estis tro granda kaj tute maltaŭga por la ekzemplo, do ni devis multe simpligi ĝin kaj lasi nur la bazajn elementojn.

Unue, ni indiku la lingvon, la version de Ubuntu Linukso, kiun ni volas uzi en la virtuala maŝino, kaj la necesajn pakaĵojn por la konstruo:

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'

Ĉiuj pakoj kiuj estas listigitaj estas bezonataj ekskluzive por PPSSPP.

Nun ni indikas la asemblematron:

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

Iom pli pri la sekcio Matrico. En Travis CI, ekzistas du manieroj krei konstruopciojn: la unua estas specifi liston de kompililoj, operaciumaj tipoj, mediovariabloj, ktp., post kiuj matrico de ĉiuj eblaj kombinaĵoj estas generita; la dua estas eksplicita indiko de la matrico. Kompreneble, vi povas kombini ĉi tiujn du alirojn kaj aldoni unikan kazon, aŭ, male, ekskludi ĝin uzante la sekcion ekskludi. Vi povas legi pli pri tio en Dokumentado de Travis CI.

Restas nur provizi projekt-specifajn kunveninstrukciojn:

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 permesas vin aldoni viajn proprajn komandojn por diversaj etapoj de la vivo de virtuala maŝino. Sekcio antaŭ_instali efektivigita antaŭ instali pakaĵojn. Tiam instali, kiu sekvas la instaladon de pakaĵoj el la listo aldonaĵoj.aptkiujn ni supre indikis. La asembleo mem okazas en skripto. Se ĉio iris bone, tiam ni trovas nin en post_sukceso (estas en ĉi tiu sekcio, ke ni funkcios statikan analizon). Ĉi tiuj ne estas ĉiuj paŝoj, kiuj povas esti modifitaj, se vi bezonas pli, tiam vi devus enrigardi Dokumentado de Travis CI.

Por facileco de legado, la komandoj estis metitaj en apartan skripton .travis.sh, kiu estas metita ĉe la projekta radiko.

Do ni havas la sekvan dosieron .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

Antaŭ ol instali la pakaĵojn, ni ĝisdatigos la submodulojn. Ĉi tio estas necesa por konstrui PPSSPP. Ni aldonu la unuan funkcion al .travis.sh (notu la etendon):

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

Nun ni rekte agordas la aŭtomatan lanĉon de PVS-Studio en Travis CI. Unue ni devas instali la pakaĵon PVS-Studio en la sistemo:

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
}

Komence de la funkcio travis_install ni instalas la kompililojn ni bezonas uzante mediovariablojn. Tiam se la variablo $PVS_ANALYZE stokas valoron Jes (ni indikis ĝin en la sekcio env dum konstrua matrica agordo), ni instalas la pakaĵon pvs-studio. Aldone al tio, pakoj ankaŭ estas indikitaj libio-socket-ssl-perl и libnet-ssleay-perl, tamen, ili estas postulataj por sendi rezultojn, do ili ne estas necesaj se vi elektis alian metodon por liveri vian raporton.

funkcio download_extract elŝutas kaj malpakigas la specifitan arkivon:

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

Estas tempo kunmeti la projekton. Ĉi tio okazas en la sekcio skripto:

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
}

Fakte, ĉi tio estas simpligita originala agordo, krom ĉi tiuj linioj:

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

En ĉi tiu sekcio de kodo ni starigis cmake flago por eksporti kompilajn komandojn. Ĉi tio estas necesa por statika kodanalizilo. Vi povas legi pli pri tio en la artikolo "Kiel ruli PVS-Studio en Linukso kaj macOS".

Se la asembleo sukcesis, tiam ni atingas post_sukceso, kie ni faras statikan analizon:

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
}

Ni rigardu pli detale la sekvajn liniojn:

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

La unua linio generas licencdosieron de la uzantnomo kaj ŝlosilo, kiujn ni specifis komence dum agordado de la mediovariabloj de Travis CI.

La dua linio komencas la analizon rekte. Flago -j fiksas la nombron da fadenoj por analizo, flago -l indikas licencon, flagon -o difinas la dosieron por eligi protokolojn, kaj la flagon -disableLicenseExpirationCheck bezonata por provversioj, ĉar defaŭlte pvs-studio-analizilo avertos la uzanton, ke la permesilo estas eksvalidiĝos. Por eviti ke tio okazu, vi povas specifi ĉi tiun flagon.

La protokolo-dosiero enhavas krudan produktaĵon, kiu ne povas esti legita sen konvertiĝo, do vi devas unue igi la dosieron legebla. Ni trapasu la ŝtipojn plog-konvertilo, kaj la eligo estas html-dosiero.

En ĉi tiu ekzemplo, mi decidis sendi raportojn per poŝto uzante la komandon sendi retpoŝton.

Kiel rezulto, ni ricevis la sekvan dosieron .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;

Nun estas tempo puŝi la ŝanĝojn al la git-deponejo, post kio Travis CI aŭtomate rulos la konstruon. Alklaku "ppsspp" por iri al la konstruaj raportoj:

Kiel agordi PVS-Studio en Travis CI uzante la ekzemplon de PSP-ludkonzola emulilo
Ni vidos superrigardon de la nuna konstruo:

Kiel agordi PVS-Studio en Travis CI uzante la ekzemplon de PSP-ludkonzola emulilo
Se la konstruo estas sukcese finita, ni ricevos retpoŝton kun la rezultoj de la statika analizo. Kompreneble, dissendo ne estas la sola maniero ricevi raporton. Vi povas elekti ajnan efektivigmetodon. Sed gravas memori, ke post kiam la konstruo finiĝos, ne eblos aliri la virtualajn maŝinajn dosierojn.

Resumo de eraroj

Ni sukcese plenumis la plej malfacilan parton. Nun ni certigu, ke ĉiuj niaj klopodoj valoras ĝin. Ni rigardu kelkajn interesajn punktojn el la statika analiza raporto, kiu venis al mi poŝte (ne vane mi indikis ĝin).

Danĝera optimumigo

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

Averto de PVS-Studio: V597 La kompililo povus forigi la "memset" funkciovokon, kiu estas uzata por malplenigi "sumo" bufro. La funkcio RtlSecureZeroMemory() devus esti uzata por forigi la privatajn datumojn. sha1.cpp 325

Ĉi tiu kodo troviĝas en la sekura haĉa modulo, tamen ĝi enhavas gravan sekurecan difekton (CWE-14). Ni rigardu la asemblean liston, kiu estas generita dum kompilado de la Debug-versio:

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

Ĉio estas en ordo kaj la funkcio memeset estas ekzekutita, tiel anstataŭigante gravajn datumojn en RAM, tamen ne ĝoju ankoraŭ. Ni rigardu la kunigliston de la Eldona versio kun optimumigo:

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

Kiel videblas el la listo, la kompililo ignoris la vokon memeset. Ĉi tio estas pro la fakto, ke en la funkcio sha1 post la voko memeset ne plu referenco al strukturo CTX. Tial, la kompililo vidas nenian signifon malŝpari procesoran tempon anstataŭante memoron kiu ne estas uzita en la estonteco. Vi povas ripari ĉi tion uzante la funkcion RtlSecureZeroMemory similaj al ŝi.

Ĝuste:

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

Nenecesa komparo

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

Averto de PVS-Studio: V547 Esprimo 'leftvol >= 0' ĉiam estas vera. sceAudio.cpp 120

Atentu la alian branĉon por la unua if. La kodo estos ekzekutita nur se ĉiuj kondiĉoj leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || dekstravol < 0 montriĝos malvera. Tial, ni ricevas la sekvajn deklarojn, kiuj estos veraj por la else branĉo: maldekstravol <= 0xFFFF, rightvol <= 0xFFFF, leftvol >= 0 и rightvol >= 0. Rimarku la lastajn du deklarojn. Ĉu havas sencon kontroli kio estas necesa kondiĉo por la ekzekuto de ĉi tiu kodo?

Do ni povas sekure forigi ĉi tiujn kondiĉajn deklarojn:

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

Alia scenaro. Estas ia eraro kaŝita malantaŭ ĉi tiuj superfluaj kondiĉoj. Eble ili ne kontrolis tion, kio estas postulata.

Ctrl+C Ctrl+V Refrakas

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 Estas identaj subesprimoj '!Memory::IsValidAddress(psmfData)' maldekstre kaj dekstre de la '||' operatoro. scePsmf.cpp 703

Atentu la ĉekon enen if. Ĉu vi ne pensas, ke estas strange, ke ni kontrolas ĉu la adreso validas? psmfData, duoble pli? Do ĉi tio ŝajnas al mi stranga... Fakte, ĉi tio estas, kompreneble, tajperaro, kaj la ideo estis kontroli ambaŭ enigajn parametrojn.

Ĝusta opcio:

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

Forgesita variablo

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

Averto de PVS-Studio: V547 Esprimo 'grandeco == 8' ĉiam estas malvera. syn-att.c 195

Ĉi tiu eraro troviĝas en la dosierujo ext, do ne vere rilata al la projekto, sed la cimo estis trovita antaŭ ol mi rimarkis ĝin, do mi decidis forlasi ĝin. Post ĉio, ĉi tiu artikolo ne temas pri reviziado de eraroj, sed pri integriĝo kun Travis CI, kaj neniu agordo de la analizilo estis efektivigita.

Variablo grandeco estas pravigita per konstanto, tamen ĝi tute ne estas uzata en la kodo, ĝuste ĝis la funkciigisto if, kiu, kompreneble, donas falsa dum kontrolado de la kondiĉoj, ĉar, kiel ni memoras, grandeco egala al nulo. Postaj kontroloj ankaŭ ne havas sencon.

Ŝajne, la aŭtoro de la kodfragmento forgesis anstataŭigi la variablon grandeco antaŭ tio.

ĉesigi

Ĉi tie ni verŝajne finos kun la eraroj. La celo de ĉi tiu artikolo estas pruvi la laboron de PVS-Studio kune kun Travis CI, kaj ne analizi la projekton kiel eble plej detale. Se vi volas pli grandajn kaj pli belajn erarojn, vi ĉiam povas admiri ilin tie :).

konkludo

Uzado de retservoj por konstrui projektojn kune kun la praktiko de pliiga analizo permesas trovi multajn problemojn tuj post kunfandado de kodo. Tamen, unu konstruo eble ne sufiĉas, do starigi testadon kune kun statika analizo signife plibonigos la kvaliton de la kodo.

utilaj ligoj

Kiel agordi PVS-Studio en Travis CI uzante la ekzemplon de PSP-ludkonzola emulilo

Se vi volas dividi ĉi tiun artikolon kun anglalingva publiko, bonvolu uzi la tradukan ligilon: Maxim Zvyagintsev. Kiel agordi PVS-Studion en Travis CI uzante la ekzemplon de emulilo de ludkonzolo de PSP.

fonto: www.habr.com

Aldoni komenton