Sudah dua tahun sejak kami terakhir memeriksa kode proyek LLVM dengan PVS-Studio, jadi mari kita lihat apakah PVS-Studio masih menjadi pemimpin di antara alat untuk mendeteksi bug dan kelemahan keamanan. Kami akan melakukannya dengan memindai rilis LLVM 8.0.0 untuk bug baru.
Artikel yang harus ditulis
Terus terang, saya tidak ingin menulis artikel ini. Tidak terlalu menyenangkan membicarakan proyek yang sudah kami periksa lebih dari satu kali (
1 ,
2 ,
3 ). Saya lebih suka sesuatu yang baru, tetapi saya tidak punya pilihan.
Setiap kali versi baru LLVM dirilis atau
Clang Static Analyzer dimutakhirkan, kami mendapatkan email yang dibaca di sepanjang baris ini:
Hei, versi baru Clang Static Analyzer mendapat diagnostik baru! PVS-Studio tampaknya semakin tidak relevan. Dentang dapat mendeteksi lebih banyak bug dari sebelumnya dan sekarang menyusul dengan PVS-Studio. Apa yang kamu katakanUntuk itu saya dengan senang hati merespons:
Kami juga belum bermalas-malasan! Kami telah secara signifikan meningkatkan kemampuan PVS-Studio, jadi jangan khawatir - kami masih yang terbaik.
Tapi itu jawaban yang buruk, saya khawatir. Ia tidak menawarkan bukti, dan itulah alasan mengapa saya menulis artikel ini. Jadi, saya telah memeriksa LLVM sekali lagi dan menemukan banyak bug dari semua jenis. Yang paling saya sukai akan dibahas lebih lanjut. Clang Static Analyzer tidak dapat mendeteksi bug ini (atau membuat prosesnya sangat merepotkan) - dan kami bisa. Ngomong-ngomong, aku hanya butuh satu malam untuk menulis semua bug itu.
Artikel itu membutuhkan waktu beberapa minggu untuk saya selesaikan. Aku hanya tidak sanggup memasukkan materi yang dikumpulkan ke dalam teks :)
Omong-omong, jika Anda bertanya-tanya teknik apa yang digunakan PVS-Studio untuk mendeteksi bug dan kerentanan, lihat
posting ini.
Diagnostik baru dan yang sudah ada
Seperti yang sudah saya katakan, yang terakhir dari banyak pemeriksaan LLVM dilakukan dua tahun lalu, dan bug yang ditemukan kemudian diperbaiki oleh penulis. Artikel ini akan menunjukkan sebagian kesalahan baru. Kenapa ada bug baru sama sekali? Ada tiga alasan:
- Proyek LLVM sedang berkembang; penulis memodifikasi kode yang ada dan menambahkan kode baru. Kedua bagian yang dimodifikasi dan baru secara alami memiliki bug baru. Fakta ini adalah argumen yang kuat untuk menjalankan analisis statis secara teratur daripada sesekali. Format artikel kami sangat cocok untuk menunjukkan kemampuan PVS-Studio, tetapi tidak ada hubungannya dengan meningkatkan kualitas kode atau membuat perbaikan bug lebih murah. Gunakan analisis statis secara teratur!
- Kami memodifikasi dan meningkatkan diagnostik yang ada, memungkinkan penganalisa untuk mendeteksi bug yang sebelumnya tidak dapat dikenali.
- PVS-Studio telah ditingkatkan dengan diagnostik baru, yang tidak ada dua tahun lalu. Saya mengelompokkan peringatan tersebut ke dalam bagian terpisah sehingga kemajuan PVS-Studio terlihat lebih jelas.
Kerusakan ditemukan oleh diagnostik yang ada
Cuplikan no. 1: Copy-Pastestatic bool ShouldUpgradeX86Intrinsic(Function *F, StringRef Name) { if (Name == "addcarryx.u32" ||
Pesan diagnostik PVS-Studio:
V501 [CWE-570] Ada sub-ekspresi identik 'Name.startswith ("avx512.mask.permvar.")' Di sebelah kiri dan di sebelah kanan '||' operator. AutoUpgrade.cpp 73
Terjadinya "avx512.mask.permvar." Substring diperiksa dua kali. Kondisi kedua jelas untuk memeriksa sesuatu yang lain, tetapi programmer lupa untuk mengubah jalur yang disalin.
Cuplikan no. 2: Typo enum CXNameRefFlags { CXNameRange_WantQualifier = 0x1, CXNameRange_WantTemplateArgs = 0x2, CXNameRange_WantSinglePiece = 0x4 }; void AnnotateTokensWorker::HandlePostPonedChildCursor( CXCursor Cursor, unsigned StartTokenIndex) { const auto flags = CXNameRange_WantQualifier | CXNameRange_WantQualifier; .... }
Pesan diagnostik PVS-Studio: V501 Ada sub-ekspresi identik 'CXNameRange_WantQualifier' di kiri dan di kanan '|' operator. CIndex.cpp 7245
CXNameRange_WantQualifier konstanta bernama digunakan dua kali karena kesalahan ketik.
Cuplikan no. 3: Kebingungan tentang prioritas operator int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) { .... if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0) return 0; .... }
Pesan diagnostik PVS-Studio:
V502 [CWE-783] Mungkin operator '?:' Bekerja dengan cara yang berbeda dari yang diharapkan. Operator '?:' Memiliki prioritas lebih rendah daripada operator '=='. PPCTargetTransformInfo.cpp 404
Saya menemukan bug ini sangat lucu. Ya, saya tahu bahwa saya memiliki rasa yang aneh :).
Seperti yang ditentukan oleh
prioritas operator , ekspresi asli dievaluasi sebagai berikut:
(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0
Dari sudut pandang praktis, kondisi ini tidak masuk akal karena dapat direduksi menjadi:
(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())
Ini jelas bug. Itu pasti variabel
Indeks yang ingin diperiksa oleh programmer untuk 0/1. Untuk memperbaiki kode, operator ternary harus dilampirkan dalam tanda kurung:
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))
Operator ternary sebenarnya sangat rumit dan dapat menyebabkan kesalahan logika. Gunakan dengan hati-hati dan jangan ragu untuk menempatkan tanda kurung tambahan di sekitarnya. Subjek ini dibahas lebih rinci di
sini , di bagian "Waspadai ?: Operator dan lampirkan dalam tanda kurung".
Cuplikan no. 4, 5: Null pointer Init *TGParser::ParseValue(Record *CurRec, RecTy *ItemType, IDParseMode Mode) { .... TypedInit *LHS = dyn_cast<TypedInit>(Result); .... LHS = dyn_cast<TypedInit>( UnOpInit::get(UnOpInit::CAST, LHS, StringRecTy::get()) ->Fold(CurRec)); if (!LHS) { Error(PasteLoc, Twine("can't cast '") + LHS->getAsString() + "' to string"); return nullptr; } .... }
Pesan diagnostik PVS-Studio:
V522 [CWE-476] Dereferencing dari null pointer 'LHS' mungkin terjadi. TGParser.cpp 2152
Jika pointer
LHS menjadi nol, program diharapkan menghasilkan peringatan. Sebagai gantinya, itu akan
men-decere pointer yang sangat null:
LHS-> getAsString () .
Ini adalah situasi yang cukup khas bagi penangan kesalahan untuk mengandung bug karena pengembang tidak mengujinya dengan benar. Analisis statis memeriksa semua kode yang dapat dijangkau, tidak peduli seberapa sering itu sebenarnya dieksekusi. Ini adalah contoh yang baik tentang bagaimana analisis statis melengkapi pengujian kode lain dan sarana perlindungan.
Penangan yang salah serupa untuk pointer
RHS ditemukan sedikit lebih jauh: V522 [CWE-476] Dereferencing dari null pointer 'RHS' mungkin terjadi. TGParser.cpp 2186
Cuplikan no. 6: Menggunakan pointer setelah pindah static Expected<bool> ExtractBlocks(....) { .... std::unique_ptr<Module> ProgClone = CloneModule(BD.getProgram(), VMap); .... BD.setNewProgram(std::move(ProgClone));
Pesan diagnostik PVS-Studio: V522 [CWE-476] Dereferencing dari null pointer 'ProgClone' mungkin terjadi. Miscompilation.cpp 601
ProgClone pointer cerdas pertama melepaskan kepemilikan objek:
BD.setNewProgram(std::move(ProgClone));
Faktanya,
ProgClone telah menjadi sebuah pointer nol - jadi, secara teknis, sebuah pointer null akan terdereferensi lebih jauh:
Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);
Tetapi itu tidak akan terjadi! Perhatikan bahwa loop tidak benar-benar dieksekusi sama sekali.
Wadah
MiscompiledFunctions pertama kali dihapus:
MiscompiledFunctions.clear();
Dan kemudian ukurannya digunakan dalam kondisi loop:
for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {
Jelas, loop tidak akan dimulai. Saya pikir itu bug juga, dan kode itu dimaksudkan untuk terlihat berbeda.
Saya kira apa yang kita lihat di sini adalah parity error yang terkenal, di mana satu bug bertindak sebagai penyamaran untuk :) lainnya.
Cuplikan no. 7: Menggunakan pointer setelah pindah static Expected<bool> TestOptimizer(BugDriver &BD, std::unique_ptr<Module> Test, std::unique_ptr<Module> Safe) { outs() << " Optimizing functions being tested: "; std::unique_ptr<Module> Optimized = BD.runPassesOn(Test.get(), BD.getPassesToRun()); if (!Optimized) { errs() << " Error running this sequence of passes" << " on the input program!\n"; BD.setNewProgram(std::move(Test));
Pesan diagnostik PVS-Studio: V522 [CWE-476] Dereferencing dari null pointer 'Test' mungkin terjadi. Miscompilation.cpp 709
Yang ini mirip dengan kasus sebelumnya. Isi objek pertama-tama dipindahkan dan kemudian digunakan seolah-olah tidak ada yang terjadi. Kesalahan ini telah tumbuh semakin umum setelah pindahan semantik ditambahkan ke C ++. Itulah yang saya sukai dari bahasa ini! Anda diberi cara baru untuk menembak diri sendiri, yang berarti PVS-Studio akan selalu memiliki pekerjaan untuk dilakukan :).
Cuplikan no. 8: Null pointer void FunctionDumper::dump(const PDBSymbolTypeFunctionArg &Symbol) { uint32_t TypeId = Symbol.getTypeId(); auto Type = Symbol.getSession().getSymbolById(TypeId); if (Type) Printer << "<unknown-type>"; else Type->dump(*this); }
Pesan diagnostik PVS-Studio: V522 [CWE-476] Dereferencing dari null pointer 'Type' mungkin terjadi. PrettyFunctionDumper.cpp 233
Sama seperti penangan kesalahan, fungsi pengujian mencetak data debug biasanya tidak mendapatkan cakupan pengujian yang memadai, dan ini adalah salah satu contohnya. Alih-alih membantu pengguna memecahkan masalah mereka, fungsi menunggu mereka untuk memperbaikinya.
Kode tetap:
if (Type) Type->dump(*this); else Printer << "<unknown-type>";
Cuplikan no. 9: Null pointer void SearchableTableEmitter::collectTableEntries( GenericTable &Table, const std::vector<Record *> &Items) { .... RecTy *Ty = resolveTypes(Field.RecType, TI->getType()); if (!Ty)
Pesan diagnostik PVS-Studio: V522 [CWE-476] Dereferencing dari null pointer 'Ty' mungkin terjadi. SearchableTableEmitter.cpp 614
Saya tidak berpikir Anda perlu komentar yang satu ini.
Cuplikan no. 10: Typo bool FormatTokenLexer::tryMergeCSharpNullConditionals() { .... auto &Identifier = *(Tokens.end() - 2); auto &Question = *(Tokens.end() - 1); .... Identifier->ColumnWidth += Question->ColumnWidth; Identifier->Type = Identifier->Type;
Pesan diagnostik PVS-Studio:
V570 Variabel 'Identifier-> Type' ditugaskan untuk dirinya sendiri. FormatTokenLexer.cpp 249
Menetapkan variabel untuk dirinya sendiri adalah operasi yang tidak berarti. Programmer harus bermaksud untuk melakukan hal berikut:
Identifier->Type = Question->Type;
Cuplikan no. 11: Istirahat yang mencurigakan void SystemZOperand::print(raw_ostream &OS) const { switch (Kind) { break; case KindToken: OS << "Token:" << getToken(); break; case KindReg: OS << "Reg:" << SystemZInstPrinter::getRegisterName(getReg()); break; .... }
Pesan diagnostik PVS-Studio:
V622 [CWE-478] Pertimbangkan untuk memeriksa pernyataan 'switch'. Mungkin saja operator 'case' pertama hilang. SystemZAsmParser.cpp 652
Ada pernyataan
istirahat yang sangat mencurigakan di awal. Bukankah seharusnya ada hal lain di sini?
Cuplikan no. 12: Memeriksa pointer setelah dereferencing InlineCost AMDGPUInliner::getInlineCost(CallSite CS) { Function *Callee = CS.getCalledFunction(); Function *Caller = CS.getCaller(); TargetTransformInfo &TTI = TTIWP->getTTI(*Callee); if (!Callee || Callee->isDeclaration()) return llvm::InlineCost::getNever("undefined callee"); .... }
Pesan diagnostik PVS-Studio:
V595 [CWE-476] Pointer 'Callee' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 172, 174. AMDGPUInline.cpp 172
Pointer
Callee pertama kali
direferensikan ketika fungsi
getTTI dipanggil.
Dan kemudian ternyata pointer harus diperiksa untuk
nullptr :
if (!Callee || Callee->isDeclaration())
Terlambat ...
Cuplikan no. 13 - Tidak ....: Memeriksa pointer setelah dereferencingContoh sebelumnya tidak unik. Masalah yang sama ditemukan dalam cuplikan ini:
static Value *optimizeDoubleFP(CallInst *CI, IRBuilder<> &B, bool isBinary, bool isPrecise = false) { .... Function *CalleeFn = CI->getCalledFunction(); StringRef CalleeNm = CalleeFn->getName();
Pesan diagnostik PVS-Studio: V595 [CWE-476] Pointer 'CalleeFn' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 1079, 1081. SederhanakanLibCalls.cpp 1079
Dan yang ini:
void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs, const Decl *Tmpl, Decl *New, LateInstantiatedAttrVec *LateAttrs, LocalInstantiationScope *OuterMostScope) { .... NamedDecl *ND = dyn_cast<NamedDecl>(New); CXXRecordDecl *ThisContext = dyn_cast_or_null<CXXRecordDecl>(ND->getDeclContext());
Pesan diagnostik PVS-Studio: V595 [CWE-476] Pointer 'ND' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 532, 534. SemaTemplateInstantiateDecl.cpp 532
Dan di sini:
- V595 [CWE-476] Pointer 'U' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 404, 407. DWARFFormValue.cpp 404
- V595 [CWE-476] Pointer 'ND' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 2149, 2151. SemaTemplateInstantiate.cpp 2149
Lalu saya kehilangan minat untuk melacak peringatan V595, jadi saya tidak bisa memberi tahu Anda jika ada bug lain dari jenis ini selain yang ditunjukkan di atas. Saya yakin ada.
Cuplikan no. 17, 18: Pergeseran mencurigakan static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize, uint64_t &Encoding) { .... unsigned Size = RegSize; .... uint64_t NImms = ~(Size-1) << 1; .... }
Pesan diagnostik PVS-Studio:
V629 [CWE-190] Pertimbangkan untuk memeriksa ekspresi '~ (Ukuran - 1) << 1'. Pergeseran bit dari nilai 32-bit dengan ekspansi selanjutnya ke tipe 64-bit. AArch64AddressingModes.h 260
Kode ini mungkin benar, tetapi terlihat aneh dan perlu diperiksa.
Misalkan variabel
Ukuran memiliki nilai 16; maka variabel
NImms diharapkan untuk mendapatkan nilai berikut:
11111111111111111111111111111111111111111111111111111111111111100000000
Namun pada kenyataannya itu akan mendapatkan nilai:
00000000000000000000000000000000000000111111111111111111111111111111000000
Ini terjadi karena semua perhitungan dilakukan pada tipe unsigned 32-bit, dan baru kemudian secara implisit dipromosikan ke
uint64_t , dengan bit paling signifikan di-
zero -out.
Masalahnya dapat diperbaiki sebagai berikut:
uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;
Bug lain dari jenis ini: V629 [CWE-190] Pertimbangkan untuk memeriksa ekspresi 'Immr << 6'. Pergeseran bit dari nilai 32-bit dengan ekspansi selanjutnya ke tipe 64-bit. AArch64AddressingModes.h 269
Cuplikan no. 19: Kata kunci lain tidak ada ? void AMDGPUAsmParser::cvtDPP(MCInst &Inst, const OperandVector &Operands) { .... if (Op.isReg() && Op.Reg.RegNo == AMDGPU::VCC) {
Pesan diagnostik PVS-Studio:
V646 [CWE-670] Pertimbangkan untuk memeriksa logika aplikasi. Mungkin kata kunci 'lain' tidak ada. AMDGPUAsmParser.cpp 5655
Yang ini bukan bug. Karena
kemudian blok pertama
jika pernyataan diakhiri dengan
melanjutkan , tidak masalah jika memiliki kata kunci
lain atau tidak. Perilaku akan sama dalam hal apa pun. Namun, hal
lain yang hilang membuat kode lebih mudah dibaca dan, karenanya, berpotensi berbahaya. Jika
terus menghilang suatu hari, perilaku itu akan berubah secara drastis. Saya sangat menyarankan untuk menambahkan yang
lain .
Cuplikan no. 20: Empat kesalahan ketik yang identik LLVM_DUMP_METHOD void Symbol::dump(raw_ostream &OS) const { std::string Result; if (isUndefined()) Result += "(undef) "; if (isWeakDefined()) Result += "(weak-def) "; if (isWeakReferenced()) Result += "(weak-ref) "; if (isThreadLocalValue()) Result += "(tlv) "; switch (Kind) { case SymbolKind::GlobalSymbol: Result + Name.str();
Pesan diagnostik PVS-Studio:
- V655 [CWE-480] Senar digabungkan tetapi tidak digunakan. Pertimbangkan untuk memeriksa ekspresi 'Hasil + Nama.str ()'. Symbol.cpp 32
- V655 [CWE-480] Senar digabungkan tetapi tidak digunakan. Pertimbangkan untuk memeriksa ekspresi 'Hasil + "(Kelas ObjC)" + Name.str ()'. Symbol.cpp 35
- V655 [CWE-480] Senar digabungkan tetapi tidak digunakan. Pertimbangkan untuk memeriksa ekspresi 'Hasil + "(ObjC Kelas EH)" + Name.str ()'. Symbol.cpp 38
- V655 [CWE-480] Senar digabungkan tetapi tidak digunakan. Pertimbangkan untuk memeriksa ekspresi 'Hasil + "(ObjC IVar)" + Name.str ()'. Symbol.cpp 41
Programmer secara tidak sengaja menggunakan operator + bukannya + = dan berakhir dengan empat konstruksi yang tidak berarti.
Cuplikan no. 21: Perilaku yang tidak terdefinisi static void getReqFeatures(std::map<StringRef, int> &FeaturesMap, const std::vector<Record *> &ReqFeatures) { for (auto &R : ReqFeatures) { StringRef AsmCondString = R->getValueAsString("AssemblerCondString"); SmallVector<StringRef, 4> Ops; SplitString(AsmCondString, Ops, ","); assert(!Ops.empty() && "AssemblerCondString cannot be empty"); for (auto &Op : Ops) { assert(!Op.empty() && "Empty operator"); if (FeaturesMap.find(Op) == FeaturesMap.end()) FeaturesMap[Op] = FeaturesMap.size(); } } }
Cobalah untuk menemukan bug sendiri terlebih dahulu. Saya menambahkan gambar sehingga Anda tidak mengintip jawabannya segera:
Pesan diagnostik PVS-Studio:
V708 [CWE-758] Konstruksi berbahaya digunakan: 'FeaturesMap [Op] = FeaturesMap.size ()', di mana 'FeaturesMap' adalah dari kelas 'map'. Ini dapat menyebabkan perilaku yang tidak terdefinisi. RISCVCompressInstEmitter.cpp 490
Baris yang salah adalah ini:
FeaturesMap[Op] = FeaturesMap.size();
Jika elemen
Op belum ditemukan, program membuat elemen baru di peta dan menetapkannya jumlah total elemen di peta ini. Anda tidak tahu apakah fungsi
ukuran akan dipanggil sebelum atau setelah menambahkan elemen baru.
Cuplikan no. 22 - Tidak. 24: tugas rangkap Error MachOObjectFile::checkSymbolTable() const { .... } else { MachO::nlist STE = getSymbolTableEntry(SymDRI); NType = STE.n_type;
Pesan diagnostik PVS-Studio:
V519 [CWE-563] Variabel 'NType' diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 1663, 1664. MachOObjectFile.cpp 1664
Saya tidak berpikir itu adalah kesalahan sejati - bukan tugas duplikat. Tapi itu masih cacat.
Dua kasus lainnya:
- V519 [CWE-563] Variabel 'B.NDesc' diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 1488, 1489. llvm-nm.cpp 1489
- V519 [CWE-563] Variabel diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 59, 61. coff2yaml.cpp 61
Cuplikan no. 25 - Tidak. 27: Lebih banyak tugas rangkapYang ini menangani versi tugas duplikat yang sedikit berbeda.
bool Vectorizer::vectorizeLoadChain( ArrayRef<Instruction *> Chain, SmallPtrSet<Instruction *, 16> *InstructionsProcessed) { .... unsigned Alignment = getAlignment(L0); .... unsigned NewAlign = getOrEnforceKnownAlignment(L0->getPointerOperand(), StackAdjustedAlignment, DL, L0, nullptr, &DT); if (NewAlign != 0) Alignment = NewAlign; Alignment = NewAlign; .... }
Pesan diagnostik PVS-Studio: V519 [CWE-563] Variabel 'Alignment' diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 1158, 1160. LoadStoreVectorizer.cpp 1160
Ini adalah potongan yang sangat aneh, dan mungkin berisi kesalahan logika. Variabel
Alignment pertama kali diberi nilai berdasarkan kondisi, dan kemudian diberikan nilai sekali lagi, tetapi tanpa pemeriksaan sebelumnya.
Cacat serupa:
- V519 [CWE-563] Variabel 'Efek' diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 152, 165. WebAssemblyRegStackify.cpp 165
- V519 [CWE-563] Variabel 'ExpectNoDerefChunk' diberi nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 4970, 4973. SemaType.cpp 4973
Cuplikan no. 28: Kondisi selalu benar static int readPrefixes(struct InternalInstruction* insn) { .... uint8_t byte = 0; uint8_t nextByte; .... if (byte == 0xf3 && (nextByte == 0x88 || nextByte == 0x89 || nextByte == 0xc6 || nextByte == 0xc7)) { insn->xAcquireRelease = true; if (nextByte != 0x90)
Pesan diagnostik PVS-Studio:
V547 [CWE-571] Ekspresi 'nextByte! = 0x90' selalu benar. X86DisassemblerDecoder.cpp 379
Cek itu tidak masuk akal. Variabel
nextByte tidak pernah sama dengan
0x90 : itu hanya secara logis mengikuti dari pemeriksaan sebelumnya. Ini pasti kesalahan logika.
Cuplikan no. 29 - Tidak ....: Kondisi selalu benar / salahAda banyak peringatan tentang seluruh kondisi (
V547 ) atau bagian dari suatu kondisi (
V560 ) yang selalu benar atau salah. Alih-alih bug asli, ini sering hanya kode buruk, efek ekspansi makro, dan sebagainya. Yang mengatakan, semua peringatan seperti itu masih harus diperiksa karena beberapa dari mereka mungkin menunjukkan kesalahan logika asli. Misalnya, cuplikan berikut tidak terlihat benar:
static DecodeStatus DecodeGPRPairRegisterClass(MCInst &Inst, unsigned RegNo, uint64_t Address, const void *Decoder) { DecodeStatus S = MCDisassembler::Success; if (RegNo > 13) return MCDisassembler::Fail; if ((RegNo & 1) || RegNo == 0xe) S = MCDisassembler::SoftFail; .... }
Pesan diagnostik PVS-Studio:
V560 [CWE-570] Bagian dari ekspresi kondisional selalu salah: RegNo == 0xe. ARMDisassembler.cpp 939
Konstanta
0xE adalah angka desimal 14. Centang
RegNo == 0xe tidak masuk akal karena jika
RegNo> 13 , fungsi akan kembali.
Saya melihat banyak peringatan V547 dan V560 lainnya, tetapi, seperti halnya dengan
V595 , saya tidak merasa bersemangat untuk memeriksanya karena saya sudah memiliki cukup bahan untuk artikel :). Jadi, tidak ada angka untuk jumlah total bug jenis ini di LLVM.
Berikut adalah contoh untuk mengilustrasikan mengapa memeriksa peringatan itu membosankan. Penganalisa sepenuhnya benar ketika mengeluarkan peringatan pada kode berikut. Tapi itu masih bukan bug.
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons, tok::TokenKind ClosingBraceKind) { bool HasError = false; .... HasError = true; if (!ContinueOnSemicolons) return !HasError; .... }
Pesan diagnostik PVS-Studio: V547 [CWE-570] Ekspresi '! HasError' selalu salah. UnwrappedLineParser.cpp 1635
Cuplikan no. 30: Pengembalian yang mencurigakan static bool isImplicitlyDef(MachineRegisterInfo &MRI, unsigned Reg) { for (MachineRegisterInfo::def_instr_iterator It = MRI.def_instr_begin(Reg), E = MRI.def_instr_end(); It != E; ++It) { return (*It).isImplicitDef(); } .... }
Pesan diagnostik PVS-Studio:
V612 [CWE-670] 'pengembalian' tanpa syarat dalam satu lingkaran. R600OptimizeVectorRegisters.cpp 63
Entah itu bug atau teknik pengkodean khusus yang dimaksudkan untuk mengkomunikasikan beberapa ide kepada sesama programmer. Bagi saya itu tidak mengatakan apa-apa kecuali bahwa itu adalah kode yang sangat mencurigakan. Tolong jangan menulis kode seperti itu :).
Merasa lelah? OK, saatnya membuat teh atau kopi.
Kerusakan ditemukan oleh diagnostik baru
Saya pikir 30 contoh sudah cukup untuk diagnostik yang ada. Sekarang mari kita lihat apakah kita dapat menemukan sesuatu yang menarik dengan diagnostik baru, yang ditambahkan setelah pemeriksaan
sebelumnya . Selama dua tahun terakhir, modul penganalisa C ++ diperpanjang dengan 66 diagnostik baru.
Cuplikan no. 31: Kode tidak dapat dijangkau Error CtorDtorRunner::run() { .... if (auto CtorDtorMap = ES.lookup(JITDylibSearchList({{&JD, true}}), std::move(Names), NoDependenciesToRegister, true)) { .... return Error::success(); } else return CtorDtorMap.takeError(); CtorDtorsByPriority.clear(); return Error::success(); }
Pesan diagnostik PVS-Studio:
V779 [CWE-561] Kode tidak terjangkau terdeteksi. Mungkin saja ada kesalahan. ExecutionUtils.cpp 146
Seperti yang Anda lihat, kedua cabang pernyataan
if diakhiri dengan pernyataan
pengembalian , yang berarti wadah
CtorDtorsByPriority tidak akan pernah dihapus.
Cuplikan no. 32: Kode tidak dapat dijangkau bool LLParser::ParseSummaryEntry() { .... switch (Lex.getKind()) { case lltok::kw_gv: return ParseGVEntry(SummaryID); case lltok::kw_module: return ParseModuleEntry(SummaryID); case lltok::kw_typeid: return ParseTypeIdEntry(SummaryID);
Pesan diagnostik PVS-Studio: V779 [CWE-561] Kode tidak terjangkau terdeteksi. Mungkin saja ada kesalahan. LLParser.cpp 835
Yang ini menarik. Lihatlah bagian ini terlebih dahulu:
return ParseTypeIdEntry(SummaryID); break;
Tampaknya tidak ada yang aneh dengan kode ini; pernyataan
istirahat tidak perlu dan dapat dihapus dengan aman. Tapi itu tidak sesederhana itu.
Peringatan dipicu oleh baris berikut:
Lex.setIgnoreColonInIdentifiers(false); return false;
Memang, kode ini tidak dapat dijangkau. Semua label case dari pernyataan
switch diakhiri dengan
return , dan satu-satunya
break yang tidak berarti tidak terlihat tidak berbahaya lagi! Bagaimana jika salah satu cabang dimaksudkan untuk diakhiri dengan
istirahat daripada
kembali ?
Cuplikan no. 33: Kliring yang tidak disengaja dari bit yang paling signifikan unsigned getStubAlignment() override { if (Arch == Triple::systemz) return 8; else return 1; } Expected<unsigned> RuntimeDyldImpl::emitSection(const ObjectFile &Obj, const SectionRef &Section, bool IsCode) { .... uint64_t DataSize = Section.getSize(); .... if (StubBufSize > 0) DataSize &= ~(getStubAlignment() - 1); .... }
Pesan diagnostik PVS-Studio:
V784 Ukuran bit mask kurang dari ukuran operan pertama. Ini akan menyebabkan hilangnya bit yang lebih tinggi. RuntimeDyld.cpp 815
Perhatikan bahwa fungsi
getStubAlignment mengembalikan nilai yang
tidak ditandatangani . Mari kita lihat bagaimana ekspresi akan mengevaluasi, dengan asumsi bahwa fungsi tersebut akan mengembalikan nilai 8:
~ (getStubAlignment () - 1)
~ (8u-1)
0xFFFFFFF8u
Perhatikan sekarang bahwa
tipe variabel
DataSize adalah 64-bit unsigned. Jadi ternyata menjalankan operasi DataSize & 0xFFFFFFF8 akan menghasilkan membersihkan semua 32 bit paling signifikan dari nilai. Saya tidak berpikir programmer menginginkannya. Mungkin mereka bermaksud untuk menjadi DataSize & 0xFFFFFFFFFFFFFFFFFFF8u.
Untuk memperbaiki kesalahan, kode harus ditulis ulang seperti ini:
DataSize &= ~(static_cast<uint64_t>(getStubAlignment()) - 1);
Atau seperti ini:
DataSize &= ~(getStubAlignment() - 1ULL);
Cuplikan no. 34: Konversi tipe eksplisit yang buruk template <typename T> void scaleShuffleMask(int Scale, ArrayRef<T> Mask, SmallVectorImpl<T> &ScaledMask) { assert(0 < Scale && "Unexpected scaling factor"); int NumElts = Mask.size(); ScaledMask.assign(static_cast<size_t>(NumElts * Scale), -1); .... }
Pesan diagnostik PVS-Studio:
V1028 [CWE-190] Kemungkinan meluap. Pertimbangkan casting operan dari operator 'NumElts * Scale' ke tipe 'size_t', bukan hasilnya. X86ISelLowering.h 1577
Konversi tipe eksplisit digunakan untuk menghindari overflow ketika mengalikan variabel tipe
int . Dalam kasus ini, meskipun, itu tidak berhasil karena perkalian akan terjadi terlebih dahulu dan hanya kemudian hasil 32-bit akan dipromosikan untuk mengetik
size_t .
Cuplikan no. 35: Copy-paste salah Instruction *InstCombiner::visitFCmpInst(FCmpInst &I) { .... if (!match(Op0, m_PosZeroFP()) && isKnownNeverNaN(Op0, &TLI)) { I.setOperand(0, ConstantFP::getNullValue(Op0->getType())); return &I; } if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) { I.setOperand(1, ConstantFP::getNullValue(Op0->getType()));
V778 [CWE-682] Dua fragmen kode serupa ditemukan. Mungkin, ini adalah kesalahan ketik dan variabel 'Op1' harus digunakan alih-alih 'Op0'. InstCombineCompares.cpp 5507
Diagnostik keren baru ini mendeteksi situasi di mana sebuah fragmen kode ditulis menggunakan salin-tempel, dengan semua nama diubah kecuali satu.
Perhatikan bahwa semua
Op0 kecuali satu diubah menjadi
Op1 di blok kedua. Kode mungkin akan terlihat seperti ini:
if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) { I.setOperand(1, ConstantFP::getNullValue(Op1->getType())); return &I; }
Cuplikan no. 36: Variabel tercampur struct Status { unsigned Mask; unsigned Mode; Status() : Mask(0), Mode(0){}; Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) { Mode &= Mask; }; .... };
Pesan diagnostik PVS-Studio:
V1001 [CWE-563] Variabel 'Mode' ditugaskan tetapi tidak digunakan pada akhir fungsi. SIModeRegister.cpp 48
Sangat berbahaya untuk memiliki nama yang sama untuk argumen fungsi seperti untuk anggota kelas karena Anda berisiko mencampurnya. Apa yang Anda lihat di sini adalah contohnya. Ungkapan berikut tidak ada artinya:
Mode &= Mask;
Argumen diubah tetapi tidak pernah digunakan setelah itu. Cuplikan ini mungkin terlihat seperti ini:
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) { this->Mode &= Mask; };
Cuplikan no. 37: Variabel tercampur class SectionBase { .... uint64_t Size = 0; .... }; class SymbolTableSection : public SectionBase { .... }; void SymbolTableSection::addSymbol(Twine Name, uint8_t Bind, uint8_t Type, SectionBase *DefinedIn, uint64_t Value, uint8_t Visibility, uint16_t Shndx, uint64_t Size) { .... Sym.Value = Value; Sym.Visibility = Visibility; Sym.Size = Size; Sym.Index = Symbols.size(); Symbols.emplace_back(llvm::make_unique<Symbol>(Sym)); Size += this->EntrySize; }
Pesan diagnostik PVS-Studio: V1001 [CWE-563] Variabel 'Ukuran' ditetapkan tetapi tidak digunakan pada akhir fungsi. Object.cpp 424
Yang ini mirip dengan contoh sebelumnya. Versi yang benar:
this->Size += this->EntrySize;
Cuplikan no. 38 - Tidak. 47: Cek pointer tidak adaKami melihat beberapa contoh peringatan
V595 sedikit lebih awal. Apa yang dideteksi adalah situasi ketika pointer pertama kali direferensikan dan hanya kemudian diperiksa.
V1004 diagnostik baru adalah kebalikan dari itu, dan mendeteksi banyak kesalahan juga. Tampaknya pointer sudah diuji yang tidak diuji lagi saat diperlukan. Berikut adalah beberapa kesalahan jenis ini yang ditemukan dalam kode LLVM.
int getGEPCost(Type *PointeeType, const Value *Ptr, ArrayRef<const Value *> Operands) { .... if (Ptr != nullptr) {
Pesan diagnostik PVS-Studio: V1004 [CWE-476] Pointer 'Ptr' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 729, 738. TargetTransformInfoImpl.h 738
Ptr dapat berupa
nullptr , yang ditunjukkan dengan tanda centang:
if (Ptr != nullptr)
Namun, penunjuk yang sama didereferensi tanpa pemeriksaan lebih jauh:
auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());
Kasus serupa lainnya.
llvm::DISubprogram *CGDebugInfo::getFunctionFwdDeclOrStub(GlobalDecl GD, bool Stub) { .... auto *FD = dyn_cast<FunctionDecl>(GD.getDecl()); SmallVector<QualType, 16> ArgTypes; if (FD)
Pesan diagnostik PVS-Studio: V1004 [CWE-476] Pointer 'FD' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 3228, 3231. CGDebugInfo.cpp 3231
Perhatikan penunjuk
FD . Kesalahan ini sangat mudah, jadi tidak ada komentar untuk ini.
Satu lagi di sini:
static void computePolynomialFromPointer(Value &Ptr, Polynomial &Result, Value *&BasePtr, const DataLayout &DL) { PointerType *PtrTy = dyn_cast<PointerType>(Ptr.getType()); if (!PtrTy) {
Pesan diagnostik PVS-Studio: V1004 [CWE-476] Pointer 'PtrTy' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 960, 965. InterleavedLoadCombinePass.cpp 965
Bagaimana Anda menghindari kesalahan seperti itu? Berhati-hatilah saat meninjau kode Anda dan periksa secara teratur dengan PVS-Studio.
Saya tidak berpikir kita harus memeriksa contoh-contoh lain dari jenis ini, jadi di sini hanya daftar peringatan:
- V1004 [CWE-476] Pointer 'Expr' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 1049, 1078. DebugInfoMetadata.cpp 1078
- V1004 [CWE-476] Pointer 'PI' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 733, 753. LegacyPassManager.cpp 753
- V1004 [CWE-476] Pointer 'StatepointCall' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 4371, 4379. Verifier.cpp 4379
- V1004 [CWE-476] Pointer 'RV' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 2263, 2268. TGParser.cpp 2268
- V1004 [CWE-476] Pointer 'CalleeFn' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 1081, 1096. SederhanakanLibCalls.cpp 1096
- V1004 [CWE-476] Pointer 'TC' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 1819, 1824. Driver.cpp 1824
Cuplikan no. 48 - Tidak. 60: Tidak kritis tetapi masih cacat (potensi kebocoran memori) std::unique_ptr<IRMutator> createISelMutator() { .... std::vector<std::unique_ptr<IRMutationStrategy>> Strategies; Strategies.emplace_back( new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps())); .... }
Pesan diagnostik PVS-Studio:
V1023 [CWE-460] Pointer tanpa pemilik ditambahkan ke wadah 'Strategi' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. llvm-isel-fuzzer.cpp 58
Anda tidak bisa hanya menulis
xxx.push_back (X baru) untuk menambahkan elemen ke wadah tipe
std :: vector <std :: unique_ptr <X>> karena tidak ada pemeran implisit dari
X * ke
std :: unique_ptr < X> .
Solusi populer adalah menulis
xxx.emplace_back (X baru) karena dapat dikompilasi: metode
emplace_back membangun elemen langsung dari argumen dan, oleh karena itu, dapat menggunakan konstruktor eksplisit.
Tetapi solusi itu tidak aman. Jika vektor penuh, memori akan dialokasikan kembali. Operasi ini mungkin gagal dan akhirnya meningkatkan
std :: bad_alloc exception. Dalam hal ini, pointer akan hilang dan program tidak akan dapat menghapus objek yang dibuat.
Solusi yang lebih aman adalah membuat
unique_ptr , yang akan mempertahankan pointer hingga vektor mencoba mengalokasikan kembali memori:
xxx.push_back(std::unique_ptr<X>(new X))
Standar C ++ 14 memungkinkan Anda untuk menggunakan 'std :: make_unique':
xxx.push_back(std::make_unique<X>())
Jenis cacat ini tidak berpengaruh pada LLVM. Kompilasi hanya akan berakhir jika alokasi memori gagal. Yang mengatakan, itu mungkin sangat penting dalam aplikasi dengan
uptime yang lama, yang tidak bisa berakhir begitu saja ketika terjadi kegagalan alokasi memori.
Jadi, meskipun kode ini tidak berbahaya bagi LLVM, saya pikir saya masih harus memberi tahu Anda tentang pola bug ini dan fakta bahwa PVS-Studio sekarang dapat mendeteksinya.
Kasus serupa lainnya:
- V1023 [CWE-460] Pointer tanpa pemilik ditambahkan ke wadah 'Lulus' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. PassManager.h 546
- V1023 [CWE-460] A pointer without owner is added to the 'AAs' container by the 'emplace_back' method. A memory leak will occur in case of an exception. AliasAnalysis.h 324
- V1023 [CWE-460] A pointer without owner is added to the 'Entries' container by the 'emplace_back' method. A memory leak will occur in case of an exception. DWARFDebugFrame.cpp 519
- V1023 [CWE-460] A pointer without owner is added to the 'AllEdges' container by the 'emplace_back' method. A memory leak will occur in case of an exception. CFGMST.h 268
- V1023 [CWE-460] A pointer without owner is added to the 'VMaps' container by the 'emplace_back' method. A memory leak will occur in case of an exception. SimpleLoopUnswitch.cpp 2012
- V1023 [CWE-460] A pointer without owner is added to the 'Records' container by the 'emplace_back' method. A memory leak will occur in case of an exception. FDRLogBuilder.h 30
- V1023 [CWE-460] A pointer without owner is added to the 'PendingSubmodules' container by the 'emplace_back' method. A memory leak will occur in case of an exception. ModuleMap.cpp 810
- V1023 [CWE-460] A pointer without owner is added to the 'Objects' container by the 'emplace_back' method. A memory leak will occur in case of an exception. DebugMap.cpp 88
- V1023 [CWE-460] A pointer without owner is added to the 'Strategies' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-isel-fuzzer.cpp 60
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 685
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 686
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 688
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 689
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 690
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 691
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 692
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 693
- V1023 [CWE-460] A pointer without owner is added to the 'Modifiers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. llvm-stress.cpp 694
- V1023 [CWE-460] A pointer without owner is added to the 'Operands' container by the 'emplace_back' method. A memory leak will occur in case of an exception. GlobalISelEmitter.cpp 1911
- V1023 [CWE-460] A pointer without owner is added to the 'Stash' container by the 'emplace_back' method. A memory leak will occur in case of an exception. GlobalISelEmitter.cpp 2100
- V1023 [CWE-460] A pointer without owner is added to the 'Matchers' container by the 'emplace_back' method. A memory leak will occur in case of an exception. GlobalISelEmitter.cpp 2702
Kesimpulan
Saya menuliskan 60 peringatan dan berhenti di situ. Apakah PVS-Studio menemukan bug lain di LLVM? Ya, benar. Tetapi ketika saya menuliskan contoh-contohnya, malam tiba, jadi saya memutuskan untuk menyerah.Saya harap Anda menikmati membaca artikel ini dan mendorong Anda untuk mencoba alat analisa PVS-Studio untuk diri Anda sendiri.Kunjungi halaman ini untuk mengunduh penganalisa dan mendapatkan kunci percobaan.Yang paling penting, gunakan analisis statis secara teratur. Pemeriksaan satu kali , seperti yang kami lakukan untuk mempopulerkan analisis statis dan mempromosikan PVS-Studio, bukan skenario normal.Semoga berhasil dengan meningkatkan kualitas dan keandalan kode Anda!