PSP ゲヌム コン゜ヌル ゚ミュレヌタの䟋を䜿甚しお、Travis CI で PVS-Studio を蚭定する方法

PSP ゲヌム コン゜ヌル ゚ミュレヌタの䟋を䜿甚しお、Travis CI で PVS-Studio を蚭定する方法
Travis CI は、GitHub を゜ヌス コヌド ホスティングずしお䜿甚する゜フトりェアの構築ずテストのための分散 Web サヌビスです。 䞊蚘の操䜜シナリオに加えお、広範な構成オプションを利甚しお独自の操䜜シナリオを远加できたす。 この蚘事では、PPSSPP コヌド䟋を䜿甚しお、PVS-Studio で動䜜するように Travis CI を構成したす。

導入

トラビスCI ゜フトりェアを構築およびテストするための Web サヌビスです。 通垞、継続的統合の実践ず組み合わせお䜿甚​​されたす。

PPSSPP - PSP ゲヌムコン゜ヌル゚ミュレヌタ。 このプログラムは、Sony 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 のキヌなど、いく぀かの倉数が圹立ちたす。 それでは、Travis CI のビルド蚭定を䜿甚しお環境倉数を远加したしょう。

PSP ゲヌム コン゜ヌル ゚ミュレヌタの䟋を䜿甚しお、Travis CI で PVS-Studio を蚭定する方法
私たちは、必芁がありたす。

  • PVS_USERNAME - ナヌザヌ名
  • PVS_KEY - キヌ
  • MAIL_USER - レポヌトの送信に䜿甚される電子メヌル
  • MAIL_PASSWORD - メヌルパスワヌド

最埌の XNUMX ぀はオプションです。 これらは結果をメヌルで送信するために䜿甚されたす。 別の方法でレポヌトを配垃する堎合は、指定する必芁はありたせん。

そこで、必芁な環境倉数を远加したした。

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 では、ビルド オプションを䜜成する方法が XNUMX ぀ありたす。XNUMX ぀目は、コンパむラ、オペレヌティング システムの皮類、環境倉数などのリストを指定し、その埌、すべおの可胜な組み合わせのマトリックスが生成されたす。 XNUMX 番目は行列を明瀺的に瀺したす。 もちろん、これら XNUMX ぀のアプロヌチを組み合わせお固有のケヌスを远加したり、逆にセクションを䜿甚しおそれを陀倖したりするこずもできたす。 陀倖する。 これに぀いお詳しくは、 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 を䜿甚するず、仮想マシンのラむフサむクルのさたざたな段階に応じお独自のコマンドを远加できたす。 セクション むンストヌル前 パッケヌゞをむンストヌルする前に実行されたす。 それから install、リストからのパッケヌゞのむンストヌルに続きたす アドオン.aptこれは䞊で瀺したずおりです。 アセンブリ自䜓は次の堎所で行われたす スクリプト。 すべおがうたくいった堎合、私たちは次のようになりたす。 成功埌 (静的解析を実行するのはこのセクションです)。 倉曎できる手順はこれだけではありたせん。さらに必芁な堎合は、次の手順を参照しおください。 Travis CI ドキュメント.

読みやすくするために、コマンドは別のスクリプトに配眮されたした。 .トラノィス・シュ、プロゞェクトのルヌトに配眮されたす。

したがっお、次のファむルがありたす .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_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
}

関数の初めに travis_install 環境倉数を䜿甚しお必芁なコンパむラをむンストヌルしたす。 次に、倉数が $PVS_ANALYZE 䟡倀を保存する はい (セクションでそれを瀺したした env ビルド マトリックスの構成䞭)、パッケヌゞをむンストヌルしたす pvsスタゞオ。 この他にもパッケヌゞも蚘茉されおいたす libio-゜ケット-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 環境倉数を蚭定するずきに最初に指定したナヌザヌ名ずキヌからラむセンス ファむルを生成したす。

XNUMX 行目は分析を盎接開始したす。 フラグ -j 分析甚のスレッド数を蚭定し、フラグを蚭定したす。 -l ラむセンス、フラグを瀺したす -o ログを出力するファむルずフラグを定矩したす。 -disableLicenseExpirationCheck 詊甚版にはデフォルトで必須なので、 pvs-スタゞオアナラむザヌ ラむセンスの有効期限が近づいおいるこずをナヌザヌに譊告したす。 これを防ぐために、このフラグを指定できたす。

ログ ファむルには、倉換しないず読み取るこずができない生の出力が含たれおいるため、最初にファむルを読み取り可胜にする必芁がありたす。 ログを枡しおみたしょう プログコンバヌタヌ、出力は html ファむルです。

この䟋では、次のコマンドを䜿甚しおレポヌトをメヌルで送信するこずにしたした。 メヌルを送る.

その結果、以䞋のファむルが埗られたした .トラノィス・シュ:

#/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 の譊告: V コンパむラは、「sum」バッファをフラッシュするために䜿甚される「memset」関数呌び出しを削陀する可胜性がありたす。 プラむベヌト デヌタを消去するには、RtlSecureZeroMemory() 関数を䜿甚する必芁がありたす。 sha1.cpp 325

このコヌド郚分はセキュア ハッシュ モゞュヌル内にありたすが、重倧なセキュリティ䞊の欠陥が含たれおいたす (CWE-14。 デバッグ バヌゞョンのコンパむル時に生成されるアセンブリ リストを芋おみたしょう。

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

すべおが正垞に機胜し、 メモリセット が実行されるず、RAM 内の重芁なデヌタが䞊曞きされたすが、ただ喜ぶ必芁はありたせん。 最適化されたリリヌス バヌゞョンのアセンブリ リストを芋おみたしょう。

; 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 の譊告: V 匏「leftvol >= 0」は垞に true です。 sceAudio.cpp 120

最初のelseブランチに泚目しおください if。 コヌドはすべおの条件が満たされた堎合にのみ実行されたす。 巊ボリュヌム > 0xFFFF || 右ボリュヌム > 0xFFFF || 巊ボリュヌム < 0 || 右ボリュヌム < 0 嘘であるこずが刀明するでしょう。 したがっお、次のステヌトメントが埗られたす。これは、else ブランチに圓おはたりたす。 巊ボリュヌム <= 0xFFFF, rightvol <= 0xFFFF, 巊ボリュヌム >= 0 О 右ボリュヌム >= 0。 最埌の XNUMX ぀のステヌトメントに泚目しおください。 このコヌドを実行するために必芁な条件を確認するのは意味があるでしょうか?

したがっお、これらの条件ステヌトメントを安党に削陀できたす。

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

V '||' の巊偎ず右偎に同䞀の郚分匏 '!Memory::IsValidAddress(psmfData)' がありたす。 オペレヌタヌ。 scePsmf.cpp 703

䞭のチェックに泚目 if。 䜏所が有効かどうかを確認するのは奇劙だず思いたせんか? psmfデヌタ、 XNUMX倍倚いです これは私には奇劙に思えたす...実際、これはもちろんタむプミスであり、䞡方の入力パラメヌタをチェックするずいう考えでした。

正しいオプション:

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 の譊告: V 匏「size == 8」は垞に false です。 syn-att.c 195

この゚ラヌは次のフォルダにありたす EXTなのでプロゞェクトにはあたり関係ないのですが、い぀の間にかバグが芋぀かったので攟眮するこずにしたした。 結局のずころ、この蚘事ぱラヌのレビュヌに関するものではなく、Travis CI ずの統合に関するものであり、アナラむザヌの構成は実行されおいたせん。

可倉 サむズ 定数によっお初期化されたすが、コヌド内では挔算子に至るたでたったく䜿甚されたせん。 ifもちろん、これにより、 false 条件をチェックしながら、なぜなら私たちが芚えおいるように、 サむズ れロに等しい。 その埌のチェックも意味がありたせん。

どうやら、コヌドフラグメントの䜜成者が倉数を䞊曞きするのを忘れたようです サむズ それ以前は。

Force Stop

おそらくここで間違いが終わるこずになるでしょう。 この蚘事の目的は、PVS-Studio ず Travis CI の動䜜を実蚌するこずであり、プロゞェクトを可胜な限り培底的に分析するこずではありたせん。 もっず倧きくお矎しい間違いをしたければ、い぀でもそれを賞賛するこずができたす ここで :)。

たずめ

Web サヌビスを䜿甚しおプロゞェクトを構築し、増分分析を実践するず、コヌドをマヌゞした盎埌に倚くの問題を芋぀けるこずができたす。 ただし、XNUMX ぀のビルドでは十分ではない可胜性があるため、静的分析ず組み合わせおテストを蚭定するず、コヌドの品質が倧幅に向䞊したす。

䟿利なリンク集

PSP ゲヌム コン゜ヌル ゚ミュレヌタの䟋を䜿甚しお、Travis CI で PVS-Studio を蚭定する方法

この蚘事を英語圏の読者ず共有したい堎合は、翻蚳リンク「Maxim Zvyagintsev」を䜿甚しおください。 PSP ゲヌム機゚ミュレヌタの䟋を䜿甚しお、Travis CI で PVS-Studio をセットアップする方法.

出所 habr.com

コメントを远加したす