Lebih dari dua tahun telah berlalu sejak pemeriksaan kode terakhir proyek LLVM menggunakan penganalisis PVS-Studio kami. Mari pastikan bahwa penganalisis PVS-Studio masih menjadi alat terdepan untuk mengidentifikasi kesalahan dan potensi kerentanan. Untuk melakukan ini, kami akan memeriksa dan menemukan kesalahan baru di rilis LLVM 8.0.0.
Artikel yang akan ditulis
Sejujurnya, saya tidak ingin menulis artikel ini. Tidak menarik untuk menulis tentang proyek yang sudah kita periksa beberapa kali (
Setiap kali versi baru LLVM dirilis atau diperbarui
Lihat, versi baru Clang Static Analyzer telah belajar menemukan kesalahan baru! Menurut saya relevansi penggunaan PVS-Studio semakin menurun. Dentang menemukan lebih banyak kesalahan daripada sebelumnya dan mengejar kemampuan PVS-Studio. Apa pendapat Anda tentang ini?
Untuk ini saya selalu ingin menjawab sesuatu seperti:
Kami juga tidak duduk diam! Kami telah meningkatkan kemampuan penganalisis PVS-Studio secara signifikan. Jadi jangan khawatir, kami terus memimpin seperti sebelumnya.
Sayangnya, ini adalah jawaban yang buruk. Tidak ada bukti di dalamnya. Dan itulah mengapa saya menulis artikel ini sekarang. Jadi, proyek LLVM sekali lagi diperiksa dan ditemukan berbagai kesalahan di dalamnya. Sekarang saya akan mendemonstrasikan hal-hal yang menurut saya menarik. Clang Static Analyzer tidak dapat menemukan kesalahan ini (atau sangat merepotkan untuk melakukannya dengan bantuannya). Tapi kita bisa. Terlebih lagi, saya menemukan dan menuliskan semua kesalahan ini dalam satu malam.
Namun penulisan artikel itu memakan waktu beberapa minggu. Saya tidak sanggup untuk menuliskan semua ini ke dalam teks :).
Omong-omong, jika Anda tertarik dengan teknologi apa yang digunakan dalam penganalisis PVS-Studio untuk mengidentifikasi kesalahan dan potensi kerentanan, saya sarankan untuk membiasakan diri dengan ini
Diagnostik baru dan lama
Seperti telah disebutkan, sekitar dua tahun yang lalu proyek LLVM diperiksa kembali dan kesalahan yang ditemukan telah diperbaiki. Sekarang artikel ini akan menyajikan kumpulan kesalahan baru. Mengapa bug baru ditemukan? Ada 3 alasan untuk ini:
- Proyek LLVM terus berkembang, mengubah kode lama dan menambahkan kode baru. Tentu saja, ada kesalahan baru dalam kode yang dimodifikasi dan ditulis. Hal ini jelas menunjukkan bahwa analisis statis harus digunakan secara teratur, dan tidak sesekali. Artikel kami menunjukkan dengan baik kemampuan penganalisis PVS-Studio, tetapi ini tidak ada hubungannya dengan peningkatan kualitas kode dan mengurangi biaya perbaikan kesalahan. Gunakan penganalisis kode statis secara teratur!
- Kami sedang menyelesaikan dan meningkatkan diagnostik yang ada. Oleh karena itu, penganalisis dapat mengidentifikasi kesalahan yang tidak diketahuinya selama pemindaian sebelumnya.
- Diagnostik baru telah muncul di PVS-Studio yang tidak ada 2 tahun lalu. Saya memutuskan untuk menyorotinya di bagian terpisah untuk menunjukkan dengan jelas perkembangan PVS-Studio.
Cacat diidentifikasi dengan diagnostik yang ada 2 tahun lalu
Fragmen N1: Salin-Tempel
static bool ShouldUpgradeX86Intrinsic(Function *F, StringRef Name) {
if (Name == "addcarryx.u32" || // Added in 8.0
....
Name == "avx512.mask.cvtps2pd.128" || // Added in 7.0
Name == "avx512.mask.cvtps2pd.256" || // Added in 7.0
Name == "avx512.cvtusi2sd" || // Added in 7.0
Name.startswith("avx512.mask.permvar.") || // Added in 7.0 // <=
Name.startswith("avx512.mask.permvar.") || // Added in 7.0 // <=
Name == "sse2.pmulu.dq" || // Added in 7.0
Name == "sse41.pmuldq" || // Added in 7.0
Name == "avx2.pmulu.dq" || // Added in 7.0
....
}
Peringatan PVS-Studio:
Diperiksa ulang apakah nama dimulai dengan substring "avx512.mask.permvar.". Pada pemeriksaan kedua, mereka jelas ingin menulis sesuatu yang lain, tetapi lupa mengoreksi teks yang disalin.
Fragmen N2: Salah ketik
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;
....
}
Peringatan PVS-Studio: V501 Ada sub-ekspresi 'CXNameRange_WantQualifier' yang identik di kiri dan kanan '|' operator. CIndex.cpp 7245
Karena kesalahan ketik, konstanta bernama sama digunakan dua kali CXNameRange_WantQualifier.
Fragmen N3: Kebingungan dengan prioritas operator
int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) {
....
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0)
return 0;
....
}
Peringatan PVS-Studio:
Menurut pendapat saya, ini adalah kesalahan yang sangat indah. Ya, saya tahu saya punya ide aneh tentang kecantikan :).
Sekarang, menurut
(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0
Dari segi praktis, kondisi seperti itu tidak masuk akal, karena dapat direduksi menjadi:
(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())
Ini jelas merupakan kesalahan. Kemungkinan besar, mereka ingin membandingkan 0/1 dengan suatu variabel Indeks. Untuk memperbaiki kode, Anda perlu menambahkan tanda kurung di sekitar operator ternary:
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))
Omong-omong, operator ternary sangat berbahaya dan memicu kesalahan logika. Berhati-hatilah dengan itu dan jangan serakah dengan tanda kurung. Saya melihat topik ini lebih detail
Fragmen N4, N5: Penunjuk nol
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;
}
....
}
Peringatan PVS-Studio:
Jika penunjuk LHS adalah null, peringatan harus dikeluarkan. Namun, sebaliknya, penunjuk nol yang sama ini akan direferensikan: LHS->getAsString().
Ini adalah situasi yang sangat umum ketika kesalahan disembunyikan di penangan kesalahan, karena tidak ada yang mengujinya. Penganalisis statis memeriksa semua kode yang dapat dijangkau, tidak peduli seberapa sering kode tersebut digunakan. Ini adalah contoh yang sangat baik tentang bagaimana analisis statis melengkapi teknik pengujian dan perlindungan kesalahan lainnya.
Kesalahan penanganan penunjuk serupa RHS diperbolehkan dalam kode di bawah ini: V522 [CWE-476] Dereferensi penunjuk nol 'RHS' mungkin terjadi. TGParser.cpp 2186
Fragmen N6: Menggunakan penunjuk setelah berpindah
static Expected<bool>
ExtractBlocks(....)
{
....
std::unique_ptr<Module> ProgClone = CloneModule(BD.getProgram(), VMap);
....
BD.setNewProgram(std::move(ProgClone)); // <=
MiscompiledFunctions.clear();
for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {
Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first); // <=
assert(NewF && "Function not found??");
MiscompiledFunctions.push_back(NewF);
}
....
}
Peringatan PVS-Studio: V522 [CWE-476] Dereferensi penunjuk nol 'ProgClone' mungkin terjadi. Kesalahan kompilasi.cpp 601
Pada awalnya penunjuk cerdas Klon Prog berhenti memiliki objek:
BD.setNewProgram(std::move(ProgClone));
Faktanya, sekarang Klon Prog adalah penunjuk nol. Oleh karena itu, dereferensi penunjuk nol harus terjadi tepat di bawah:
Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);
Namun kenyataannya, hal ini tidak akan terjadi! Perhatikan bahwa perulangan tidak benar-benar dijalankan.
Di awal wadah Fungsi yang Salah Dikompilasi dibersihkan:
MiscompiledFunctions.clear();
Selanjutnya, ukuran container ini digunakan dalam kondisi loop:
for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {
Sangat mudah untuk melihat bahwa perulangan tidak dimulai. Saya pikir ini juga merupakan bug dan kodenya harus ditulis secara berbeda.
Tampaknya kita telah menemukan kesamaan kesalahan yang terkenal itu! Satu kesalahan menutupi kesalahan lainnya :).
Fragmen N7: Menggunakan penunjuk setelah berpindah
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)); // <=
BD.EmitProgressBitcode(*Test, "pass-error", false); // <=
if (Error E = BD.debugOptimizerCrash())
return std::move(E);
return false;
}
....
}
Peringatan PVS-Studio: V522 [CWE-476] Dereferensi penunjuk nol 'Tes' mungkin terjadi. Kesalahan kompilasi.cpp 709
Situasi yang sama lagi. Mula-mula isi benda tersebut dipindahkan, kemudian digunakan seolah-olah tidak terjadi apa-apa. Saya melihat situasi ini semakin sering dalam kode program setelah semantik gerakan muncul di C++. Inilah mengapa saya menyukai bahasa C++! Ada semakin banyak cara baru untuk menembak mati diri Anda sendiri. Penganalisis PVS-Studio akan selalu berfungsi :).
Fragmen N8: Penunjuk nol
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);
}
Peringatan PVS-Studio: V522 [CWE-476] Dereferensi penunjuk nol 'Jenis' mungkin terjadi. PrettyFunctionDumper.cpp 233
Selain penangan kesalahan, fungsi cetakan debug biasanya tidak diuji. Kita punya kasus seperti itu di hadapan kita. Fungsi tersebut menunggu pengguna, yang alih-alih menyelesaikan masalahnya, malah terpaksa memperbaikinya.
Dengan benar:
if (Type)
Type->dump(*this);
else
Printer << "<unknown-type>";
Fragmen N9: Penunjuk nol
void SearchableTableEmitter::collectTableEntries(
GenericTable &Table, const std::vector<Record *> &Items) {
....
RecTy *Ty = resolveTypes(Field.RecType, TI->getType());
if (!Ty) // <=
PrintFatalError(Twine("Field '") + Field.Name + "' of table '" +
Table.Name + "' has incompatible type: " +
Ty->getAsString() + " vs. " + // <=
TI->getType()->getAsString());
....
}
Peringatan PVS-Studio: V522 [CWE-476] Dereferensi penunjuk nol 'Ty' mungkin terjadi. Dapat DicariTableEmitter.cpp 614
Saya pikir semuanya sudah jelas dan tidak memerlukan penjelasan.
Fragmen N10: Salah ketik
bool FormatTokenLexer::tryMergeCSharpNullConditionals() {
....
auto &Identifier = *(Tokens.end() - 2);
auto &Question = *(Tokens.end() - 1);
....
Identifier->ColumnWidth += Question->ColumnWidth;
Identifier->Type = Identifier->Type; // <=
Tokens.erase(Tokens.end() - 1);
return true;
}
Peringatan PVS-Studio:
Tidak ada gunanya menugaskan variabel ke dirinya sendiri. Kemungkinan besar mereka ingin menulis:
Identifier->Type = Question->Type;
Fragmen N11: 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;
....
}
Peringatan PVS-Studio:
Ada operator yang sangat mencurigakan di awal istirahat. Apakah Anda lupa menulis sesuatu yang lain di sini?
Fragmen N12: Memeriksa pointer setelah dereferensi
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");
....
}
Peringatan PVS-Studio:
penunjuk Callee di awal direferensikan pada saat fungsi dipanggil dapatkanTTI.
Dan ternyata penunjuk ini harus diperiksa kesetaraannya nullptr:
if (!Callee || Callee->isDeclaration())
Tapi sudah terlambatβ¦
Fragmen N13 - N...: Memeriksa pointer setelah dereferensi
Situasi yang dibahas dalam potongan kode sebelumnya tidaklah unik. Tampaknya di sini:
static Value *optimizeDoubleFP(CallInst *CI, IRBuilder<> &B,
bool isBinary, bool isPrecise = false) {
....
Function *CalleeFn = CI->getCalledFunction();
StringRef CalleeNm = CalleeFn->getName(); // <=
AttributeList CalleeAt = CalleeFn->getAttributes();
if (CalleeFn && !CalleeFn->isIntrinsic()) { // <=
....
}
Peringatan PVS-Studio: V595 [CWE-476] Penunjuk 'CalleeFn' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 1079, 1081. SimplifyLibCalls.cpp 1079
Dan di sini:
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()); // <=
CXXThisScopeRAII ThisScope(*this, ThisContext, Qualifiers(),
ND && ND->isCXXInstanceMember()); // <=
....
}
Peringatan PVS-Studio: V595 [CWE-476] Penunjuk 'ND' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 532, 534. SemaTemplateInstantiateDecl.cpp 532
Dan di sini:
- V595 [CWE-476] Penunjuk 'U' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 404, 407. DWARFormValue.cpp 404
- V595 [CWE-476] Penunjuk 'ND' digunakan sebelum diverifikasi terhadap nullptr. Periksa baris: 2149, 2151. SemaTemplateInstantiate.cpp 2149
Dan kemudian saya menjadi tidak tertarik mempelajari peringatan dengan nomor V595. Jadi saya tidak tahu apakah ada kesalahan serupa lainnya selain yang tercantum di sini. Kemungkinan besar ada.
Fragmen N17, N18: Pergeseran yang mencurigakan
static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize,
uint64_t &Encoding) {
....
unsigned Size = RegSize;
....
uint64_t NImms = ~(Size-1) << 1;
....
}
Peringatan PVS-Studio:
Ini mungkin bukan bug dan kodenya berfungsi persis seperti yang diharapkan. Tapi ini jelas tempat yang sangat mencurigakan dan perlu diperiksa.
Katakanlah variabelnya Ukuran sama dengan 16, dan kemudian pembuat kode berencana untuk memasukkannya ke dalam variabel NImms nilai:
1111111111111111111111111111111111111111111111111111111111100000
Namun kenyataannya, hasilnya adalah:
0000000000000000000000000000000011111111111111111111111111100000
Faktanya adalah bahwa semua perhitungan dilakukan menggunakan tipe unsigned 32-bit. Dan hanya dengan begitu, tipe 32-bit yang tidak ditandatangani ini akan diperluas secara implisit uint64_t. Dalam hal ini, bit paling signifikan akan menjadi nol.
Anda dapat memperbaiki situasi seperti ini:
uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;
Situasi serupa: V629 [CWE-190] Pertimbangkan untuk memeriksa ekspresi 'Immr << 6'. Pergeseran bit dari nilai 32-bit dengan perluasan selanjutnya ke tipe 64-bit. AArch64AddressingModes.h 269
Fragmen N19: Kata kunci tidak ada lain?
void AMDGPUAsmParser::cvtDPP(MCInst &Inst, const OperandVector &Operands) {
....
if (Op.isReg() && Op.Reg.RegNo == AMDGPU::VCC) {
// VOP2b (v_add_u32, v_sub_u32 ...) dpp use "vcc" token.
// Skip it.
continue;
} if (isRegOrImmWithInputMods(Desc, Inst.getNumOperands())) { // <=
Op.addRegWithFPInputModsOperands(Inst, 2);
} else if (Op.isDPPCtrl()) {
Op.addImmOperands(Inst, 1);
} else if (Op.isImm()) {
// Handle optional arguments
OptionalIdx[Op.getImmTy()] = I;
} else {
llvm_unreachable("Invalid operand type");
}
....
}
Peringatan PVS-Studio:
Tidak ada kesalahan di sini. Sejak itu-blok yang pertama if berakhir dengan terus, maka tidak masalah, ada kata kuncinya lain atau tidak. Apa pun caranya, kodenya akan bekerja sama. Masih terlewatkan lain membuat kode menjadi lebih tidak jelas dan berbahaya. Jika di masa depan terus menghilang, kode akan mulai bekerja dengan cara yang berbeda. Menurut saya lebih baik ditambahkan lain.
Fragmen N20: Empat kesalahan ketik dengan tipe yang sama
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(); // <=
break;
case SymbolKind::ObjectiveCClass:
Result + "(ObjC Class) " + Name.str(); // <=
break;
case SymbolKind::ObjectiveCClassEHType:
Result + "(ObjC Class EH) " + Name.str(); // <=
break;
case SymbolKind::ObjectiveCInstanceVariable:
Result + "(ObjC IVar) " + Name.str(); // <=
break;
}
OS << Result;
}
Peringatan PVS-Studio:
- V655 [CWE-480] Senarnya digabungkan tetapi tidak digunakan. Pertimbangkan untuk memeriksa ekspresi 'Hasil + Nama.str()'. Simbol.cpp 32
- V655 [CWE-480] Senarnya digabungkan tetapi tidak digunakan. Pertimbangkan untuk memeriksa ekspresi 'Hasil + "(Kelas ObjC)" + Nama.str()'. Simbol.cpp 35
- V655 [CWE-480] Senarnya digabungkan tetapi tidak digunakan. Pertimbangkan untuk memeriksa ekspresi 'Hasil + "(ObjC Kelas EH) " + Nama.str()'. Simbol.cpp 38
- V655 [CWE-480] Senarnya digabungkan tetapi tidak digunakan. Pertimbangkan untuk memeriksa ekspresi 'Hasil + "(ObjC IVar)" + Nama.str()'. Simbol.cpp 41
Secara tidak sengaja, operator + digunakan sebagai pengganti operator +=. Hasilnya adalah desain yang tidak bermakna.
Fragmen N21: Perilaku 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();
}
}
}
Coba cari sendiri kode berbahayanya. Dan ini gambar untuk mengalihkan perhatian agar tidak langsung melihat jawabannya:
Peringatan PVS-Studio:
Garis masalah:
FeaturesMap[Op] = FeaturesMap.size();
Jika elemen Op tidak ditemukan, maka elemen baru dibuat di peta dan jumlah elemen di peta ini ditulis di sana. Tidak diketahui apakah fungsi tersebut akan dipanggil ukuran sebelum atau sesudah menambahkan elemen baru.
Fragmen N22-N24: Penugasan berulang
Error MachOObjectFile::checkSymbolTable() const {
....
} else {
MachO::nlist STE = getSymbolTableEntry(SymDRI);
NType = STE.n_type; // <=
NType = STE.n_type; // <=
NSect = STE.n_sect;
NDesc = STE.n_desc;
NStrx = STE.n_strx;
NValue = STE.n_value;
}
....
}
Peringatan PVS-Studio:
Saya rasa tidak ada kesalahan nyata di sini. Hanya tugas berulang yang tidak perlu. Namun masih merupakan kesalahan besar.
Demikian pula:
- 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
Fragmen N25-N27: Lebih banyak penugasan ulang
Sekarang mari kita lihat versi penugasan kembali 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;
....
}
Peringatan 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 kode yang sangat aneh yang tampaknya mengandung kesalahan logika. Pada awalnya, variabel Strategi suatu nilai diberikan tergantung pada kondisinya. Dan kemudian penugasan terjadi lagi, tetapi sekarang tanpa pemeriksaan apa pun.
Situasi serupa dapat dilihat di sini:
- 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
Fragmen N28: 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) // PAUSE instruction support // <=
break;
}
....
}
Peringatan PVS-Studio:
Memeriksa tidak masuk akal. Variabel byte berikutnya selalu tidak sama dengan nilainya 0x90, yang mengikuti dari pemeriksaan sebelumnya. Ini adalah semacam kesalahan logis.
Fragmen N29 - N...: Kondisi selalu benar/salah
Penganalisis mengeluarkan banyak peringatan bahwa seluruh kondisi (
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;
....
}
Peringatan PVS-Studio:
Konstanta 0xE adalah nilai 14 dalam desimal. Penyelidikan Nomor Reg == 0xe tidak masuk akal karena jika Nomor Registrasi > 13, maka fungsi tersebut akan menyelesaikan eksekusinya.
Ada banyak peringatan lain dengan ID V547 dan V560, tetapi juga dengan
Saya akan memberi Anda contoh mengapa mempelajari pemicu ini membosankan. Penganalisis benar sekali dalam mengeluarkan peringatan untuk kode berikut. Tapi ini bukanlah sebuah kesalahan.
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons,
tok::TokenKind ClosingBraceKind) {
bool HasError = false;
....
HasError = true;
if (!ContinueOnSemicolons)
return !HasError;
....
}
Peringatan PVS-Studio: V547 [CWE-570] Ekspresi '!HasError' selalu salah. Membuka bungkusLineParser.cpp 1635
Fragmen N30: ββββ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();
}
....
}
Peringatan PVS-Studio:
Ini bisa berupa kesalahan atau teknik khusus yang dimaksudkan untuk menjelaskan sesuatu kepada pemrogram yang membaca kode. Desain ini tidak menjelaskan apa pun kepada saya dan terlihat sangat mencurigakan. Lebih baik tidak menulis seperti itu :).
Lelah? Maka saatnya membuat teh atau kopi.
Cacat diidentifikasi oleh diagnostik baru
Saya rasa 30 aktivasi diagnostik lama sudah cukup. Sekarang mari kita lihat hal menarik apa yang dapat ditemukan dengan diagnostik baru yang muncul di penganalisis setelahnya
Fragmen N31: 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();
}
Peringatan PVS-Studio:
Seperti yang Anda lihat, kedua cabang operator if diakhiri dengan panggilan ke operator kembali. Oleh karena itu, wadahnya CtorDtorsBerdasarkan Prioritas tidak akan pernah dibersihkan.
Fragmen N32: 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); // <=
break; // <=
default:
return Error(Lex.getLoc(), "unexpected summary kind");
}
Lex.setIgnoreColonInIdentifiers(false); // <=
return false;
}
Peringatan PVS-Studio: V779 [CWE-561] Kode tidak dapat dijangkau terdeteksi. Mungkin saja ada kesalahan. LLParser.cpp 835
Situasi yang menarik. Mari kita lihat tempat ini dulu:
return ParseTypeIdEntry(SummaryID);
break;
Sekilas sepertinya tidak ada kesalahan di sini. Sepertinya operatornya istirahat ada satu tambahan di sini, dan Anda cukup menghapusnya. Namun, tidak semuanya sesederhana itu.
Penganalisis mengeluarkan peringatan pada baris:
Lex.setIgnoreColonInIdentifiers(false);
return false;
Dan memang benar, kode ini tidak dapat dijangkau. Semua kasus di saklar diakhiri dengan panggilan dari operator kembali. Dan sekarang tidak masuk akal sendirian istirahat sepertinya tidak berbahaya! Mungkin salah satu cabangnya harus diakhiri dengan istirahattapi tidak menyala kembali?
Fragmen N33: Reset acak bit tinggi
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);
....
}
Peringatan PVS-Studio:
Harap dicatat bahwa fungsinya dapatkan StubAlignment tipe pengembalian unsigned. Mari kita hitung nilai ekspresi, dengan asumsi fungsi mengembalikan nilai 8:
~(getStubAlignment() - 1)
~(8u-1)
0xFFFFFFFF8u
Sekarang perhatikan bahwa variabelnya Ukuran Data memiliki tipe 64-bit yang tidak ditandatangani. Ternyata saat melakukan operasi DataSize & 0xFFFFFFF8u, ketiga puluh dua bit orde tinggi akan direset ke nol. Kemungkinan besar, ini bukan yang diinginkan programmer. Saya curiga dia ingin menghitung: DataSize & 0xFFFFFFFFFFFFFF8u.
Untuk memperbaiki kesalahan, Anda harus menulis ini:
DataSize &= ~(static_cast<uint64_t>(getStubAlignment()) - 1);
Atau lebih:
DataSize &= ~(getStubAlignment() - 1ULL);
Fragmen N34: Pemeran tipe eksplisit gagal
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);
....
}
Peringatan PVS-Studio:
Pengecoran tipe eksplisit digunakan untuk menghindari overflow saat mengalikan variabel tipe int. Namun, pengecoran tipe eksplisit di sini tidak melindungi terhadap luapan. Pertama, variabel-variabelnya akan dikalikan, dan baru setelah itu hasil perkalian 32-bitnya akan diperluas ke tipe
Fragmen N35: Gagal Salin-Tempel
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())); // <=
return &I;
}
....
}
Diagnostik baru yang menarik ini mengidentifikasi situasi di mana sepotong kode telah disalin dan beberapa nama di dalamnya mulai diubah, tetapi di satu tempat mereka belum memperbaikinya.
Harap dicatat bahwa di blok kedua mereka berubah Op0 pada Op1. Namun di satu tempat mereka tidak memperbaikinya. Kemungkinan besar seharusnya ditulis seperti ini:
if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) {
I.setOperand(1, ConstantFP::getNullValue(Op1->getType()));
return &I;
}
Fragmen N36: Kebingungan Variabel
struct Status {
unsigned Mask;
unsigned Mode;
Status() : Mask(0), Mode(0){};
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
Mode &= Mask;
};
....
};
Peringatan PVS-Studio:
Sangat berbahaya memberikan argumen fungsi dengan nama yang sama dengan anggota kelas. Sangat mudah untuk menjadi bingung. Kita punya kasus seperti itu di hadapan kita. Ungkapan ini tidak masuk akal:
Mode &= Mask;
Argumen fungsi berubah. Itu saja. Argumen ini tidak lagi digunakan. Kemungkinan besar Anda seharusnya menulisnya seperti ini:
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
this->Mode &= Mask;
};
Fragmen N37: Kebingungan Variabel
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;
}
Peringatan PVS-Studio: V1001 [CWE-563] Variabel 'Ukuran' ditetapkan tetapi tidak digunakan pada akhir fungsi. Objek.cpp 424
Situasinya mirip dengan yang sebelumnya. Itu harus ditulis:
this->Size += this->EntrySize;
Fragmen N38-N47: Mereka lupa memeriksa indeks
Sebelumnya, kita melihat contoh pemicu diagnostik
int getGEPCost(Type *PointeeType, const Value *Ptr,
ArrayRef<const Value *> Operands) {
....
if (Ptr != nullptr) { // <=
assert(....);
BaseGV = dyn_cast<GlobalValue>(Ptr->stripPointerCasts());
}
bool HasBaseReg = (BaseGV == nullptr);
auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType()); // <=
....
}
Peringatan PVS-Studio: V1004 [CWE-476] Penunjuk 'Ptr' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 729, 738. TargetTransformInfoImpl.h 738
Variabel Ptr mungkin setara nullptr, dibuktikan dengan cek:
if (Ptr != nullptr)
Namun, di bawah penunjuk ini direferensikan tanpa pemeriksaan awal:
auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());
Mari kita pertimbangkan kasus serupa lainnya.
llvm::DISubprogram *CGDebugInfo::getFunctionFwdDeclOrStub(GlobalDecl GD,
bool Stub) {
....
auto *FD = dyn_cast<FunctionDecl>(GD.getDecl());
SmallVector<QualType, 16> ArgTypes;
if (FD) // <=
for (const ParmVarDecl *Parm : FD->parameters())
ArgTypes.push_back(Parm->getType());
CallingConv CC = FD->getType()->castAs<FunctionType>()->getCallConv(); // <=
....
}
Peringatan PVS-Studio: V1004 [CWE-476] Penunjuk 'FD' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 3228, 3231. CGDebugInfo.cpp 3231
Perhatikan tandanya FD. Saya yakin masalahnya terlihat jelas dan tidak diperlukan penjelasan khusus.
Dan selanjutnya:
static void computePolynomialFromPointer(Value &Ptr, Polynomial &Result,
Value *&BasePtr,
const DataLayout &DL) {
PointerType *PtrTy = dyn_cast<PointerType>(Ptr.getType());
if (!PtrTy) { // <=
Result = Polynomial();
BasePtr = nullptr;
}
unsigned PointerBits =
DL.getIndexSizeInBits(PtrTy->getPointerAddressSpace()); // <=
....
}
Peringatan PVS-Studio: V1004 [CWE-476] Penunjuk 'PtrTy' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 960, 965. InterleavedLoadCombinePass.cpp 965
Bagaimana cara melindungi diri Anda dari kesalahan seperti itu? Lebih memperhatikan Tinjauan Kode dan gunakan penganalisa statis PVS-Studio untuk memeriksa kode Anda secara teratur.
Tidak ada gunanya mengutip potongan kode lain dengan kesalahan jenis ini. Saya hanya akan meninggalkan daftar peringatan di artikel:
- V1004 [CWE-476] Penunjuk 'Expr' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 1049, 1078. DebugInfoMetadata.cpp 1078
- V1004 [CWE-476] Penunjuk 'PI' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 733, 753. LegacyPassManager.cpp 753
- V1004 [CWE-476] Penunjuk 'StatepointCall' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 4371, 4379. Verifier.cpp 4379
- V1004 [CWE-476] Penunjuk 'RV' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 2263, 2268. TGParser.cpp 2268
- V1004 [CWE-476] Penunjuk 'CalleeFn' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 1081, 1096. SimplifyLibCalls.cpp 1096
- V1004 [CWE-476] Penunjuk 'TC' digunakan secara tidak aman setelah diverifikasi terhadap nullptr. Periksa baris: 1819, 1824. Driver.cpp 1824
Fragmen N48-N60: Tidak kritis, tetapi cacat (kemungkinan kebocoran memori)
std::unique_ptr<IRMutator> createISelMutator() {
....
std::vector<std::unique_ptr<IRMutationStrategy>> Strategies;
Strategies.emplace_back(
new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps()));
....
}
Peringatan PVS-Studio:
Untuk menambahkan elemen ke akhir wadah seperti std::vektor > kamu tidak bisa hanya menulis xxx.push_back(X baru), karena tidak ada konversi implisit dari X* Π² std::unique_ptr.
Solusi umum adalah menulis xxx.emplace_back(X baru)sejak dikompilasi: metode emplace_back membangun elemen langsung dari argumennya dan karenanya dapat menggunakan konstruktor eksplisit.
Itu tidak aman. Jika vektornya penuh, maka memori dialokasikan kembali. Operasi realokasi memori mungkin gagal, mengakibatkan pengecualian dilempar std::bad_alloc. Dalam hal ini, penunjuk akan hilang dan objek yang dibuat tidak akan pernah terhapus.
Solusi yang aman adalah dengan berkreasi unik_ptryang akan memiliki penunjuk sebelum vektor mencoba mengalokasikan kembali memori:
xxx.push_back(std::unique_ptr<X>(new X))
Sejak C++14, Anda dapat menggunakan 'std::make_unique':
xxx.push_back(std::make_unique<X>())
Jenis cacat ini tidak penting untuk LLVM. Jika memori tidak dapat dialokasikan, kompiler akan berhenti begitu saja. Namun, untuk aplikasi dengan panjang
Jadi, meskipun kode ini tidak menimbulkan ancaman praktis bagi LLVM, saya merasa berguna untuk membicarakan pola kesalahan ini dan penganalisis PVS-Studio telah belajar mengidentifikasinya.
Peringatan lain dari jenis ini:
- V1023 [CWE-460] Pointer tanpa pemilik ditambahkan ke container 'Passes' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. PassManager.h 546
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'AAs' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. Analisis Alias.h 324
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Entri' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. DWARFDebugFrame.cpp 519
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'AllEdges' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. CFGMST.h 268
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'VMaps' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. SimpleLoopUnswitch.cpp 2012
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Records' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. FDRLogBuilder.h 30
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'PendingSubmodules' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. ModuleMap.cpp 810
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Objek' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. DebugMap.cpp 88
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Strategi' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. llvm-isel-fuzzer.cpp 60
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Pengubah' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. llvm-stress.cpp 685
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Pengubah' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. llvm-stress.cpp 686
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Pengubah' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. llvm-stress.cpp 688
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Pengubah' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. llvm-stress.cpp 689
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Pengubah' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. llvm-stress.cpp 690
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Pengubah' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. llvm-stress.cpp 691
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Pengubah' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. llvm-stress.cpp 692
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Pengubah' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. llvm-stress.cpp 693
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Pengubah' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. llvm-stress.cpp 694
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Operand' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. GlobalISelEmitter.cpp 1911
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Stash' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. GlobalISelEmitter.cpp 2100
- V1023 [CWE-460] Sebuah pointer tanpa pemilik ditambahkan ke wadah 'Matchers' dengan metode 'emplace_back'. Kebocoran memori akan terjadi jika ada pengecualian. GlobalISelEmitter.cpp 2702
Kesimpulan
Saya mengeluarkan total 60 peringatan dan kemudian berhenti. Apakah ada cacat lain yang terdeteksi oleh penganalisis PVS-Studio di LLVM? Ya saya punya. Namun, ketika saya menulis potongan kode untuk artikel tersebut, saat itu sudah larut malam, atau bahkan malam hari, dan saya memutuskan sudah waktunya untuk mengakhirinya.
Saya harap ini menarik bagi Anda dan ingin mencoba penganalisa PVS-Studio.
Anda dapat mengunduh penganalisis dan mendapatkan kunci kapal penyapu ranjau di
Yang terpenting, gunakan analisis statis secara teratur. Pemeriksaan satu kali, yang kami lakukan untuk mempopulerkan metodologi analisis statis dan PVS-Studio bukanlah skenario normal.
Semoga berhasil dalam meningkatkan kualitas dan keandalan kode Anda!
Jika Anda ingin membagikan artikel ini kepada audiens berbahasa Inggris, silakan gunakan tautan terjemahan: Andrey Karpov.
Sumber: www.habr.com