
Dari waktu ke waktu kami harus menulis artikel tentang memeriksa versi compiler berikutnya. Ini tidak menarik. Namun, seperti yang diperlihatkan oleh praktik, jika Anda tidak melakukan ini untuk waktu yang lama, orang mulai meragukan apakah penganalisis PVS-Studio layak untuk judul penangkap bug yang baik dan potensi kerentanan. Mungkin kompiler baru sudah tahu bagaimana melakukan ini? Ya, penyusun tidak tinggal diam. Namun, PVS-Studio juga berkembang, berulang kali mendemonstrasikan kemampuan untuk menemukan kesalahan bahkan dalam kode proyek berkualitas tinggi seperti kompiler.
Saatnya untuk memeriksa ulang kode Clang
Sejujurnya, saya mengambil artikel " Memeriksa GCC 10 Compiler Menggunakan PVS-Studio " sebagai dasar teks ini . Jadi jika menurut Anda Anda sudah membaca beberapa paragraf di suatu tempat, Anda tidak berpikir :).
Bukan rahasia lagi bahwa kompiler memiliki penganalisis kode statis bawaan mereka sendiri dan mereka juga sedang dikembangkan. Oleh karena itu, dari waktu ke waktu kami menulis artikel tentang bagaimana penganalisis statis PVS-Studio dapat menemukan kesalahan bahkan di dalam kompiler dan bahwa kami memakan roti karena suatu alasan :).
Nyatanya, Anda tidak dapat membandingkan penganalisis statis klasik dengan kompiler. Penganalisis statis tidak hanya tentang menemukan kesalahan dalam kode, tetapi juga infrastruktur yang dikembangkan. Misalnya, ini adalah integrasi dengan sistem seperti SonarQube, PlatformIO, Azure DevOps, Travis CI, CircleCI, GitLab CI / CD, Jenkins, Visual Studio. Ini adalah mekanisme lanjutan untuk penekanan massal terhadap peringatan, yang memungkinkan Anda dengan cepat mulai menggunakan PVS-Studio bahkan dalam proyek lama yang besar. Ini adalah distribusi notifikasi. Dan lain sebagainya. Namun, ini masih menjadi pertanyaan pertama yang ditanyakan: "Dapatkah PVS-Studio menemukan sesuatu yang tidak dapat ditemukan oleh penyusun?" Ini berarti bahwa kami akan berulang kali menulis artikel tentang memeriksa sendiri kompiler ini.
Mari kembali ke topik memeriksa proyek Clang. Tidak perlu memikirkan proyek ini dan memberi tahu apa itu. Faktanya, tidak hanya kode Clang 11 itu sendiri yang diuji, tetapi juga library LLVM 11 yang membuatnya. Dari sudut pandang artikel ini, tidak ada bedanya apakah cacat ditemukan dalam kompilator atau kode perpustakaan.
Bagi saya, kode Dentang / LLVM jauh lebih jelas daripada kode GCC. Setidaknya semua makro yang mengerikan ini hilang, dan fitur modern dari bahasa C ++ secara aktif digunakan.
Meskipun demikian, proyeknya besar, dan tanpa pengaturan awal penganalisis, masih sangat membosankan untuk melihat laporannya. Pada dasarnya, positif setengah palsu mengganggu. Yang saya maksud dengan positif semi-palsu adalah situasi ketika penganalisis secara formal benar, tetapi peringatan tidak masuk akal. Misalnya, banyak positif seperti itu dikeluarkan untuk pengujian unit dan kode yang dihasilkan.
Contoh untuk tes:
Spaces.SpacesInParentheses = false; // <=
Spaces.SpacesInCStyleCastParentheses = true; // <=
verifyFormat("Type *A = ( Type * )P;", Spaces);
verifyFormat("Type *A = ( vector<Type *, int *> )P;", Spaces);
verifyFormat("x = ( int32 )y;", Spaces);
verifyFormat("int a = ( int )(2.0f);", Spaces);
verifyFormat("#define AA(X) sizeof((( X * )NULL)->a)", Spaces);
verifyFormat("my_int a = ( my_int )sizeof(int);", Spaces);
verifyFormat("#define x (( int )-1)", Spaces);
// Run the first set of tests again with:
Spaces.SpacesInParentheses = false; // <=
Spaces.SpaceInEmptyParentheses = true;
Spaces.SpacesInCStyleCastParentheses = true; // <=
verifyFormat("call(x, y, z);", Spaces);
verifyFormat("call( );", Spaces);
Penganalisis memperingatkan bahwa variabel diberi nilai yang sama dengan yang sudah dimilikinya:
- V1048 Variabel 'Spaces.SpacesInParentheses' diberi nilai yang sama. FormatTest.cpp 11554
- V1048 Variabel 'Spaces.SpacesInCStyleCastParentheses' diberi nilai yang sama. FormatTest.cpp 11556
Secara formal, penganalisis mengembalikan respons yang benar, dan ini adalah potongan kode yang harus disederhanakan atau diperbaiki. Pada saat yang sama, jelaslah bahwa, pada kenyataannya, semuanya baik-baik saja dan tidak ada gunanya mengedit sesuatu juga.
Contoh lain: penganalisis mengeluarkan sejumlah besar peringatan ke file Options.inc yang dibuat secara otomatis. Anda dapat melihat "lembar kode" di file:

Dan PVS-Studio mengirimkan peringatan ke semua ini:
- V501 Ada sub-ekspresi identik di kiri dan kanan operator '==': nullptr == nullptr Options.inc 26
- V501 Ada sub-ekspresi identik di kiri dan kanan operator '==': nullptr == nullptr Options.inc 27
- V501 Ada sub-ekspresi identik di kiri dan kanan operator '==': nullptr == nullptr Options.inc 28
- dan seterusnya untuk setiap baris ...
Semua ini tidak menakutkan. Semua ini dapat dikalahkan: nonaktifkan pemeriksaan file yang tidak perlu, tandai beberapa makro dan fungsi, sembunyikan jenis alarm tertentu, dan sebagainya. Mungkin saja, tetapi melakukan ini sebagai bagian dari tugas menulis artikel tidaklah menarik. Oleh karena itu, saya melakukan hal yang sama persis seperti di artikel tentang compiler GCC. Saya mempelajari laporan sampai saya memiliki 11 contoh kode yang menarik untuk menulis artikel. Mengapa 11? Saya pikir karena versi Clang adalah 11, maka biarkan fragmennya menjadi 11 :).
11 potongan kode yang mencurigakan
Fragmen N1, pembagian modulo satu

Kesalahan keren! Aku suka ini!
void Act() override {
....
// If the value type is a vector, and we allow vector select, then in 50%
// of the cases generate a vector select.
if (isa<FixedVectorType>(Val0->getType()) && (getRandom() % 1)) {
unsigned NumElem =
cast<FixedVectorType>(Val0->getType())->getNumElements();
CondTy = FixedVectorType::get(CondTy, NumElem);
}
....
}
Peringatan PVS-Studio: V1063 Operasi modulo by 1 tidak ada artinya. Hasilnya akan selalu nol. llvm-stress.cpp 631 Modulo
divisi digunakan untuk mendapatkan nilai acak 0 atau 1. Namun, ternyata, nilai 1 ini membingungkan, dan orang membuat pola kesalahan klasik, membaginya dengan satu, meskipun perlu untuk membagi dua. Operasi X% 1 tidak ada artinya, karena hasilnya selalu 0 . Versi kode yang benar:
if (isa<FixedVectorType>(Val0->getType()) && (getRandom() % 2)) {
Diagnostik V1063, yang baru-baru ini muncul di PVS-Studio, sangat sederhana, tetapi, seperti yang Anda lihat, ini berhasil.
Seperti yang kita ketahui, pengembang kompilator melihat apa yang kami lakukan dan meminjam praktik terbaik kami. Tidak ada yang salah dengan itu. Kami senang bahwa PVS-Studio adalah mesin kemajuan . Kami mengatur waktu setelah berapa banyak diagnosis yang sama akan muncul di Clang dan GCC :).
Fragmen N2, kondisi salah ketik
class ReturnValueSlot {
....
bool isNull() const { return !Addr.isValid(); }
....
};
static bool haveSameParameterTypes(ASTContext &Context, const FunctionDecl *F1,
const FunctionDecl *F2, unsigned NumParams) {
....
unsigned I1 = 0, I2 = 0;
for (unsigned I = 0; I != NumParams; ++I) {
QualType T1 = NextParam(F1, I1, I == 0);
QualType T2 = NextParam(F2, I2, I == 0);
if (!T1.isNull() && !T1.isNull() && !Context.hasSameUnqualifiedType(T1, T2))
return false;
}
return true;
}
Peringatan PVS-Studio: V501 Terdapat sub-ekspresi identik di kiri dan kanan operator '&&' :! T1.isNull () &&! T1.isNull () SemaOverload.cpp 9493 Memeriksa
dua kali ! T1.isNull () . Ini adalah kesalahan ketik yang jelas, dan bagian kedua dari kondisi tersebut harus memeriksa variabel T2 .
Fragmen N3, potensi di luar batas array
std::vector<Decl *> DeclsLoaded;
SourceLocation ASTReader::getSourceLocationForDeclID(GlobalDeclID ID) {
....
unsigned Index = ID - NUM_PREDEF_DECL_IDS;
if (Index > DeclsLoaded.size()) {
Error("declaration ID out-of-range for AST file");
return SourceLocation();
}
if (Decl *D = DeclsLoaded[Index])
return D->getLocation();
....
}
Peringatan PVS-Studio: V557 Array overrun dimungkinkan. Indeks 'Indeks' mengarah ke luar batas larik. ASTReader.cpp 7318
Misalkan array berisi satu elemen, dan variabel Indeks juga satu. Kondisi (1> 1) salah, dan akibatnya array akan overrun. Pemeriksaan yang benar:
if (Index >= DeclsLoaded.size()) {
Fragmen N4, urutan evaluasi argumen
void IHexELFBuilder::addDataSections() {
....
uint32_t SecNo = 1;
....
Section = &Obj->addSection<OwnedDataSection>(
".sec" + std::to_string(SecNo++), RecAddr,
ELF::SHF_ALLOC | ELF::SHF_WRITE, SecNo);
....
}
Peringatan PVS-Studio: V567 Perilaku tidak ditentukan. Urutan evaluasi argumen tidak ditentukan untuk fungsi 'addSection'. Pertimbangkan untuk memeriksa variabel 'SecNo'. Object.cpp 1223
Perhatikan bahwa argumen SecNo digunakan dua kali dan bertambah seiring waktu. Tidak mungkin untuk mengatakan dalam urutan apa argumen akan dievaluasi. Oleh karena itu, hasil akan bervariasi tergantung pada versi kompiler atau pengaturan kompilasi.
Izinkan saya menjelaskan ini dengan contoh sintetis:
#include <cstdio>
int main()
{
int i = 1;
printf("%d, %d\n", i, i++);
return 0;
}
Tergantung pada kompilernya, baik "1, 2" dan "2, 1" dapat dicetak. Menggunakan Compiler Explorer, saya mendapatkan hasil sebagai berikut:
- program yang dikompilasi dengan Clang 11.0.0 menghasilkan : 1, 1.
- program yang dikompilasi dengan GCC 10.2 menghasilkan : 2, 1.
Menariknya, untuk kasus sederhana ini, compiler Clang mengeluarkan peringatan:
<source>:6:26: warning:
unsequenced modification and access to 'i' [-Wunsequenced]
printf("%d, %d\n", i, i++);
Ternyata, dalam kondisi nyata, peringatan tersebut tidak membantu. Diagnostik dinonaktifkan, karena tidak nyaman untuk penggunaan praktis, atau kompilator tidak dapat memperingatkan tentang kasus yang lebih kompleks.
Fragmen N5, tes ulang aneh
template <class ELFT>
void GNUStyle<ELFT>::printVersionSymbolSection(const ELFFile<ELFT> *Obj,
const Elf_Shdr *Sec) {
....
Expected<StringRef> NameOrErr =
this->dumper()->getSymbolVersionByIndex(Ndx, IsDefault);
if (!NameOrErr) {
if (!NameOrErr) {
unsigned SecNdx = Sec - &cantFail(Obj->sections()).front();
this->reportUniqueWarning(createError(
"unable to get a version for entry " + Twine(I) +
" of SHT_GNU_versym section with index " + Twine(SecNdx) + ": " +
toString(NameOrErr.takeError())));
}
Versions.emplace_back("<corrupt>");
continue;
}
....
}
Peringatan PVS-Studio: V571 Pemeriksaan berulang. Kondisi 'if (! NameOrErr)' telah diverifikasi pada baris 4666. ELFDumper.cpp 4667
Pemeriksaan kedua menduplikasi yang pertama dan berlebihan. Mungkin pemeriksaan kedua bisa dihapus begitu saja. Tetapi kemungkinan besar ini salah ketik, dan variabel yang berbeda harus digunakan dalam kondisi kedua.
Snippet N6, mendereferensi penunjuk yang berpotensi null
void RewriteObjCFragileABI::RewriteObjCClassMetaData(
ObjCImplementationDecl *IDecl, std::string &Result)
{
ObjCInterfaceDecl *CDecl = IDecl->getClassInterface();
if (CDecl->isImplicitInterfaceDecl()) {
RewriteObjCInternalStruct(CDecl, Result);
}
unsigned NumIvars = !IDecl->ivar_empty()
? IDecl->ivar_size()
: (CDecl ? CDecl->ivar_size() : 0);
....
}
Peringatan PVS-Studio: V595 Pointer 'CDecl' digunakan sebelum diverifikasi terhadap nullptr. Baris cek: 5275, 5284. RewriteObjC.cpp 5275
Selama pemeriksaan pertama, penunjuk CDecl selalu direferensikan dengan jelas:
if (CDecl->isImplicitInterfaceDecl())
Dan hanya dari kode yang tertulis di bawah ini menjadi jelas bahwa pointer ini bisa menjadi null:
(CDecl ? CDecl->ivar_size() : 0)
Kemungkinan besar, pemeriksaan pertama akan terlihat seperti ini:
if (CDecl && CDecl->isImplicitInterfaceDecl())
Snippet N7, mendereferensi pointer yang berpotensi null
bool
Sema::InstantiateClass(....)
{
....
NamedDecl *ND = dyn_cast<NamedDecl>(I->NewDecl);
CXXRecordDecl *ThisContext =
dyn_cast_or_null<CXXRecordDecl>(ND->getDeclContext());
CXXThisScopeRAII ThisScope(*this, ThisContext, Qualifiers(),
ND && ND->isCXXInstanceMember());
....
}
Peringatan PVS-Studio: V595 Pointer 'ND' digunakan sebelum diverifikasi terhadap nullptr. Baris cek: 2803, 2805. SemaTemplateInstantiate.cpp 2803
Variasi dari kesalahan sebelumnya. Berbahaya untuk mendereferensi penunjuk tanpa terlebih dahulu memeriksa apakah nilainya diperoleh dengan menggunakan transmisi dinamis. Selain itu, dari kode di bawah ini, Anda dapat melihat bahwa pemeriksaan seperti itu diperlukan.
Fragment N8, fungsi terus dijalankan meskipun status error
bool VerifyObject(llvm::yaml::Node &N,
std::map<std::string, std::string> Expected) {
....
auto *V = llvm::dyn_cast_or_null<llvm::yaml::ScalarNode>(Prop.getValue());
if (!V) {
ADD_FAILURE() << KS << " is not a string";
Match = false;
}
std::string VS = V->getValue(Tmp).str();
....
}
Peringatan PVS-Studio: V1004 Penunjuk 'V' digunakan dengan tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 61, 65. TraceTests.cpp 65
Pointer V mungkin nol. Ini jelas merupakan kondisi yang keliru, yang bahkan dilaporkan. Namun, setelah itu, fungsi melanjutkan eksekusi seolah-olah tidak terjadi apa-apa, yang akan menyebabkan dereferensi penunjuk nol ini. Mungkin mereka lupa mengganggu fungsinya, dan versi yang benar akan terlihat seperti ini:
auto *V = llvm::dyn_cast_or_null<llvm::yaml::ScalarNode>(Prop.getValue());
if (!V) {
ADD_FAILURE() << KS << " is not a string";
Match = false;
return false;
}
std::string VS = V->getValue(Tmp).str();
Snippet N9, salah ketik
const char *tools::SplitDebugName(const ArgList &Args, const InputInfo &Input,
const InputInfo &Output) {
if (Arg *A = Args.getLastArg(options::OPT_gsplit_dwarf_EQ))
if (StringRef(A->getValue()) == "single")
return Args.MakeArgString(Output.getFilename());
Arg *FinalOutput = Args.getLastArg(options::OPT_o);
if (FinalOutput && Args.hasArg(options::OPT_c)) {
SmallString<128> T(FinalOutput->getValue());
llvm::sys::path::replace_extension(T, "dwo");
return Args.MakeArgString(T);
} else {
// Use the compilation dir.
SmallString<128> T(
Args.getLastArgValue(options::OPT_fdebug_compilation_dir));
SmallString<128> F(llvm::sys::path::stem(Input.getBaseInput()));
llvm::sys::path::replace_extension(F, "dwo");
T += F;
return Args.MakeArgString(F); // <=
}
}
Peringatan PVS-Studio: V1001 Variabel 'T' ditetapkan tetapi tidak digunakan di akhir fungsi. CommonArgs.cpp 873
Perhatikan akhir dari fungsinya. Variabel lokal T berubah tetapi tidak digunakan dengan cara apa pun. Kemungkinan besar, ini salah ketik dan fungsinya harus diakhiri dengan baris kode berikut:
T += F;
return Args.MakeArgString(T);
Fragmen N10, pembagi adalah nol
typedef int32_t si_int;
typedef uint32_t su_int;
typedef union {
du_int all;
struct {
#if _YUGA_LITTLE_ENDIAN
su_int low;
su_int high;
#else
su_int high;
su_int low;
#endif // _YUGA_LITTLE_ENDIAN
} s;
} udwords;
COMPILER_RT_ABI du_int __udivmoddi4(du_int a, du_int b, du_int *rem) {
....
if (d.s.low == 0) {
if (d.s.high == 0) {
// K X
// ---
// 0 0
if (rem)
*rem = n.s.high % d.s.low;
return n.s.high / d.s.low;
}
....
}
Peringatan PVS-Studio:
- V609 Mod oleh nol. Penyebut 'dslow' == 0.udivmoddi4.c 61
- V609 Bagilah dengan nol. Penyebut 'dslow' == 0.udivmoddi4.c 62
Saya tidak tahu apakah ini kesalahan atau ide yang rumit, tetapi kodenya sangat aneh. Ada dua variabel bilangan bulat biasa, dan satu dapat dibagi dengan yang lain. Menariknya, ini hanya terjadi jika kedua variabel bernilai nol. Apa maksud semua ini?
Fragmen N11, Salin-Tempel
bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly(....)
{
....
StringRef FName = II->getName();
....
if (FName == "postEvent" &&
FD->getQualifiedNameAsString() == "QCoreApplication::postEvent") {
return true;
}
if (FName == "postEvent" &&
FD->getQualifiedNameAsString() == "QCoreApplication::postEvent") {
return true;
}
....
}
Peringatan PVS-Studio: V581 Ekspresi bersyarat dari pernyataan 'jika' yang terletak berdampingan adalah identik. Baris centang: 3108, 3113. MallocChecker.cpp 3113
Potongan kode telah disalin, tetapi tidak diubah dengan cara apa pun. Cuplikan kedua harus dihapus atau dimodifikasi untuk mulai melakukan pemeriksaan yang berguna.
Kesimpulan

Izinkan saya mengingatkan Anda bahwa Anda dapat menggunakan opsi lisensi gratis ini untuk memeriksa proyek open source . Ngomong-ngomong, ada opsi lain untuk melisensikan PVS-Studio secara gratis, termasuk bahkan untuk proyek tertutup. Mereka tercantum di sini: " Opsi lisensi PVS-Studio gratis ". Terima kasih atas perhatian Anda.
Artikel kami yang lain tentang pemeriksaan kompiler
- Analisis LLVM (Clang) (Agustus 2011), analisis kedua (Agustus 2012), analisis ketiga (Oktober 2016), analisis keempat (April 2019)
- Analisis GCC (Agustus 2016), analisis kedua (April 2020)
- Analisis Huawei Ark Compiler (Desember 2019)
- Analisis .NET Compiler Platform ("Roslyn") (Desember 2015), analisis kedua (April 2019)
- Analisis Roslyn Analyzers (Agustus 2019)
- Analisis PascalABC.NET (Maret 2017)

Jika Anda ingin berbagi artikel ini dengan audiens berbahasa Inggris, silakan gunakan tautan terjemahan: Andrey Karpov. Memeriksa Clang 11 dengan PVS-Studio .