LLVM projekti viimasest koodikontrollist meie PVS-Studio analüsaatori abil on möödas üle kahe aasta. Veenduge, et PVS-Studio analüsaator on endiselt juhtiv tööriist vigade ja potentsiaalsete haavatavuste tuvastamiseks. Selleks kontrollime ja leiame uued vead LLVM 8.0.0 versioonis.
Artikkel tuleb kirjutada
Ausalt öeldes ei tahtnud ma seda artiklit kirjutada. Pole huvitav kirjutada projektist, mida oleme juba mitu korda kontrollinud (
Iga kord, kui LLVM-i uus versioon välja antakse või värskendatakse
Vaata, Clang Static Analyzeri uus versioon on õppinud uusi vigu leidma! Mulle tundub, et PVS-Studio kasutamise asjakohasus väheneb. Clang leiab varasemast rohkem vigu ja jõuab järele PVS-Studio võimalustele. Mida te sellest arvate?
Sellele tahan alati vastata midagi sellist:
Me ei istu ka tegevusetult! Oleme oluliselt täiustanud PVS-Studio analüsaatori võimalusi. Nii et ärge muretsege, me juhime jätkuvalt nagu varem.
Kahjuks on see halb vastus. Selles pole tõendeid. Ja sellepärast ma seda artiklit praegu kirjutan. Niisiis on LLVM-i projekt veel kord kontrollitud ja selles on leitud mitmesuguseid vigu. Nüüd demonstreerin neid, mis tundusid mulle huvitavad. Clang Static Analyzer ei leia neid vigu (või on seda tema abiga äärmiselt ebamugav teha). Aga me saame. Pealegi leidsin ja kirjutasin kõik need vead ühe õhtuga üles.
Kuid artikli kirjutamine võttis mitu nädalat. Ma lihtsalt ei suutnud seda kõike teksti panna :).
Muide, kui olete huvitatud sellest, milliseid tehnoloogiaid PVS-Studio analüsaatoris kasutatakse vigade ja võimalike haavatavuste tuvastamiseks, siis soovitan sellega tutvuda
Uus ja vana diagnostika
Nagu juba märgitud, kontrolliti umbes kaks aastat tagasi LLVM-i projekti uuesti ja leitud vead parandati. Nüüd tutvustatakse selles artiklis uut vigade kogumit. Miks leiti uusi vigu? Sellel on 3 põhjust:
- LLVM projekt areneb, muutes vana koodi ja lisades uut koodi. Loomulikult on muudetud ja kirjutatud koodis uusi vigu. See näitab selgelt, et staatilist analüüsi tuleks kasutada regulaarselt, mitte aeg-ajalt. Meie artiklid näitavad hästi PVS-Studio analüsaatori võimalusi, kuid sellel pole midagi pistmist koodikvaliteedi parandamise ja vigade parandamise kulude vähendamisega. Kasutage regulaarselt staatilise koodianalüsaatorit!
- Viimistleme ja täiustame olemasolevat diagnostikat. Seetõttu suudab analüsaator tuvastada vead, mida ta eelmiste skaneeringute käigus ei märganud.
- PVS-Stuudiosse on ilmunud uus diagnostika, mida 2 aastat tagasi ei eksisteerinud. Otsustasin need eraldi jaotises esile tõsta, et PVS-Stuudio arengut selgelt näidata.
Diagnostikaga tuvastatud defektid, mis olid olemas 2 aastat tagasi
Fragment N1: Kopeeri-kleebi
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
....
}
PVS-Stuudio hoiatus:
Kontrollitakse kaks korda, et nimi algaks alamstringiga "avx512.mask.permvar.". Teises kontrollis tahtsid nad ilmselgelt midagi muud kirjutada, kuid unustasid kopeeritud teksti parandada.
Fragment N2: kirjaviga
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;
....
}
Hoiatus PVS-Studio: V501 '|' vasakul ja paremal on identsed alamväljendid 'CXNameRange_WantQualifier'. operaator. CIndex.cpp 7245
Trükivea tõttu kasutatakse sama nimega konstanti kaks korda CXNameRange_WantQualifier.
Fragment N3: Segadus operaatori eelisseisundiga
int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) {
....
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0)
return 0;
....
}
PVS-Stuudio hoiatus:
Minu meelest on see väga ilus viga. Jah, ma tean, et mul on ilu kohta kummalised ideed :).
Nüüd, vastavalt
(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0
Praktilisest seisukohast ei ole sellisel tingimusel mõtet, kuna seda saab taandada:
(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())
See on selge viga. Tõenäoliselt taheti võrrelda 0/1 muutujaga indeks. Koodi parandamiseks peate kolmeosalise operaatori ümber lisama sulud:
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))
Muide, kolmekomponentne operaator on väga ohtlik ja kutsub esile loogikavigu. Olge sellega väga ettevaatlik ja ärge olge ahne sulgudega. Vaatasin seda teemat lähemalt
Fragment N4, N5: nullkursor
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;
}
....
}
PVS-Stuudio hoiatus:
Kui osuti LHS on tühine, tuleks teha hoiatus. Selle asemel tühistatakse viide sellele samale nullkursorile: LHS->getAsString().
See on väga tüüpiline olukord, kui viga on veakäsitlejas peidetud, kuna keegi ei testi neid. Staatilised analüsaatorid kontrollivad kogu kättesaadavat koodi, olenemata sellest, kui sageli seda kasutatakse. See on väga hea näide sellest, kuidas staatiline analüüs täiendab teisi testimis- ja veakaitsetehnikaid.
Sarnane osuti käsitlemise viga RHS Lubatud just allolevas koodis: V522 [CWE-476] Võib toimuda nullkursori 'RHS' viitamise tühistamine. TGParser.cpp 2186
Fragment N6: kursori kasutamine pärast liigutamist
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);
}
....
}
PVS-Studio hoiatus: V522 [CWE-476] Nullkursori 'ProgClone' viitamise tühistamine võib toimuda. Miscompilation.cpp 601
Alguses nutikas osuti ProgClone lakkab omamast objekti:
BD.setNewProgram(std::move(ProgClone));
Tegelikult nüüd ProgClone on null osuti. Seetõttu peaks nullkursori viide toimuma allpool:
Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);
Kuid tegelikkuses seda ei juhtu! Pange tähele, et silmust tegelikult ei käivitata.
Konteineri alguses Valesti koostatud funktsioonid kustutatud:
MiscompiledFunctions.clear();
Järgmisena kasutatakse selle konteineri suurust silmuse tingimusel:
for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {
On lihtne näha, et silmus ei käivitu. Ma arvan, et see on ka viga ja kood tuleks kirjutada teisiti.
Näib, et oleme kohanud seda kuulsat vigade pariteeti! Üks viga varjab teist :).
Fragment N7: kursori kasutamine pärast liigutamist
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;
}
....
}
PVS-Studio hoiatus: V522 [CWE-476] Võib toimuda nullkursori 'Test' viitamise tühistamine. Miscompilation.cpp 709
Jälle sama olukord. Alguses liigutatakse objekti sisu ja seejärel kasutatakse seda nii, nagu poleks midagi juhtunud. Näen seda olukorda järjest sagedamini programmikoodis pärast seda, kui C++-s ilmus liikumissemantika. See on põhjus, miks ma armastan C++ keelt! Üha rohkem on uusi viise, kuidas endal jalg maha tulistada. PVS-Studio analüsaatoril on alati tööd :).
Fragment N8: nullkursor
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);
}
PVS-Studio hoiatus: V522 [CWE-476] Võib toimuda nullkursori 'Tüüp' viitamise tühistamine. PrettyFunctionDumper.cpp 233
Lisaks veakäsitlejatele ei testita tavaliselt ka silumisprintimise funktsioone. Meil on just selline juhtum ees. Funktsioon ootab kasutajat, kes oma probleemide lahendamise asemel on sunnitud selle parandama.
Õigesti:
if (Type)
Type->dump(*this);
else
Printer << "<unknown-type>";
Fragment N9: nullkursor
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());
....
}
PVS-Studio hoiatus: V522 [CWE-476] Nullkursori 'Ty' viitamise tühistamine võib toimuda. OtsitavTableEmitter.cpp 614
Ma arvan, et kõik on selge ega vaja selgitusi.
Fragment N10: kirjaviga
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;
}
PVS-Stuudio hoiatus:
Muutujat pole mõtet endale omistada. Tõenäoliselt tahtsid nad kirjutada:
Identifier->Type = Question->Type;
Fragment N11: kahtlane katkestus
void SystemZOperand::print(raw_ostream &OS) const {
switch (Kind) {
break;
case KindToken:
OS << "Token:" << getToken();
break;
case KindReg:
OS << "Reg:" << SystemZInstPrinter::getRegisterName(getReg());
break;
....
}
PVS-Stuudio hoiatus:
Alguses on väga kahtlane operaator murdma. Kas unustasite siia veel midagi kirjutada?
Fragment N12: kursori kontrollimine pärast viitamise tühistamist
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");
....
}
PVS-Stuudio hoiatus:
Pointer Callee alguses eemaldatakse funktsiooni kutsumise ajal viide saadaTTI.
Ja siis selgub, et selle osuti võrdsust tuleks kontrollida nullptr:
if (!Callee || Callee->isDeclaration())
Aga on juba hilja…
Fragment N13 - N...: kursori kontrollimine pärast viitamise tühistamist
Eelmises koodifragmendis käsitletud olukord ei ole ainulaadne. See ilmub siin:
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()) { // <=
....
}
PVS-Studio hoiatus: V595 [CWE-476] 'CalleeFn' osutit kasutati enne, kui see kontrolliti nullptr suhtes. Kontrollread: 1079, 1081. SimplifyLibCalls.cpp 1079
Ja siin:
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()); // <=
....
}
PVS-Studio hoiatus: V595 [CWE-476] "ND" osutit kasutati enne, kui see kontrolliti nullptr-i suhtes. Kontrollread: 532, 534. SemaTemplateInstantiateDecl.cpp 532
Ja siin:
- V595 [CWE-476] U-osutit kasutati enne nullptr-i kontrollimist. Kontrollread: 404, 407. DWARFormValue.cpp 404
- V595 [CWE-476] 'ND' osutit kasutati enne nullptr-i kontrollimist. Kontrollread: 2149, 2151. SemaTemplateInstantiate.cpp 2149
Ja siis ei huvitanud mind V595 numbriga hoiatuste uurimine. Nii et ma ei tea, kas peale siinloetletute on veel sarnaseid vigu. Suure tõenäosusega on.
Fragment N17, N18: kahtlane nihe
static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize,
uint64_t &Encoding) {
....
unsigned Size = RegSize;
....
uint64_t NImms = ~(Size-1) << 1;
....
}
PVS-Stuudio hoiatus:
See ei pruugi olla viga ja kood töötab täpselt nii, nagu ette nähtud. Aga see on selgelt väga kahtlane koht ja seda tuleb kontrollida.
Ütleme, et muutuja SUURUS on võrdne 16-ga ja siis plaanis koodi autor saada selle muutujana NImms väärtus:
1111111111111111111111111111111111111111111111111111111111100000
Kuid tegelikkuses on tulemus:
0000000000000000000000000000000011111111111111111111111111100000
Fakt on see, et kõik arvutused tehakse 32-bitise märgita tüübi abil. Ja alles siis laiendatakse seda 32-bitist allkirjastamata tüüpi kaudselt uint64_t. Sel juhul on kõige olulisemad bitid null.
Saate olukorra parandada järgmiselt:
uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;
Sarnane olukord: V629 [CWE-190] Kaaluge avaldise 'Immr << 6' kontrollimist. 32-bitise väärtuse bitivahetus koos järgneva laiendamisega 64-bitisele tüübile. AArch64AddressingModes.h 269
Fragment N19: puudub märksõna teine?
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");
}
....
}
PVS-Stuudio hoiatus:
Siin pole viga. Alates esimese plokist if lõpeb jätkama, siis pole vahet, on märksõna teine või mitte. Mõlemal juhul töötab kood samamoodi. Ikka jäi vahele teine muudab koodi ebaselgemaks ja ohtlikumaks. Kui tulevikus jätkama kaob, hakkab kood hoopis teistmoodi tööle. Minu arvates on parem lisada teine.
Fragment N20: neli sama tüüpi kirjaviga
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;
}
PVS-Stuudio hoiatused:
- V655 [CWE-480] Stringid olid ühendatud, kuid neid ei kasutata. Kaaluge avaldise 'Result + Name.str()' kontrollimist. Sümbol.cpp 32
- V655 [CWE-480] Stringid olid ühendatud, kuid neid ei kasutata. Kaaluge avaldise 'Result + "(ObjC Class)" + Name.str()' kontrollimist. Sümbol.cpp 35
- V655 [CWE-480] Stringid olid ühendatud, kuid neid ei kasutata. Kaaluge avaldise 'Result + "(ObjC Class EH)" + Name.str()' kontrollimist. Sümbol.cpp 38
- V655 [CWE-480] Stringid olid ühendatud, kuid neid ei kasutata. Kaaluge avaldise 'Result + "(ObjC Ivar)" + Name.str()' kontrollimist. Sümbol.cpp 41
Juhuslikult kasutatakse += operaatori asemel operaatorit +. Tulemuseks on kujundused, millel puudub tähendus.
Fragment N21: määratlemata käitumine
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();
}
}
}
Proovige ohtlik kood ise üles leida. Ja see on pilt tähelepanu hajutamiseks, et mitte kohe vastust vaadata:
PVS-Stuudio hoiatus:
Probleemi rida:
FeaturesMap[Op] = FeaturesMap.size();
Kui element Op ei leita, siis luuakse kaardil uus element ja sinna kirjutatakse selle kaardi elementide arv. Pole teada, kas funktsiooni kutsutakse suurus enne või pärast uue elemendi lisamist.
Fragment N22-N24: Korduvad ülesanded
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;
}
....
}
PVS-Stuudio hoiatus:
Ma arvan, et siin pole tõsist viga. Lihtsalt tarbetu korduv ülesanne. Aga eksitus ikkagi.
Samamoodi:
- V519 [CWE-563] Muutujale 'B.NDesc' omistatakse väärtused kaks korda järjest. Võib-olla on see viga. Kontrollread: 1488, 1489. llvm-nm.cpp 1489
- V519 [CWE-563] Muutujale omistatakse väärtused kaks korda järjest. Võib-olla on see viga. Kontrollread: 59, 61. coff2yaml.cpp 61
Fragment N25-N27: rohkem ümbermääramisi
Vaatame nüüd ümbermääramise veidi teistsugust versiooni.
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;
....
}
PVS-Studio hoiatus: V519 [CWE-563] Muutujale 'Joondamine' määratakse väärtused kaks korda järjest. Võib-olla on see viga. Kontrollread: 1158, 1160. LoadStoreVectorizer.cpp 1160
See on väga kummaline kood, mis ilmselt sisaldab loogikaviga. Alguses muutuv Joondumine olenevalt olukorrast määratakse väärtus. Ja siis toimub määramine uuesti, kuid nüüd ilma igasuguse kontrollita.
Sarnaseid olukordi saab näha siit:
- V519 [CWE-563] Muutujale „Efektid” omistatakse väärtused kaks korda järjest. Võib-olla on see viga. Kontrollread: 152, 165. WebAssemblyRegStackify.cpp 165
- V519 [CWE-563] Muutujale 'ExpectNoDerefChunk' määratakse väärtused kaks korda järjest. Võib-olla on see viga. Kontrollread: 4970, 4973. SemaType.cpp 4973
Fragment N28: alati korralik
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;
}
....
}
PVS-Stuudio hoiatus:
Kontrollimisel pole mõtet. Muutuv nextByte ei ole alati võrdne väärtusega 0x90, mis tuleneb eelmisest kontrollist. See on mingi loogikaviga.
Fragment N29 - N...: alati tõesed/valed tingimused
Analüsaator annab palju hoiatusi, et kogu seisund (
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;
....
}
PVS-Stuudio hoiatus:
Konstant 0xE on kümnendkoha väärtus 14. Läbivaatus RegNo == 0xe pole mõtet, sest kui RegNo > 13, siis funktsioon lõpetab täitmise.
ID-dega V547 ja V560 oli palju muid hoiatusi, kuid nagu ka
Toon teile näite, miks nende päästikute uurimine on igav. Analüsaatoril on järgmise koodi eest hoiatuse andmisel täiesti õigus. Kuid see pole viga.
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons,
tok::TokenKind ClosingBraceKind) {
bool HasError = false;
....
HasError = true;
if (!ContinueOnSemicolons)
return !HasError;
....
}
PVS-Studio hoiatus: V547 [CWE-570] Avaldis '!HasError' on alati vale. UnwrappedLineParser.cpp 1635
Fragment N30: kahtlane tagasitulek
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();
}
....
}
PVS-Stuudio hoiatus:
See on kas viga või spetsiifiline tehnika, mis on mõeldud koodi lugevatele programmeerijatele midagi selgitama. See disain ei selgita mulle midagi ja tundub väga kahtlane. Parem mitte nii kirjutada :).
Väsinud? Siis on aeg teed või kohvi keeta.
Uue diagnostikaga tuvastatud defektid
Arvan, et 30 vana diagnostika aktiveerimist on piisav. Vaatame nüüd, mida huvitavat pärast analüsaatorisse ilmunud uue diagnostikaga leida võib
Fragment N31: kättesaamatu kood
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();
}
PVS-Stuudio hoiatus:
Nagu näete, operaatori mõlemad harud if lõpeb kõnega operaatorile tagasipöördumine. Vastavalt sellele konteiner CtorDtorsByPriority ei puhastata kunagi.
Fragment N32: kättesaamatu kood
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;
}
PVS-Studio hoiatus: V779 [CWE-561] Tuvastati kättesaamatu kood. Võimalik, et tegemist on veaga. LLParser.cpp 835
Huvitav olukord. Vaatame kõigepealt seda kohta:
return ParseTypeIdEntry(SummaryID);
break;
Esmapilgul tundub, et viga siin pole. See näeb välja nagu operaator murdma siin on veel üks ja saate selle lihtsalt kustutada. Siiski pole kõik nii lihtne.
Analüsaator annab joontele hoiatuse:
Lex.setIgnoreColonInIdentifiers(false);
return false;
Ja tõepoolest, see kood on kättesaamatu. Kõik juhtumid sisse lüliti lõpeb operaatori kõnega tagasipöördumine. Ja nüüd üksi mõttetu murdma ei tundu nii kahjutu! Võib-olla peaks üks harudest lõppema murdma, mitte edasi tagasipöördumine?
Fragment N33: kõrgete bittide juhuslik lähtestamine
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);
....
}
PVS-Stuudio hoiatus:
Pange tähele, et funktsioon getStubAlignment tagastab tüübi allakirjutamata. Arvutame avaldise väärtuse, eeldades, et funktsioon tagastab väärtuse 8:
~(getStubAlignment() - 1)
~ (8u-1)
0xFFFFFFFF8u
Nüüd pange tähele, et muutuja DataSize on 64-bitine allkirjastamata tüüp. Selgub, et toimingu DataSize & 0xFFFFFFF8u sooritamisel nullitakse kõik kolmkümmend kaks kõrget järgu bitti. Tõenäoliselt ei tahtnud programmeerija seda. Kahtlustan, et ta tahtis arvutada: DataSize & 0xFFFFFFFFFFFFFFFF8u.
Vea parandamiseks peaksite kirjutama järgmise:
DataSize &= ~(static_cast<uint64_t>(getStubAlignment()) - 1);
Või nii:
DataSize &= ~(getStubAlignment() - 1ULL);
Fragment N34: selgesõnaline ülekandmine ebaõnnestus
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);
....
}
PVS-Stuudio hoiatus:
Selgesõnalist tüüpi valamist kasutatakse tüübimuutujate korrutamisel ülevoolu vältimiseks int. Selgesõnaline tüübivalamine ei kaitse aga ülevoolu eest. Esiteks korrutatakse muutujad ja alles seejärel laiendatakse korrutamise 32-bitine tulemus tüübiks
Fragment N35: Kopeerimine ja kleepimine ebaõnnestus
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;
}
....
}
See uus huvitav diagnostika tuvastab olukorrad, kus koodijupp on kopeeritud ja mõnda nime selles on hakatud muutma, kuid ühes kohas pole seda parandatud.
Pange tähele, et teises plokis need muutusid Op0 edasi Op1. Kuid ühes kohas nad seda ei parandanud. Tõenäoliselt oleks see pidanud olema kirjutatud nii:
if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) {
I.setOperand(1, ConstantFP::getNullValue(Op1->getType()));
return &I;
}
Fragment N36: muutuv segadus
struct Status {
unsigned Mask;
unsigned Mode;
Status() : Mask(0), Mode(0){};
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
Mode &= Mask;
};
....
};
PVS-Stuudio hoiatus:
Väga ohtlik on anda funktsiooni argumentidele klassiliikmetega samad nimed. Väga lihtne on segadusse sattuda. Meil on just selline juhtum ees. Sellel väljendil pole mõtet:
Mode &= Mask;
Funktsiooni argument muutub. See on kõik. Seda argumenti enam ei kasutata. Tõenäoliselt oleksite pidanud selle kirjutama nii:
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
this->Mode &= Mask;
};
Fragment N37: muutuv segadus
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;
}
Hoiatus PVS-Studio: V1001 [CWE-563] Muutuja Size on määratud, kuid seda funktsiooni lõpuks ei kasutata. Object.cpp 424
Olukord on sarnane eelmisega. See peaks olema kirjutatud:
this->Size += this->EntrySize;
Fragment N38-N47: nad unustasid indeksit kontrollida
Varem vaatlesime diagnostilise käivitamise näiteid
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()); // <=
....
}
PVS-Studio hoiatus: V1004 [CWE-476] Ptr osutit kasutati ebaturvaliselt pärast seda, kui see kontrolliti nullptri suhtes. Kontrollread: 729, 738. TargetTransformInfoImpl.h 738
Muutuv Ptr võib olla võrdne nullptr, mida tõendab tšekk:
if (Ptr != nullptr)
Siiski on alloleval kursoril viide ilma eelneva kontrollita:
auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());
Vaatleme teist sarnast juhtumit.
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(); // <=
....
}
PVS-Studio hoiatus: V1004 [CWE-476] 'FD' osutit kasutati ebaturvaliselt pärast seda, kui see kontrolliti nullptr-i suhtes. Kontrollread: 3228, 3231. CGDebugInfo.cpp 3231
Pöörake tähelepanu märgile FD. Olen kindel, et probleem on selgelt nähtav ja erilist selgitust pole vaja.
Ja edasi:
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()); // <=
....
}
PVS-Studio hoiatus: V1004 [CWE-476] 'PtrTy' osutit kasutati pärast nullptr-i suhtes kontrollimist ebaturvaliselt. Kontrollread: 960, 965. InterleavedLoadCombinePass.cpp 965
Kuidas end selliste vigade eest kaitsta? Olge Code-Review'is tähelepanelikum ja kasutage oma koodi korrapäraseks kontrollimiseks PVS-Studio staatilist analüsaatorit.
Teistele seda tüüpi vigadega koodifragmentidele pole mõtet viidata. Jätan artiklisse ainult hoiatuste loendi:
- V1004 [CWE-476] „Expr” osutit kasutati ebaturvaliselt pärast seda, kui see kontrolliti nullptr suhtes. Kontrollread: 1049, 1078. DebugInfoMetadata.cpp 1078
- V1004 [CWE-476] PI-osutit kasutati pärast nullptr-i suhtes kontrollimist ebaturvaliselt. Kontrollread: 733, 753. LegacyPassManager.cpp 753
- V1004 [CWE-476] Osutit „StatepointCall” kasutati pärast nullptr-i suhtes kontrollimist ebaturvaliselt. Kontrollread: 4371, 4379. Verifier.cpp 4379
- V1004 [CWE-476] RV osutit kasutati ebaturvaliselt pärast seda, kui see kontrolliti nullptr-i suhtes. Kontrollread: 2263, 2268. TGParser.cpp 2268
- V1004 [CWE-476] 'CalleeFn' osutit kasutati pärast nullptr-i suhtes kontrollimist ebaturvaliselt. Kontrollread: 1081, 1096. SimplifyLibCalls.cpp 1096
- V1004 [CWE-476] TC osutit kasutati ebaturvaliselt pärast seda, kui see kontrolliti nullptr-i suhtes. Kontrollread: 1819, 1824. Driver.cpp 1824
Fragment N48-N60: pole kriitiline, kuid defekt (võimalik mäluleke)
std::unique_ptr<IRMutator> createISelMutator() {
....
std::vector<std::unique_ptr<IRMutationStrategy>> Strategies;
Strategies.emplace_back(
new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps()));
....
}
PVS-Stuudio hoiatus:
Elemendi lisamiseks konteineri lõppu nagu std::vektor > sa ei saa lihtsalt kirjutada xxx.push_back(uus X), kuna puudub kaudne teisendus X* в std::unikaalne_ptr.
Levinud lahendus on kirjutada xxx.emplace_back(uus X)kuna see koostab: meetod emplace_back konstrueerib elemendi otse oma argumentidest ja saab seetõttu kasutada eksplitsiitseid konstruktoreid.
See ei ole ohutu. Kui vektor on täis, eraldatakse mälu uuesti. Mälu ümberjaotamise toiming võib ebaõnnestuda, mille tulemuseks on erand std::bad_alloc. Sel juhul kursor kaob ja loodud objekti ei kustutata kunagi.
Ohutu lahendus on luua unikaalne_ptrmis omab kursorit enne, kui vektor proovib mälu ümber jaotada:
xxx.push_back(std::unique_ptr<X>(new X))
Alates C++14-st saate kasutada 'std::make_unique':
xxx.push_back(std::make_unique<X>())
Seda tüüpi defekt ei ole LLVM-i jaoks kriitiline. Kui mälu ei saa eraldada, siis kompilaator lihtsalt peatub. Kuid rakenduste jaoks, millel on pikk
Ehkki see kood LLVM-ile praktilist ohtu ei kujuta, leidsin, et kasulik on rääkida sellest veamustrist ja sellest, et PVS-Studio analüsaator on õppinud seda tuvastama.
Muud seda tüüpi hoiatused:
- V1023 [CWE-460] Ilma omanikuta kursor lisatakse konteinerisse „Läbib” meetodiga „emplace_back”. Erandi korral tekib mäluleke. PassManager.h 546
- V1023 [CWE-460] Ilma omanikuta kursor lisatakse AA-de konteinerisse meetodiga 'emplace_back'. Erandi korral tekib mäluleke. AliasAnalysis.h 324
- V1023 [CWE-460] Ilma omanikuta kursor lisatakse konteinerisse 'Entries' meetodiga 'emplace_back'. Erandi korral tekib mäluleke. DWARFDebugFrame.cpp 519
- V1023 [CWE-460] Ilma omanikuta kursor lisatakse konteinerisse 'AllEdges' meetodiga 'emplace_back'. Erandi korral tekib mäluleke. CFGMST.h 268
- V1023 [CWE-460] Ilma omanikuta kursor lisatakse VMapsi konteinerisse meetodiga 'emplace_back'. Erandi korral tekib mäluleke. SimpleLoopUnswitch.cpp 2012
- V1023 [CWE-460] Ilma omanikuta kursor lisatakse 'emplace_back' meetodil kirjed konteinerisse. Erandi korral tekib mäluleke. FDRLogBuilder.h 30
- V1023 [CWE-460] Ilma omanikuta osuti lisatakse konteinerisse „PendingSubmodules” meetodiga „emplace_back”. Erandi korral tekib mäluleke. ModuleMap.cpp 810
- V1023 [CWE-460] Ilma omanikuta kursor lisatakse 'Objects' konteinerisse meetodiga 'emplace_back'. Erandi korral tekib mäluleke. DebugMap.cpp 88
- V1023 [CWE-460] Ilma omanikuta kursor lisatakse strateegia 'emplace_back' meetodil konteinerisse "Strategies". Erandi korral tekib mäluleke. llvm-isel-fuzzer.cpp 60
- V1023 [CWE-460] Ilma omanikuta osuti lisatakse konteinerisse 'Muutajad' meetodiga 'emplace_back'. Erandi korral tekib mäluleke. llvm-stress.cpp 685
- V1023 [CWE-460] Ilma omanikuta osuti lisatakse konteinerisse 'Muutajad' meetodiga 'emplace_back'. Erandi korral tekib mäluleke. llvm-stress.cpp 686
- V1023 [CWE-460] Ilma omanikuta osuti lisatakse konteinerisse 'Muutajad' meetodiga 'emplace_back'. Erandi korral tekib mäluleke. llvm-stress.cpp 688
- V1023 [CWE-460] Ilma omanikuta osuti lisatakse konteinerisse 'Muutajad' meetodiga 'emplace_back'. Erandi korral tekib mäluleke. llvm-stress.cpp 689
- V1023 [CWE-460] Ilma omanikuta osuti lisatakse konteinerisse 'Muutajad' meetodiga 'emplace_back'. Erandi korral tekib mäluleke. llvm-stress.cpp 690
- V1023 [CWE-460] Ilma omanikuta osuti lisatakse konteinerisse 'Muutajad' meetodiga 'emplace_back'. Erandi korral tekib mäluleke. llvm-stress.cpp 691
- V1023 [CWE-460] Ilma omanikuta osuti lisatakse konteinerisse 'Muutajad' meetodiga 'emplace_back'. Erandi korral tekib mäluleke. llvm-stress.cpp 692
- V1023 [CWE-460] Ilma omanikuta osuti lisatakse konteinerisse 'Muutajad' meetodiga 'emplace_back'. Erandi korral tekib mäluleke. llvm-stress.cpp 693
- V1023 [CWE-460] Ilma omanikuta osuti lisatakse konteinerisse 'Muutajad' meetodiga 'emplace_back'. Erandi korral tekib mäluleke. llvm-stress.cpp 694
- V1023 [CWE-460] Ilma omanikuta kursor lisatakse konteinerisse 'Operandid' meetodiga 'emplace_back'. Erandi korral tekib mäluleke. GlobalISelEmitter.cpp 1911
- V1023 [CWE-460] Ilma omanikuta kursor lisatakse konteinerisse "Stash" meetodiga "emplace_back". Erandi korral tekib mäluleke. GlobalISelEmitter.cpp 2100
- V1023 [CWE-460] Ilma omanikuta osuti lisatakse konteinerisse „Sobitajad” meetodiga „emplace_back”. Erandi korral tekib mäluleke. GlobalISelEmitter.cpp 2702
Järeldus
Andsin kokku 60 hoiatust ja siis lõpetasin. Kas on muid defekte, mida PVS-Studio analüsaator LLVM-is tuvastab? Jah mul on. Kui aga artikli jaoks koodijuppe välja kirjutasin, oli käes hilisõhtu või õigemini isegi öö ja otsustasin, et on aeg seda päevaks nimetada.
Loodan, et see oli teile huvitav ja soovite PVS-Studio analüsaatorit proovida.
Analüsaatori saate alla laadida ja miinijahtija võtme hankida aadressil
Kõige tähtsam on kasutada regulaarselt staatilist analüüsi. Ühekordsed kontrollid, mille oleme läbi viinud staatilise analüüsi metoodika populariseerimiseks ja PVS-Stuudio ei ole tavaline stsenaarium.
Edu koodi kvaliteedi ja usaldusväärsuse parandamisel!
Kui soovite seda artiklit inglise keelt kõneleva publikuga jagada, kasutage tõlkelinki: Andrey Karpov.
Allikas: www.habr.com