Kumaha nyetél PVS-Studio di Travis CI nganggo émulator PSP sabagé conto

Kumaha nyetél PVS-Studio di Travis CI nganggo émulator PSP sabagé conto
Travis CI nyaéta layanan wéb anu disebarkeun pikeun ngawangun sareng nguji parangkat lunak anu ngagunakeun GitHub salaku kode sumber hosting. Salian skénario operasi di luhur, anjeun tiasa nambihan hatur nuhun kana pilihan konfigurasi anu éksténsif. Dina artikel ieu kami baris ngonpigurasikeun Travis CI pikeun gawé bareng PVS-Studio ngagunakeun conto kode PPSSPP.

perkenalan

Travis C.I. nyaéta layanan wéb pikeun ngawangun sareng nguji parangkat lunak. Biasana dianggo babarengan sareng prakték integrasi kontinyu.

PPSSPP - PSP kaulinan konsol émulator. Program éta tiasa niru peluncuran sagala kaulinan tina gambar disk anu dimaksudkeun pikeun Sony PSP. Program éta dirilis dina 1 Nopémber 2012. PPSSPP dilisensikeun dina GPL v2. Saha waé tiasa ngadamel perbaikan kode sumber proyék.

PVS Studio - analisa kode statik pikeun milarian kasalahan sareng poténsi kerentanan dina kode program. Dina artikel ieu, pikeun robah a, urang bakal ngajalankeun PVS-Studio teu lokal dina mesin pamekar, tapi dina awan, sarta néangan kasalahan dina PPSSPP.

Nyetél Travis CI

Urang bakal butuh Repository on GitHub, dimana proyek urang butuh lokasina, kitu ogé konci pikeun PVS-Studio (anjeun bisa meunangkeun konci sidang atawa bébas pikeun proyék Open Source).

Hayu urang buka situs Travis C.I.. Saatos otorisasina nganggo akun GitHub anjeun, urang bakal ningali daptar repositori:

Kumaha nyetél PVS-Studio di Travis CI nganggo émulator PSP sabagé conto
Pikeun tés, kuring nyéépkeun PPSSPP.

Kami ngaktifkeun gudang anu urang hoyong kumpulkeun:

Kumaha nyetél PVS-Studio di Travis CI nganggo émulator PSP sabagé conto
Di momen, Travis CI teu bisa ngawangun proyék urang sabab teu aya parentah pikeun ngawangun. Janten éta waktuna pikeun konfigurasi.

Salila analisa, sababaraha variabel bakal mangpaat pikeun urang, contona, konci pikeun PVS-Studio, nu bakal jadi pikaresepeun pikeun nangtukeun dina file konfigurasi. Janten hayu urang tambahkeun variabel lingkungan nganggo setélan ngawangun di Travis CI:

Kumaha nyetél PVS-Studio di Travis CI nganggo émulator PSP sabagé conto
Urang peryogi:

  • PVS_USERNAME - ngaran pamaké
  • PVS_KEY - konci
  • MAIL_USER - email anu bakal dianggo pikeun ngirim laporan
  • MAIL_PASSWORD - sandi email

Dua panungtungan nyaéta pilihan. Ieu bakal dianggo pikeun ngirim hasil ku mail. Upami anjeun hoyong nyebarkeun laporan ku cara anu sanés, anjeun henteu kedah nunjukkeun aranjeunna.

Janten, kami parantos nambihan variabel lingkungan anu urang peryogikeun:

Kumaha nyetél PVS-Studio di Travis CI nganggo émulator PSP sabagé conto
Ayeuna hayu urang nyieun file .travis.yml sarta nempatkeun eta dina akar proyek. PPSSPP parantos ngagaduhan file konfigurasi pikeun Travis CI, tapi éta ageung teuing sareng henteu cocog pikeun conto, janten urang kedah nyederhanakeun pisan sareng ngan ukur ngantunkeun unsur dasar.

Mimiti, hayu urang nunjukkeun basa, versi Linux Ubuntu anu urang hoyong dianggo dina mesin virtual, sareng bungkusan anu dipikabutuh pikeun ngawangun:

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'

Sadaya pakét anu didaptarkeun diperyogikeun sacara éksklusif pikeun PPSSPP.

Ayeuna urang nunjukkeun matrix assembly:

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

A saeutik leuwih ngeunaan bagian matrix. Dina Travis CI, aya dua cara pikeun nyieun pilihan ngawangun: kahiji nya éta nangtukeun daptar compiler, jenis sistem operasi, variabel lingkungan, jeung sajabana, nu satutasna a matrix sadaya kombinasi mungkin dihasilkeun; kadua mangrupa indikasi eksplisit matrix. Tangtosna, anjeun tiasa ngagabungkeun dua pendekatan ieu sareng nambihan kasus anu unik, atanapi, sabalikna, ngaluarkeun éta nganggo bagian. ngaluarkeun. Anjeun tiasa maca langkung seueur ngeunaan ieu dina dokuméntasi Travis CI.

Sadaya anu tetep nyaéta nyayogikeun pitunjuk perakitan khusus pikeun proyék:

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 ngidinan Anjeun pikeun nambahkeun paréntah sorangan pikeun sagala rupa tahapan kahirupan mesin virtual. Bagian before_install dieksekusi sateuacan masang bungkusan. Saterusna masang, anu nuturkeun pamasangan bungkusan tina daptar addons.aptanu kami nunjukkeun di luhur. Majelis sorangan lumangsung dina naskah. Upami sadayana lancar, maka urang mendakan diri urang after_success (éta dina bagian ieu urang bakal ngajalankeun analisis statik). Ieu sanés sadayana léngkah anu tiasa dirobih, upami anjeun peryogi langkung seueur, anjeun kedah milarian dokuméntasi Travis CI.

Pikeun gampang maca, paréntah disimpen dina naskah anu misah .travis.sh, anu disimpen dina akar proyék.

Janten urang gaduh file di handap ieu .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

Sateuacan masang bungkusan, kami bakal ngapdet submodul. Ieu diperlukeun pikeun ngawangun PPSSPP. Hayu urang tambahkeun fungsi munggaran ka .travis.sh (perhatikeun extension):

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

Ayeuna urang langsung ka nyetél peluncuran otomatis PVS-Studio di Travis CI. Mimiti urang kedah masang pakét PVS-Studio dina sistem:

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
}

Dina awal fungsi travis_install urang masang kompiler anu urang peryogikeun nganggo variabel lingkungan. Lajeng lamun variabel $PVS_ANALYZE nilai toko nuhun (Kami nunjukkeun éta dina bagian utusan salila ngawangun konfigurasi matrix), urang install pakét pvs-studio. Sajaba ti éta, bungkusan ogé dituduhkeun libio-stop kontak-ssl-perl и libnet-ssleay-perl, kumaha oge, aranjeunna diperlukeun pikeun hasil milis, jadi maranéhna teu diperlukeun lamun geus milih metoda sejen pikeun delivering laporan Anjeun.

fungsi download_extract undeur jeung unpacks arsip dieusian:

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

Geus waktuna pikeun nunda proyék babarengan. Ieu lumangsung dina bagian naskah:

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
}

Nyatana, ieu mangrupikeun konfigurasi asli anu disederhanakeun, iwal ti garis ieu:

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

Dina bagian ieu kode urang diatur pikeun cmkeun bandéra pikeun ékspor paréntah kompilasi. Ieu dipikabutuh pikeun analisa kode statik. Anjeun tiasa maca langkung seueur ngeunaan ieu dina tulisan "Kumaha ngajalankeun PVS-Studio dina Linux sareng macOS".

Lamun assembly éta suksés, lajeng urang meunang ka after_success, dimana urang ngalakukeun analisa statik:

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
}

Hayu urang nempo leuwih deukeut garis handap:

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

Baris kahiji ngahasilkeun file lisénsi ti ngaran pamaké sarta konci nu urang dieusian di pisan awal nalika nyetel variabel lingkungan Travis CI.

Baris kadua dimimitian analisis langsung. Bandéra -j susunan jumlah threads pikeun analisis, bandéra -l nunjukkeun lisénsi, bandéra -o ngahartikeun file pikeun outputting log, sarta bandéra -disableLicenseExpirationCheck diperlukeun pikeun versi percobaan, saprak sacara standar pvs-studio-analyzer bakal ngingetkeun pangguna yén lisénsina badé béak. Pikeun nyegah ieu lumangsung, Anjeun bisa nangtukeun bandéra ieu.

File log ngandung kaluaran atah anu teu tiasa dibaca tanpa konvérsi, janten anjeun kedah ngajantenkeun filena tiasa dibaca. Hayu urang lulus log ngaliwatan plog-converter, sarta kaluaran mangrupa file html.

Dina conto ieu, kuring mutuskeun pikeun ngirim laporan ku mail ngagunakeun paréntah kiriman surat.

Hasilna, urang ngagaduhan file di handap ieu .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;

Ayeuna waktuna pikeun nyorong parobihan kana gudang git, saatos éta Travis CI bakal otomatis ngajalankeun ngawangun. Klik "ppsspp" pikeun muka laporan ngawangun:

Kumaha nyetél PVS-Studio di Travis CI nganggo émulator PSP sabagé conto
Urang bakal ningali tinjauan wangunan ayeuna:

Kumaha nyetél PVS-Studio di Travis CI nganggo émulator PSP sabagé conto
Upami ngawangun parantos réngsé, kami bakal nampi email kalayan hasil analisa statik. Tangtosna, milis sanés hiji-hijina jalan pikeun nampi laporan. Anjeun tiasa milih metode palaksanaan naon waé. Tapi penting pikeun émut yén saatos ngawangun réngsé, éta moal tiasa ngaksés file mesin virtual.

Ringkesan kasalahan

Kami parantos suksés ngalengkepan bagian anu paling hese. Ayeuna hayu urang pastikeun yén sagala usaha urang patut eta. Hayu urang tingali sababaraha titik anu pikaresepeun tina laporan analisa statik anu sumping ka kuring ku mail (henteu kanggo nanaon kuring nunjukkeun éta).

optimasi bahaya

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

Peringatan PVS-Studio: V597 Kompiler tiasa ngahapus telepon fungsi 'memset', anu dianggo pikeun nyéépkeun panyangga 'jumlah'. Fungsi RtlSecureZeroMemory () kedah dianggo pikeun mupus data pribadi. sha1.cpp 325

Potongan kode ieu aya dina modul hashing anu aman, tapi ngandung cacad kaamanan anu serius (CWE-14). Hayu urang tingali daptar assembly anu dihasilkeun nalika nyusun versi Debug:

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

Sagalana aya dina urutan jeung fungsi memeset dieksekusi, kukituna overwriting data penting dina RAM, kumaha oge, ulah girang ngan acan. Hayu urang tingali daptar rakitan versi Release sareng optimasi:

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

Sapertos tiasa ditingali tina daptar, kompiler teu malire sauran memeset. Ieu alatan kanyataan yén dina fungsi sha1 sanggeus nelepon memeset euweuh deui rujukan pikeun struktur CTX. Ku alatan éta, compiler nu nilik euweuh titik dina wasting processor waktos overwriting memori nu teu dipaké dina mangsa nu bakal datang. Anjeun tiasa ngalereskeun ieu ku ngagunakeun fungsi RtlSecureZeroMemory atawa mirip ka manehna.

Bener:

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

Babandingan teu perlu

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

Peringatan PVS-Studio: V547 Babasan 'leftvol>= 0' sok leres. sceAudio.cpp 120

Nengetan cabang sejenna pikeun kahiji if. Kode bakal dieksekusi ngan lamun sagala kaayaan leftvol> 0xFFFF || volume katuhu > 0xFFFF || kéncavol < 0 || katuhuvol <0 bakal tétéla palsu. Ku alatan éta, urang meunang pernyataan di handap ieu, nu bakal bener keur cabang sejenna: volume kénca <= 0xFFFF, volume katuhu <= 0xFFFF, vol kenca >= 0 и vol katuhu >= 0. Perhatikeun dua pernyataan panungtungan. Teu asup akal pikeun pariksa naon kaayaan anu dipikabutuh pikeun palaksanaan sapotong kode ieu?

Janten urang tiasa aman ngahapus pernyataan kondisional ieu:

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

skenario sejen. Aya sababaraha jenis kasalahan disumputkeun tukangeun kaayaan kaleuleuwihan ieu. Panginten aranjeunna henteu pariksa naon anu diperyogikeun.

Ctrl + C Ctrl + V neunggeul deui

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 Aya sub-ekspresi idéntik '!Memory::IsValidAddress(psmfData)' ka kénca jeung ka katuhu tina '||' operator. scePsmf.cpp 703

Nengetan cek di jero if. Naha anjeun teu nganggap aneh yén urang pariksa naha alamatna valid? psmfData, dua kali leuwih? Ku kituna ieu sigana aneh mun kuring ... Kanyataanna, ieu, tangtosna, typo a, sarta gagasan éta pikeun pariksa duanana parameter input.

Pilihan anu leres:

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

variabel poho

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

Peringatan PVS-Studio: V547 Ekspresi 'ukuran == 8' sok palsu. syn-att.c 195

Kasalahan ieu aya dina polder ext, jadi teu bener relevan pikeun proyék, tapi bug ieu kapanggih saméméh kuring noticed eta, jadi kuring mutuskeun ninggalkeun eta. Barina ogé, tulisan ieu sanés ngeunaan marios kasalahan, tapi ngeunaan integrasi sareng Travis CI, sareng henteu aya konfigurasi analisa anu dilaksanakeun.

Variabel ukuran ieu initialized ku konstanta, kumaha oge, teu dipake pisan dina kode, katuhu ka handap pikeun operator nu if, anu, tangtosna, masihan palsu bari mariksa kaayaan, sabab, sakumaha urang inget, ukuran sarua jeung nol. Cék satuluyna ogé teu aya hartina.

Tétéla, panulis sempalan kode poho pikeun nimpa variabel ukuran saméméh éta.

Eureun

Ieu dimana urang sigana bakal mungkas ku kasalahan. Tujuan tina tulisan ieu nyaéta pikeun nunjukkeun karya PVS-Studio sareng Travis CI, sareng henteu nganalisa proyék sacara saksama-gancang. Upami anjeun hoyong kasalahan anu langkung ageung sareng langkung saé, anjeun salawasna tiasa muja aranjeunna di dieu :).

kacindekan

Nganggo jasa wéb pikeun ngawangun proyék sareng prakték analisis incremental ngamungkinkeun anjeun mendakan seueur masalah langsung saatos ngahijikeun kode. Sanajan kitu, hiji wangunan bisa jadi teu cukup, jadi nyetel nguji babarengan jeung analisis statik bakal nyata ngaronjatkeun kualitas kode.

link mangpaat

Kumaha nyetél PVS-Studio di Travis CI nganggo émulator PSP sabagé conto

Upami anjeun hoyong ngabagikeun tulisan ieu ka pamiarsa anu nyarios basa Inggris, mangga nganggo tautan tarjamahan: Maxim Zvyagintsev. Kumaha nyetél PVS-Studio di Travis CI nganggo conto émulator konsol game PSP.

sumber: www.habr.com

Tambahkeun komentar