So konfigurieren Sie PVS-Studio in Travis CI am Beispiel eines PSP-Spielekonsolen-Emulators

So konfigurieren Sie PVS-Studio in Travis CI am Beispiel eines PSP-Spielekonsolen-Emulators
Travis CI ist ein verteilter Webdienst zum Erstellen und Testen von Software, der GitHub als Quellcode-Hosting verwendet. Zusätzlich zu den oben genannten Betriebsszenarien können Sie dank der umfangreichen Konfigurationsmöglichkeiten Ihre eigenen hinzufügen. In diesem Artikel konfigurieren wir Travis CI mithilfe des PPSSPP-Codebeispiels für die Arbeit mit PVS-Studio.

Einführung

Travis CI ist ein Webdienst zum Erstellen und Testen von Software. Es wird normalerweise in Verbindung mit kontinuierlichen Integrationspraktiken verwendet.

PPSSPP – PSP-Spielekonsolen-Emulator. Das Programm ist in der Lage, den Start aller Spiele von Disk-Images zu emulieren, die für die Sony PSP bestimmt sind. Das Programm wurde am 1. November 2012 veröffentlicht. PPSSPP ist unter GPL v2 lizenziert. Jeder kann Verbesserungen vornehmen Quellcode des Projekts.

PVS Studio — ein statischer Code-Analysator zur Suche nach Fehlern und potenziellen Schwachstellen im Programmcode. In diesem Artikel starten wir PVS-Studio zur Abwechslung nicht lokal auf dem Rechner des Entwicklers, sondern in der Cloud und suchen nach Fehlern in PPSSPP.

Travis CI einrichten

Wir benötigen ein Repository auf GitHub, in dem sich das von uns benötigte Projekt befindet, sowie einen Schlüssel für PVS-Studio (den Sie erhalten können). Testschlüssel oder kostenlos für Open-Source-Projekte).

Gehen wir zur Website Travis CI. Nach der Autorisierung mit Ihrem GitHub-Konto sehen wir eine Liste der Repositories:

So konfigurieren Sie PVS-Studio in Travis CI am Beispiel eines PSP-Spielekonsolen-Emulators
Für den Test habe ich PPSSPP geforkt.

Wir aktivieren das Repository, das wir sammeln möchten:

So konfigurieren Sie PVS-Studio in Travis CI am Beispiel eines PSP-Spielekonsolen-Emulators
Im Moment kann Travis CI unser Projekt nicht bauen, da keine Bauanleitung vorliegt. Es ist also Zeit für die Konfiguration.

Während der Analyse werden uns einige Variablen nützlich sein, beispielsweise der Schlüssel für PVS-Studio, dessen Angabe in der Konfigurationsdatei unerwünscht wäre. Fügen wir also Umgebungsvariablen mithilfe der Build-Einstellungen in Travis CI hinzu:

So konfigurieren Sie PVS-Studio in Travis CI am Beispiel eines PSP-Spielekonsolen-Emulators
Wir brauchen:

  • PVS_USERNAME – Benutzername
  • PVS_KEY – Schlüssel
  • MAIL_USER – E-Mail, die zum Senden des Berichts verwendet wird
  • MAIL_PASSWORD – E-Mail-Passwort

Die letzten beiden sind optional. Diese werden für den Versand der Ergebnisse per Post verwendet. Wenn Sie den Bericht auf andere Weise verbreiten möchten, müssen Sie dies nicht angeben.

Daher haben wir die benötigten Umgebungsvariablen hinzugefügt:

So konfigurieren Sie PVS-Studio in Travis CI am Beispiel eines PSP-Spielekonsolen-Emulators
Jetzt erstellen wir eine Datei .travis.yml und platzieren Sie es im Stammverzeichnis des Projekts. PPSSPP hatte bereits eine Konfigurationsdatei für Travis CI, diese war jedoch zu groß und für das Beispiel völlig ungeeignet, sodass wir sie stark vereinfachen und nur die Grundelemente belassen mussten.

Geben wir zunächst die Sprache, die Version von Ubuntu Linux, die wir in der virtuellen Maschine verwenden möchten, und die für den Build erforderlichen Pakete an:

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'

Alle aufgeführten Pakete werden ausschließlich für PPSSPP benötigt.

Jetzt geben wir die Montagematrix an:

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

Etwas mehr über den Abschnitt Matrix. In Travis CI gibt es zwei Möglichkeiten, Build-Optionen zu erstellen: Die erste besteht darin, eine Liste von Compilern, Betriebssystemtypen, Umgebungsvariablen usw. anzugeben, woraufhin eine Matrix aller möglichen Kombinationen generiert wird; der zweite ist ein expliziter Hinweis auf die Matrix. Natürlich können Sie diese beiden Ansätze kombinieren und einen Einzelfall hinzufügen oder ihn im Gegenteil mithilfe des Abschnitts ausschließen ausschließen. Mehr dazu können Sie hier lesen Travis CI-Dokumentation.

Es bleibt nur noch die Bereitstellung projektspezifischer Montageanleitungen:

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

Mit Travis CI können Sie Ihre eigenen Befehle für verschiedene Lebensphasen einer virtuellen Maschine hinzufügen. Abschnitt before_install wird vor der Installation von Paketen ausgeführt. Dann installieren, was auf die Installation von Paketen aus der Liste folgt addons.aptwas wir oben angedeutet haben. Die Montage selbst erfolgt in Skript. Wenn alles gut gelaufen ist, dann sind wir dabei nach_erfolg (In diesem Abschnitt führen wir eine statische Analyse durch.) Dies sind nicht alle Schritte, die geändert werden können. Wenn Sie mehr benötigen, sollten Sie einen Blick darauf werfen Travis CI-Dokumentation.

Zur besseren Lesbarkeit wurden die Befehle in einem separaten Skript platziert .travis.sh, die im Projektstammverzeichnis platziert wird.

Wir haben also die folgende Datei .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

Vor der Installation der Pakete aktualisieren wir die Submodule. Dies wird zum Erstellen von PPSSPP benötigt. Fügen wir die erste Funktion hinzu .travis.sh (Beachten Sie die Erweiterung):

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

Nun kommen wir direkt zum Einrichten des automatischen Starts von PVS-Studio in Travis CI. Zuerst müssen wir das PVS-Studio-Paket auf dem System installieren:

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
}

Zu Beginn der Funktion travis_install Wir installieren die benötigten Compiler mithilfe von Umgebungsvariablen. Dann wenn die Variable $PVS_ANALYZE speichert Wert Ja (wir haben es im Abschnitt angegeben env während der Build-Matrix-Konfiguration) installieren wir das Paket pvs-studio. Darüber hinaus werden auch Pakete angezeigt libio-socket-ssl-perl и libnet-ssleay-perlSie sind jedoch für den Versand der Ergebnisse erforderlich. Sie sind also nicht erforderlich, wenn Sie eine andere Methode zur Übermittlung Ihres Berichts gewählt haben.

Funktion download_extract lädt das angegebene Archiv herunter und entpackt es:

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

Es ist Zeit, das Projekt zusammenzustellen. Dies geschieht im Abschnitt Skript:

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
}

Tatsächlich handelt es sich hierbei um eine vereinfachte Originalkonfiguration, mit Ausnahme dieser Zeilen:

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

In diesem Codeabschnitt legen wir fest cmake Flag zum Exportieren von Kompilierungsbefehlen. Dies ist für einen statischen Code-Analysator erforderlich. Mehr dazu lesen Sie im Artikel „So führen Sie PVS-Studio unter Linux und macOS aus«.

Wenn die Montage erfolgreich war, dann geht es weiter nach_erfolg, wo wir statische Analysen durchführen:

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
}

Schauen wir uns die folgenden Zeilen genauer an:

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

Die erste Zeile generiert eine Lizenzdatei aus dem Benutzernamen und Schlüssel, die wir ganz am Anfang beim Einrichten der Travis CI-Umgebungsvariablen angegeben haben.

Die zweite Zeile startet direkt die Analyse. Flagge -J Legt die Anzahl der Threads für die Analyse fest, Flag -l zeigt Lizenz, Flagge an definiert die Datei zur Ausgabe von Protokollen und das Flag -disableLicenseExpirationCheck für Testversionen erforderlich, da standardmäßig pvs-studio-analyzer warnt den Benutzer, dass die Lizenz bald abläuft. Um dies zu verhindern, können Sie dieses Flag angeben.

Die Protokolldatei enthält Rohausgaben, die ohne Konvertierung nicht gelesen werden können. Daher müssen Sie die Datei zunächst lesbar machen. Lassen Sie uns die Protokolle weiterleiten Plog-Konverter, und die Ausgabe ist eine HTML-Datei.

In diesem Beispiel habe ich mich entschieden, Berichte per E-Mail mit dem Befehl zu versenden E-Mail senden.

Als Ergebnis haben wir die folgende Datei erhalten .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;

Jetzt ist es an der Zeit, die Änderungen in das Git-Repository zu übertragen, woraufhin Travis CI den Build automatisch ausführt. Klicken Sie auf „ppsspp“, um zu den Build-Berichten zu gelangen:

So konfigurieren Sie PVS-Studio in Travis CI am Beispiel eines PSP-Spielekonsolen-Emulators
Wir sehen einen Überblick über den aktuellen Build:

So konfigurieren Sie PVS-Studio in Travis CI am Beispiel eines PSP-Spielekonsolen-Emulators
Wenn der Build erfolgreich abgeschlossen ist, erhalten wir eine E-Mail mit den Ergebnissen der statischen Analyse. Natürlich ist der Versand per Post nicht die einzige Möglichkeit, einen Bericht zu erhalten. Sie können eine beliebige Implementierungsmethode wählen. Es ist jedoch wichtig zu bedenken, dass nach Abschluss des Builds kein Zugriff mehr auf die Dateien der virtuellen Maschine möglich ist.

Fehlerzusammenfassung

Den schwierigsten Teil haben wir erfolgreich gemeistert. Stellen wir nun sicher, dass sich all unsere Bemühungen lohnen. Schauen wir uns einige interessante Punkte aus dem statischen Analysebericht an, der mir per Post zugegangen ist (nicht umsonst habe ich darauf hingewiesen).

Gefährliche Optimierung

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-Warnung: V597 Der Compiler könnte den Funktionsaufruf „memset“ löschen, der zum Leeren des „sum“-Puffers verwendet wird. Zum Löschen der privaten Daten sollte die Funktion RtlSecureZeroMemory() verwendet werden. sha1.cpp 325

Dieser Code befindet sich im sicheren Hashing-Modul, weist jedoch eine schwerwiegende Sicherheitslücke auf (CWE-14). Schauen wir uns die Assemblyliste an, die beim Kompilieren der Debug-Version generiert wird:

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

Alles ist in Ordnung und die Funktion Memset ausgeführt wird und dabei wichtige Daten im RAM überschreibt, freuen Sie sich aber noch nicht. Schauen wir uns die Assemblyliste der Release-Version mit Optimierung an:

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

Wie aus der Auflistung hervorgeht, hat der Compiler den Aufruf ignoriert Memset. Dies liegt daran, dass in der Funktion sha1 nach dem Anruf Memset kein Bezug mehr zur Struktur ctx. Daher sieht der Compiler keinen Sinn darin, Prozessorzeit damit zu verschwenden, Speicher zu überschreiben, der in Zukunft nicht mehr verwendet wird. Sie können dies beheben, indem Sie die Funktion verwenden RtlSecureZeroMemory oder ähnlich sie.

Richtig:

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

Unnötiger Vergleich

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-Warnung: V547 Der Ausdruck „leftvol >= 0“ ist immer wahr. sceAudio.cpp 120

Achten Sie zunächst auf den else-Zweig if. Der Code wird nur ausgeführt, wenn alle Bedingungen erfüllt sind leftvol > 0xFFFF || rightvol > 0xFFFF || leftvol < 0 || rightvol < 0 wird sich als falsch herausstellen. Daher erhalten wir die folgenden Aussagen, die für den else-Zweig gelten: leftvol <= 0xFFFF, rightvol <= 0xFFFF, leftvol >= 0 и rightvol >= 0. Beachten Sie die letzten beiden Aussagen. Ist es sinnvoll zu prüfen, was eine notwendige Bedingung für die Ausführung dieses Codeabschnitts ist?

Daher können wir diese bedingten Anweisungen sicher entfernen:

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

Ein anderes Szenario. Hinter diesen redundanten Bedingungen verbirgt sich ein Fehler. Vielleicht haben sie nicht geprüft, was erforderlich war.

Strg+C Strg+V schlägt zurück

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 Es gibt identische Unterausdrücke „!Memory::IsValidAddress(psmfData)“ links und rechts von „||“ Operator. scePsmf.cpp 703

Achten Sie auf den Scheck im Inneren if. Finden Sie es nicht seltsam, dass wir prüfen, ob die Adresse gültig ist? psmfData, doppelt so viel? Das kommt mir also seltsam vor ... Tatsächlich handelt es sich hierbei natürlich um einen Tippfehler, und die Idee bestand darin, beide Eingabeparameter zu überprüfen.

Korrekte Möglichkeit:

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

Variable vergessen

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-Warnung: V547 Der Ausdruck „size == 8“ ist immer falsch. syn-att.c 195

Dieser Fehler befindet sich im Ordner ext, also nicht wirklich relevant für das Projekt, aber der Fehler wurde gefunden, bevor ich ihn bemerkte, also beschloss ich, ihn zu belassen. Schließlich geht es in diesem Artikel nicht um die Überprüfung von Fehlern, sondern um die Integration mit Travis CI, und es wurde keine Konfiguration des Analysators vorgenommen.

Variable Größe wird durch eine Konstante initialisiert, wird jedoch im Code überhaupt nicht verwendet, bis hin zum Operator if, was natürlich gibt falsch bei der Überprüfung der Bedingungen, denn wie wir uns erinnern, Größe gleich Null. Auch nachträgliche Kontrollen machen keinen Sinn.

Offenbar hat der Autor des Codefragments vergessen, die Variable zu überschreiben Größe davor.

Stoppen

Hier werden wir wahrscheinlich mit den Fehlern aufhören. Der Zweck dieses Artikels besteht darin, die Arbeit von PVS-Studio zusammen mit Travis CI zu demonstrieren und nicht das Projekt so gründlich wie möglich zu analysieren. Wenn Sie größere und schönere Fehler wollen, können Sie diese immer bewundern hier :).

Abschluss

Durch die Verwendung von Webdiensten zum Erstellen von Projekten zusammen mit der Praxis der inkrementellen Analyse können Sie viele Probleme sofort nach dem Zusammenführen von Code finden. Allerdings reicht ein Build möglicherweise nicht aus, sodass die Einrichtung von Tests zusammen mit statischer Analyse die Qualität des Codes erheblich verbessern wird.

Nützliche Links

So konfigurieren Sie PVS-Studio in Travis CI am Beispiel eines PSP-Spielekonsolen-Emulators

Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Übersetzungslink: Maxim Zvyagintsev. So richten Sie PVS-Studio in Travis CI am Beispiel des PSP-Spielekonsolen-Emulators ein.

Source: habr.com

Kommentar hinzufügen