
Mer Àn tvÄ Är har gÄtt sedan den senaste koden för LLVM-projektet granskades med vÄr PVS-Studio-analysator. LÄt oss se till att PVS-Studio-analysatorn fortfarande Àr det ledande verktyget för att upptÀcka fel och potentiella sÄrbarheter. För att göra detta kommer vi att kontrollera och hitta nya fel i releaseversionen LLVM 8.0.0.
En artikel som behöver skrivas
Ărligt talat, jag ville inte skriva den hĂ€r artikeln. Det Ă€r inte intressant att skriva om ett projekt som vi redan har granskat flera gĂ„nger (, , ). Det Ă€r bĂ€ttre att skriva om nĂ„got nytt, men jag har inget val.
Varje gÄng en ny version av LLVM slÀpps eller uppdateras , fÄr vi frÄgor av följande typ i vÄr e-post:
Titta, den nya versionen av Clang Static Analyzer har lÀrt sig att hitta nya fel! Jag tycker att relevansen av att anvÀnda PVS-Studio minskar. Clang hittar fler fel Àn tidigare och nÀrmar sig PVS-Studios kapabiliteter. Vad tycker du om detta?
Till detta vill jag alltid svara nÄgot i stil med:
Vi sitter inte heller stilla! Vi har avsevÀrt förbÀttrat funktionerna i PVS-Studio-analysatorn. SÄ oroa dig inte, vi fortsÀtter att vara ledande som tidigare.
TyvÀrr Àr det ett dÄligt svar. Det finns inga bevis för det. Och just dÀrför skriver jag den hÀr artikeln nu. SÄ, LLVM-projektet har Äterigen granskats och olika fel har hittats. De som jag tyckte var intressanta kommer jag nu att visa. Dessa fel kan Clang Static Analyzer inte identifiera (eller det Àr mycket besvÀrligt att göra det med dess hjÀlp). Men vi kan göra det. Dessutom har jag funnit och noterat alla dessa fel pÄ en kvÀll.
Men skrivandet av artikeln har dragit ut pÄ tiden i flera veckor. Jag kunde verkligen inte tvinga mig att formulera allt i textform :).
Förresten, om du Àr intresserad av vilka teknologier som anvÀnds i PVS-Studio-analysatorn för att identifiera fel och potentiella sÄrbarheter, sÄ föreslÄr jag att du bekantar dig med detta .
Nya och gamla diagnoser
Som redan nÀmnts, för cirka tvÄ Är sedan granskades LLVM-projektet Ànnu en gÄng och de funna felen ÄtgÀrdades. Nu kommer en ny mÀngd fel att presenteras i denna artikel. Varför har nya fel hittats? Det finns tre skÀl till detta:
- LLVM-projektet utvecklas, den gamla koden förÀndras och ny kod tillkommer. Naturligtvis finns det nya fel i den Àndrade och nyss skrivna koden. Detta visar tydligt att statisk analys bör tillÀmpas regelbundet, inte bara sporadiskt. VÄra artiklar visar vÀl möjligheterna med PVS-Studio-analyzern, men det har inget att göra med att förbÀttra kodkvaliteten och minska kostnaderna för att rÀtta till buggar. AnvÀnd statisk kodanalysator regelbundet!
- Vi förbÀttrar och förfinar redan existerande diagnoser. DÀrför kan analysatorn upptÀcka fel som inte observerades vid tidigare kontroller.
- PVS-Studio har fÄtt nya diagnoser som inte fanns för tvÄ Är sedan. Jag har valt att lyfta fram dem i en separat sektion för att tydligt visa utvecklingen av PVS-Studio.
Defekter som upptÀcktes av diagnoser som fanns för tvÄ Är sedan.
Fragment N1: Copy-Paste
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-Studio varning: [CWE-570] Det finns identiska sub-uttryck âName.startswith(«avx512.mask.permvar.»)â till vĂ€nster och höger om â||â operatorn. AutoUpgrade.cpp 73
Det kontrolleras tvÄ gÄnger om namnet börjar med strÀngen «avx512.mask.permvar.». I den andra kontrollen ville man tydligt skriva nÄgot annat, men glömde att rÀtta det kopierade texten.
Fragment N2: Stavfel
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;
....
}PVS-Studio varning: V501 Det finns identiska sub-uttryck âCXNameRange_WantQualifierâ till vĂ€nster och höger om â|â operatorn. CIndex.cpp 7245
PÄ grund av ett stavfel anvÀnds samma namngivna konstant tvÄ gÄnger. CXNameRange_WantQualifier.
Fragment N3: Förvirring med operatorprioriteringar
int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) {
....
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0)
return 0;
....
}PVS-Studio varning: [CWE-783] Kanske fungerar â?:â operatorn pĂ„ ett annat sĂ€tt Ă€n förvĂ€ntat. â?:â operatorn har lĂ€gre prioritet Ă€n â==â operatorn. PPCTargetTransformInfo.cpp 404
Jag tycker att detta Àr ett mycket vackert fel. Ja, jag vet att jag har konstiga uppfattningar om skönhet :).
För nÀrvarande, enligt , berÀknas uttrycket pÄ följande sÀtt:
(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0Ur ett praktiskt perspektiv har detta villkor inget syfte, eftersom det kan förenklas till:
(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())Detta Àr ett uppenbart fel. Troligtvis ville man jÀmföra 0/1 med variabeln Index. För att ÄtgÀrda koden mÄste man lÀgga till parenteser runt den ternÀra operatorn:
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))För övrigt, den ternÀra operatorn Àr mycket farlig och kan orsaka logiska fel. Var mycket försiktig med den och försök att alltid sÀtta parenteser. Jag har diskuterat detta Àmne , i kapitlet "Frukta operatorn ?: och omge den med parenteser."
Fragment N4, N5: Nullpekare
Init *TGParser::ParseValue(Record *CurRec, RecTy *ItemType, IDParseMode Mode) {
....
TypedInit *LHS = dyn_cast(Result);
....
LHS = dyn_cast(
UnOpInit::get(UnOpInit::CAST, LHS, StringRecTy::get())
->Fold(CurRec));
if (!LHS) {
Error(PasteLoc, Twine("kan inte kasta '") + LHS->getAsString() +
"' till strÀng");
return nullptr;
}
....
}PVS-Studio varning: [CWE-476] Derreferensiering av nullpekaren âLHSâ kan Ă€ga rum. TGParser.cpp 2152
Om pekaren LHS visar sig vara null, ska en varning ges. Men istÀllet kommer dereferensieringen av denna nullpekare att intrÀffa: LHS->getAsString().
Detta Àr en typisk situation dÀr felet gömmer sig i felhanteringskoden, eftersom ingen testar dem. Statisk analys kontrollerar hela den Ätkomliga koden, oavsett hur ofta den anvÀnds. Detta Àr ett utmÀrkt exempel pÄ hur statisk analys kompletterar andra testningstekniker och skydd mot fel.
En liknande felhantering av pekaren RHS har begĂ„tts i koden lite lĂ€ngre ner: V522 [CWE-476] Derreferensiering av nullpekaren âRHSâ kan Ă€ga rum. TGParser.cpp 2186
Fragment N6: AnvÀndning av pekare efter flyttning
static Expected
ExtractBlocks(....)
{
....
std::unique_ptr ProgClone = CloneModule(BD.getProgram(), VMap);
....
BD.setNewProgram(std::move(ProgClone)); // getFunction(MisCompFunctions[i].first); // <=
assert(NewF && "Funktionen hittades inte??");
MiscompiledFunctions.push_back(NewF);
}
....
}Varning PVS-Studio: V522 [CWE-476] Derreferensiering av nullpekaren âProgCloneâ kan Ă€ga rum. Miscompilation.cpp 601
I början slutar den smarta pekaren ProgClone att Àga objektet:
BD.setNewProgram(std::move(ProgClone));Faktum Ă€r att den nu ProgClone â Ă€r en nullpekare. DĂ€rför ska det strax nedan ske en dereferens av nullpekaren:
Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);Men i verkligheten kommer detta inte att hÀnda! Observera att loopen egentligen inte körs.
I början av containern MiscompiledFunctions rensar:
MiscompiledFunctions.clear();DÀrefter anvÀnds storleken pÄ denna container i villkoret för loopen:
for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {Det Àr lÀtt att se att loopen inte startar. Jag tror att detta ocksÄ Àr ett fel, och koden borde skrivas pÄ ett annat sÀtt.
Det verkar som om vi har stött pÄ den berömda pariteten av fel! Ett fel döljer ett annat :).
Fragment N7: AnvÀndning av pekare efter flyttning
static Expected TestOptimizer(BugDriver &BD, std::unique_ptr Test,
std::unique_ptr Safe) {
outs() << " Optimizing functions being tested: ";
std::unique_ptr 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;
}
....
}Varning PVS-Studio: V522 [CWE-476] Dereferens av nullpekaren 'Test' kan förekomma. Miscompilation.cpp 709
à terigen samma situation. I början flyttas innehÄllet i objektet, och sedan anvÀnds det som om inget har hÀnt. Jag stöter allt oftare pÄ denna situation i programkod efter att C++ fick flyttsemantic. Det Àr just dÀrför jag Àlskar C++! Det dyker upp allt fler sÀtt att skjuta sig sjÀlv i foten. PVS-Studio-analyzern kommer alltid att ha arbete :).
Fragment N8: Nullpekare
void FunctionDumper::dump(const PDBSymbolTypeFunctionArg &Symbol) {
uint32_t TypeId = Symbol.getTypeId();
auto Type = Symbol.getSession().getSymbolById(TypeId);
if (Type)
Printer << "";
else
Type->dump(*this);
}Varning PVS-Studio: V522 [CWE-476] Dereferens av nullpekaren 'Type' kan förekomma. PrettyFunctionDumper.cpp 233
Utöver felhanterare testas vanligtvis inte funktioner för avlusning av data. Det hÀr Àr just ett sÄdant fall. Funktionen förvÀntar sig en anvÀndare som, istÀllet för att lösa sina problem, kommer att tvingas fixa den.
Korrekt:
if (Type)
Type->dump(*this);
else
Printer << "";Fragment N9: Nullpekare
void SearchableTableEmitter::collectTableEntries(
GenericTable &Table, const std::vector &Items) {
....
RecTy *Ty = resolveTypes(Field.RecType, TI->getType());
if (!Ty) // getAsString() + " vs. " + // getType()->getAsString());
....
}Varning PVS-Studio: V522 [CWE-476] Dereferering av nullpekaren 'Ty' kan intrÀffa. SearchableTableEmitter.cpp 614
Jag tror att allt Àr tydligt och inte behöver förklaring.
Fragment N10: Stavfel
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-Studio varning: Variabeln 'Identifier->Type' tilldelas sig sjÀlv. FormatTokenLexer.cpp 249
Det finns ingen mening med att tilldela en variabel sig sjÀlv. Troligtvis ville man skriva:
Identifier->Type = Question->Type;Fragment N11: MisstÀnkt break
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-Studio varning: [CWE-478] ĂvervĂ€g att inspektera 'switch'-satsen. Det Ă€r möjligt att den första 'case'-operatören saknas. SystemZAsmParser.cpp 652
I början finns det en mycket misstÀnkt operator break. Glömde man kanske att skriva nÄgot mer hÀr?
Fragment N12: Kontroll av pekare efter dereferering
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-Studio varning: [CWE-476] Pekaren 'Callee' anvÀndes innan den verifierades mot nullptr. Kontrollera rader: 172, 174. AMDGPUInline.cpp 172
Pekare Callee deregistreras i början under funktionsanropet getTTI.
Och sedan visar det sig att denna pekare bör kontrolleras för likhet nullptr:
if (!Callee || Callee->isDeclaration())Men det Àr redan för sent...
Fragment N13 â NâŠ: Kontroll av pekare efter dereferering
Situationen som beskrivs i det föregÄende kodfragmentet Àr inte unik. Den förekommer hÀr:
static Value *optimizeDoubleFP(CallInst *CI, IRBuilder &B,
bool isBinary, bool isPrecise = false) {
....
Function *CalleeFn = CI->getCalledFunction();
StringRef CalleeNm = CalleeFn->getName(); // getAttributes();
if (CalleeFn && !CalleeFn->isIntrinsic()) { // <=
....
}Varning PVS-Studio: V595 [CWE-476] Pekaren 'CalleeFn' anvÀndes innan den verifierades mot nullptr. Kontrollera rader: 1079, 1081. SimplifyLibCalls.cpp 1079
Och hÀr:
void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
const Decl *Tmpl, Decl *New,
LateInstantiatedAttrVec *LateAttrs,
LocalInstantiationScope *OuterMostScope) {
....
NamedDecl *ND = dyn_cast(New);
CXXRecordDecl *ThisContext =
dyn_cast_or_null(ND->getDeclContext()); // isCXXInstanceMember()); // <=
....
}Varningsmeddelande frÄn PVS-Studio: V595 [CWE-476] Pekaren 'ND' anvÀndes innan den verifierades mot nullptr. Kontrollera rader: 532, 534. SemaTemplateInstantiateDecl.cpp 532
Och hÀr:
- V595 [CWE-476] Pekaren 'U' anvÀndes innan den verifierades mot nullptr. Kontrollera rader: 404, 407. DWARFFormValue.cpp 404
- V595 [CWE-476] Pekaren 'ND' anvÀndes innan den verifierades mot nullptr. Kontrollera rader: 2149, 2151. SemaTemplateInstantiate.cpp 2149
Jag blev sedan inte intresserad av att studera varningar med nummer V595. SÄ jag vet inte om det finns nÄgra andra liknande fel, förmodligen finns det.
Fragment N17, N18: MisstÀnkt förskjutning
static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize,
uint64_t &Encoding) {
....
unsigned Size = RegSize;
....
uint64_t NImms = ~(Size-1) << 1;
....
}PVS-Studio varning: [CWE-190] ĂvervĂ€g att inspektera uttrycket ' ~(Size â 1) << 1'. Bitförskjutning av det 32-bitars vĂ€rdet med efterföljande expansion till 64-bitars typ. AArch64AddressingModes.h 260
Det kanske inte Àr ett fel, och koden fungerar precis som avsett. Men det Àr uppenbart en mycket misstÀnkt plats och den bör kontrolleras.
Anta att variabeln Size Àr lika med 16, och dÄ planerade kodförfattaren att fÄ i variabeln NImms vÀrdet:
1111111111111111111111111111111111111111111111111111111111100000
Men i verkligheten kommer vÀrdet att bli:
0000000000000000000000000000000011111111111111111111111111100000
Detta beror pÄ att alla berÀkningar görs med hjÀlp av 32-bitars unsigned typ. Och först dÀrefter kommer denna 32-bitars osignerade typ att implicit utvidgas till uint64_t. Under processen kommer de övre bitarna att vara noll.
Situationen kan ÄtgÀrdas sÄ hÀr:
uint64_t NImms = ~static_cast(Size-1) << 1;Liknande situation: V629 [CWE-190] ĂvervĂ€g att inspektera uttrycket 'Immr << 6'. Bitförskjutning av det 32-bitars vĂ€rdet med efterföljande expansion till 64-bitars typ. AArch64AddressingModes.h 269
Fragment N19: Saknat nyckelord else?
void AMDGPUAsmParser::cvtDPP(MCInst &Inst, const OperandVector &Operands) {
....
if (Op.isReg() && Op.Reg.RegNo == AMDGPU::VCC) {
// VOP2b (v_add_u32, v_sub_u32 ...) dpp anvÀnder "vcc" token.
// Hoppa över det.
continue;
} if (isRegOrImmWithInputMods(Desc, Inst.getNumOperands())) { // <=
Op.addRegWithFPInputModsOperands(Inst, 2);
} else if (Op.isDPPCtrl()) {
Op.addImmOperands(Inst, 1);
} else if (Op.isImm()) {
// Hantera valfria argument
OptionalIdx[Op.getImmTy()] = I;
} else {
llvm_unreachable("Ogiltig operandtyp");
}
....
}PVS-Studio varning: [CWE-670] ĂvervĂ€g att inspektera programmets logik. Det Ă€r möjligt att 'else' nyckelordet saknas. AMDGPUAsmParser.cpp 5655
HÀr finns inget fel. Eftersom dÄ-blocket av den första if slutar pÄ continue, sÄ spelar det ingen roll om nyckelordet finns else eller inte. Oavsett kommer koden att fungera pÄ samma sÀtt. Men en bortglömd else gör koden mer otydlig och farlig. Om detta skulle försvinna continue kommer koden att börja fungera helt annorlunda. Jag tycker det Àr bÀttre att lÀgga till else.
Fragment N20: Fyra likartade skrivfel
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;
}Varningar frÄn PVS-Studio:
- V655 [CWE-480] StrĂ€ngarna har sammanfogats men anvĂ€nds inte. ĂvervĂ€g att inspektera uttrycket 'Result + Name.str()'. Symbol.cpp 32
- V655 [CWE-480] StrĂ€ngarna har sammanfogats men anvĂ€nds inte. ĂvervĂ€g att inspektera uttrycket 'Result + "(ObjC Class) " + Name.str()'. Symbol.cpp 35
- V655 [CWE-480] StrĂ€ngarna har sammanfogats men anvĂ€nds inte. ĂvervĂ€g att inspektera uttrycket 'Result + "(ObjC Class EH) " + Name.str()'. Symbol.cpp 38
- V655 [CWE-480] StrĂ€ngarna har sammanfogats men anvĂ€nds inte. ĂvervĂ€g att inspektera uttrycket 'Result + "(ObjC IVar) " + Name.str()'. Symbol.cpp 41
Av misstag anvÀnds operator + istÀllet för operator +=. Detta resulterar i konstruktioner som saknar mening.
Fragment N21: Odefinierat beteende
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 kan inte vara tom");
for (auto &Op : Ops) {
assert(!Op.empty() && "Tom operator");
if (FeaturesMap.find(Op) == FeaturesMap.end())
FeaturesMap[Op] = FeaturesMap.size();
}
}
}Försök sjÀlv att hitta farlig kod. Och hÀr Àr en bild för att avleda uppmÀrksamheten sÄ att du inte ser svaret direkt:

PVS-Studio varning: [CWE-758] Farlig konstruktion anvÀnds: 'FeaturesMap[Op] = FeaturesMap.size()', dÀr 'FeaturesMap' Àr av klass 'map'. Detta kan leda till odefinierat beteende. RISCVCompressInstEmitter.cpp 490
Problematiska raden:
FeaturesMap[Op] = FeaturesMap.size();Om elementet Op inte hittas, skapas ett nytt element i kartan och dÀrtill skrivs antalet element i denna karta. Men det rÄder osÀkerhet om funktionen size kommer att anropas före eller efter att det nya elementet har lagts till.
Fragment N22-N24: Ă terkommande tilldelningar
Fel 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-Studio varning: [CWE-563] Variabeln âNTypeâ tilldelas vĂ€rden tvĂ„ gĂ„nger i rad. Kanske Ă€r detta ett misstag. Kontrollera rader: 1663, 1664. MachOObjectFile.cpp 1664
Jag tror inte att det finns nÄgot verkligt fel hÀr. Bara en onödig upprepning av tilldelningen. Men det Àr ÀndÄ slarv.
PÄ liknande sÀtt:
- V519 [CWE-563] Variabeln âB.NDescâ tilldelas vĂ€rden tvĂ„ gĂ„nger i rad. Kanske Ă€r detta ett misstag. Kontrollera rader: 1488, 1489. llvm-nm.cpp 1489
- V519 [CWE-563] Variabeln tilldelas vÀrden tvÄ gÄnger i rad. Kanske Àr detta ett misstag. Kontrollera rader: 59, 61. coff2yaml.cpp 61
Fragment N25-N27: Ytterligare upprepade tilldelningar
LÄt oss nu övervÀga en nÄgot annan variant av upprepade tilldelningar.
bool Vectorizer::vectorizeLoadChain(
ArrayRef Chain,
SmallPtrSet *InstructionsProcessed) {
....
unsigned Alignment = getAlignment(L0);
....
unsigned NewAlign = getOrEnforceKnownAlignment(L0->getPointerOperand(),
StackAdjustedAlignment,
DL, L0, nullptr, &DT);
if (NewAlign != 0)
Alignment = NewAlign;
Alignment = NewAlign;
....
}Varningsmeddelande frĂ„n PVS-Studio: V519 [CWE-563] Variabeln âAlignmentâ tilldelas vĂ€rden tvĂ„ gĂ„nger i rad. Kanske Ă€r detta ett misstag. Kontrollera rader: 1158, 1160. LoadStoreVectorizer.cpp 1160
Detta Àr mycket konstig kod som uppenbarligen innehÄller ett logiskt fel. I början tilldelas variabeln Alignment ett vÀrde beroende pÄ ett villkor. Sedan sker det en ny tilldelning, men nu utan nÄgon kontroll.
Liknande situationer kan ses hÀr:
- V519 [CWE-563] Variabeln âEffectsâ tilldelas vĂ€rden tvĂ„ gĂ„nger i rad. Kanske Ă€r detta ett misstag. Kontrollera rader: 152, 165. WebAssemblyRegStackify.cpp 165
- V519 [CWE-563] Variabeln âExpectNoDerefChunkâ tilldelas vĂ€rden tvĂ„ gĂ„nger i rad. Kanske Ă€r detta ett misstag. Kontrollera rader: 4970, 4973. SemaType.cpp 4973
Fragment N28: Alltid sann villkor
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-instruktion stöds // <=
break;
}
....
}PVS-Studio varning: [CWE-571] Uttrycket ânextByte != 0x90â Ă€r alltid sant. X86DisassemblerDecoder.cpp 379
Kontrollen Àr meningslös. Variabeln nextByte Àr alltid inte lika med vÀrdet 0x90, vilket följer av den tidigare kontrollen. Det Àr ett logiskt fel.
Fragment N29 â NâŠ: Alltid sanna/falska villkor
Analysatorn ger mĂ„nga varningar om att hela villkoret () eller en del av det () alltid sant eller falskt. Oftast Ă€r detta inte verkliga fel, utan bara slarvig kod, resultatet av makrodeployeringar och liknande. ĂndĂ„ finns det anledning att se över alla dessa varningar, eftersom det ibland dyker upp verkliga logiska fel. Till exempel Ă€r denna kodavsnitt misstĂ€nkt:
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-Studio varning: [CWE-570] En del av det villkorliga uttrycket Àr alltid falskt: RegNo == 0xe. ARMDisassembler.cpp 939
Konstanten 0xE Àr vÀrdet 14 i decimal. Kontrollera RegNo == 0xe Àr meningslöst, eftersom om RegNo > 13, sÄ kommer funktionen att avsluta sin körning.
Det fanns mÄnga andra varningar med identifierarna V547 och V560, men, precis som i fallet med , var det ointressant för mig att studera dessa varningar. Jag förstod redan att det fanns tillrÀckligt med material för att skriva en artikel :). DÀrför Àr det oklart hur mÄnga fel av denna typ som kan upptÀckas i LLVM med hjÀlp av PVS-Studio.
Jag ger ett exempel pÄ varför det Àr trÄkigt att studera dessa upplysningar. Analysatorn har helt rÀtt att utfÀrda en varning för följande kod. Men det Àr ocksÄ ingen fel.
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons,
tok::TokenKind ClosingBraceKind) {
bool HasError = false;
....
HasError = true;
if (!ContinueOnSemicolons)
return !HasError;
....
}PVS-Studio varning: V547 [CWE-570] Uttrycket â!HasErrorâ Ă€r alltid falskt. UnwrappedLineParser.cpp 1635
Fragment N30: MisstÀnkt return
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-Studio varning: [CWE-670] En ovillkorlig âreturnâ inom en loop. R600OptimizeVectorRegisters.cpp 63
Detta Àr antingen ett fel eller en specifik teknik som syftar till att förklara nÄgot för programmerare som lÀser koden. En sÄdan konstruktion förklarar inget för mig och ser mycket misstÀnkt ut. BÀttre att inte skriva sÄ :).
Ăr du trött? DĂ„ Ă€r det dags att brygga te eller kaffe.

Defekter upptÀckta av nya diagnoser
Jag tror att 30 upplysningar frÄn gamla diagnoser Àr tillrÀckligt. LÄt oss nu se vad intressant som kan upptÀckas av de nya diagnoser som har tillkommit i analysatorn efter kontrollen. Totalt har 66 diagnoser för allmÀnt bruk lagts till i C++-analysatorn under denna tid.
Fragment N31: OÄtkomlig kod
Fel 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-Studio varning: [CWE-561] OuppnÄelig kod upptÀcktes. Det kan finnas ett fel.
Som ni kan se, slutar bÄda grenarna av operatorn if med ett anrop till operatorn ÄtervÀnda. DÀrmed kommer behÄllaren CtorDtorsByPriority aldrig att rensas.
Fragment N32: OuppnÄelig kod
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(), "ovÀntad sammanfattning typ");
}
Lex.setIgnoreColonInIdentifiers(false); // <=
return false;
}Varning PVS-Studio: V779 [CWE-561] OuppnÄelig kod upptÀcktes. Det kan finnas ett fel.
En intressant situation. LÄt oss börja med denna plats:
return ParseTypeIdEntry(SummaryID);
break;Vid första anblick verkar det inte finnas nÄgot fel hÀr. Det verkar som om operatorn break Àr överflödig, och den kan tas bort. Men allt Àr inte sÄ enkelt.
Analysatorn ger en varning pÄ raderna:
Lex.setIgnoreColonInIdentifiers(false);
return false;Och i sjÀlva verket Àr denna kod ouppnÄelig. Alla fall i switch slutar med anropet till operatorn ÄtervÀnda. Och nu ser den meningslösa ensamma break inte sÄ harmlös ut! Kanske borde en av grenarna sluta pÄ break, istÀllet för pÄ ÄtervÀnda?
Fragment N33: Slumptal av högre bitar
unsigned getStubAlignment() override {
if (Arch == Triple::systemz)
return 8;
else
return 1;
}
FörvÀntad
RuntimeDyldImpl::emitSection(const ObjectFile &Obj,
const SectionRef &Section,
bool IsCode) {
....
uint64_t DataSize = Section.getSize();
....
if (StubBufSize > 0)
DataSize &= ~(getStubAlignment() - 1);
....
}PVS-Studio varning: Storleken pÄ bitmasken Àr mindre Àn storleken pÄ den första operanden. Detta kommer att orsaka förlust av högre bitar.
Observera att funktionen getStubAlignment returnerar typen unsigned.LÄt oss berÀkna vÀrdet pÄ uttrycket, om vi antar att funktionen returnerar vÀrdet 8:
~(getStubAlignment() - 1)
~(8u-1)
0xFFFFFFF8âŹu
Nu, observera att variabeln DataSize har en 64-bit unsigned typ. SĂ„, vid utförandet av operationen DataSize & 0xFFFFFFF8âŹu kommer alla trettiotvĂ„ högre bitar att nollstĂ€llas. Sannolikt Ă€r detta inte vad programmeraren avsĂ„g. Jag misstĂ€nker att han ville berĂ€kna: DataSize & 0xFFFFFFFFFFFFFFF8âŹu.
För att rÀtta till felet, bör det skrivas sÄ hÀr:
DataSize &= ~(static_cast(getStubAlignment()) - 1);Eller sÄ hÀr:
DataSize &= ~(getStubAlignment() - 1ULL);Fragment N34: Misslyckad typkonvertering
template <typename T>
void scaleShuffleMask(int Scale, ArrayRef<T> Mask,
SmallVectorImpl<T> &ScaledMask) {
assert(0 < Scale && "OvÀntad skalfaktor");
int NumElts = Mask.size();
ScaledMask.assign(static_cast<size_t>(NumElts * Scale), -1);
....
}PVS-Studio varning: [CWE-190] Möjlig överflöde. ĂvervĂ€g att konvertera operanderna av 'NumElts * Scale' operatorn till 'size_t' typ, inte resultatet. X86ISelLowering.h 1577
Typkonvertering anvÀnds för att undvika överflöde vid multiplikation av variabler av typ int. Men hÀr skyddar typkonverteringen inte mot överflöde. I början kommer variablerna att multipliceras, och först dÀrefter kommer den 32-bitars produktresultatet att utvidgas till typen .
Fragment N35: Misslyckad Copy-Paste
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;
}
....
}[CWE-682] TvÄ liknande kodfragment hittades. Kanske Àr detta ett skrivfel och 'Op1' variabeln borde anvÀndas istÀllet för 'Op0'. InstCombineCompares.cpp 5507
Denna nya intressanta diagnos identifierar situationer dÀr en kodsnutt kopierades och vissa namn Àndrades, men pÄ ett stÀlle blev det inte korrigerat.
Observera att i den andra blocket Àndrades Op0 till Op1. Men pÄ ett stÀlle blev det inte korrigerat. Det borde troligtvis ha skrivits sÄ hÀr:
if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) {
I.setOperand(1, ConstantFP::getNullValue(Op1->getType()));
return &I;
}Fragment N36: Förvirring med variabler
struct Status {
unsigned Mask;
unsigned Mode;
Status() : Mask(0), Mode(0){};
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
Mode &= Mask;
};
....
};PVS-Studio varning: [CWE-563] 'Mode' variabeln tilldelas men anvÀnds inte i slutet av funktionen. SIModeRegister.cpp 48
Det Àr mycket farligt att ge funktionsargument samma namn som klassmedlemmar. Det Àr lÀtt att bli förvirrad. Detta Àr just ett sÄdant fall. Detta uttryck ger ingen mening:
Mode &= Mask;Funktionens argument Àndras. Och that's it. Detta argument anvÀnds inte lÀngre. Det borde troligtvis ha skrivits sÄ hÀr:
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
this->Mode &= Mask;
};Fragment N37: Förvirring med variabler
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(Sym));
Size += this->EntrySize;
}PVS-Studio-varning: V1001 [CWE-563] Variabeln 'Size' tilldelas men anvÀnds inte i slutet av funktionen. Object.cpp 424
Situationen Àr liknande den föregÄende. Det borde stÄ:
this->Size += this->EntrySize;Fragment N38-N47: Pekaren glömdes att kontrolleras
Tidigare granskade vi exempel pÄ hur diagnostik utlöses . HuvudinnehÄllet Àr att pekaren först avrefereras och sedan kontrolleras. Den nya diagnosen Àr motsatt i betydelse, men hittar ocksÄ mÄnga fel. Den upptÀcker situationer nÀr pekaren först har kontrollerats, men sedan glömdes det. LÄt oss titta pÄ sÄdana fall som hittades inom LLVM.
int getGEPCost(Type *PointeeType, const Value *Ptr,
ArrayRef Operands) {
....
if (Ptr != nullptr) { // <=
assert(....);
BaseGV = dyn_cast(Ptr->stripPointerCasts());
}
bool HasBaseReg = (BaseGV == nullptr);
auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType()); // <=
....
}PVS-Studio-varning: V1004 [CWE-476] Pekaren 'Ptr' anvÀndes osÀkert efter att den verifierades mot nullptr. Kontrollera rader: 729, 738. TargetTransformInfoImpl.h 738
Variabel Ptr kan vara lika med nullptr, vilket bevisas av kontrollen:
if (Ptr != nullptr)Men nedan avrefereras denna pekare redan utan föregÄende kontroll:
auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());LÄt oss titta pÄ ett annat liknande fall.
llvm::DISubprogram *CGDebugInfo::getFunctionFwdDeclOrStub(GlobalDecl GD,
bool Stub) {
....
auto *FD = dyn_cast(GD.getDecl());
SmallVector ArgTypes;
if (FD) // parameters())
ArgTypes.push_back(Parm->getType());
CallingConv CC = FD->getType()->castAs()->getCallConv(); // <=
....
}PVS-Studio-varning: V1004 [CWE-476] Pekaren 'FD' anvÀndes osÀkert efter att den verifierades mot nullptr. Kontrollera rader: 3228, 3231. CGDebugInfo.cpp 3231
Observera pekaren FD. Jag Àr sÀker pÄ att problemet Àr vÀl synligt, och nÄgra sÀrskilda förklaringar behövs inte.
Och dessutom:
static void computePolynomialFromPointer(Value &Ptr, Polynomial &Result,
Value *&BasePtr,
const DataLayout &DL) {
PointerType *PtrTy = dyn_cast(Ptr.getType());
if (!PtrTy) { // getPointerAddressSpace()); // <=
....
}Varning PVS-Studio: V1004 [CWE-476] Pekaren âPtrTyâ anvĂ€ndes osĂ€kert efter att den bekrĂ€ftades mot nullptr. Kontrollera rader: 960, 965. InterleavedLoadCombinePass.cpp 965
Hur skyddar man sig mot sÄdana fel? Var noggrannare vid kodgranskning och anvÀnd den statiska kodanalysatorn PVS-Studio för regelbundna kontroller.
Det Àr meningslöst att ta med andra kodfragment med denna typ av fel. Jag lÀmnar bara en lista med varningar i artikeln:
- V1004 [CWE-476] Pekaren âExprâ anvĂ€ndes osĂ€kert efter att den bekrĂ€ftades mot nullptr. Kontrollera rader: 1049, 1078. DebugInfoMetadata.cpp 1078
- V1004 [CWE-476] Pekaren âPIâ anvĂ€ndes osĂ€kert efter att den bekrĂ€ftades mot nullptr. Kontrollera rader: 733, 753. LegacyPassManager.cpp 753
- V1004 [CWE-476] Pekaren âStatepointCallâ anvĂ€ndes osĂ€kert efter att den bekrĂ€ftades mot nullptr. Kontrollera rader: 4371, 4379. Verifier.cpp 4379
- V1004 [CWE-476] Pekaren âRVâ anvĂ€ndes osĂ€kert efter att den bekrĂ€ftades mot nullptr. Kontrollera rader: 2263, 2268. TGParser.cpp 2268
- V1004 [CWE-476] Pekaren âCalleeFnâ anvĂ€ndes osĂ€kert efter att den bekrĂ€ftades mot nullptr. Kontrollera rader: 1081, 1096. SimplifyLibCalls.cpp 1096
- V1004 [CWE-476] Pekaren âTCâ anvĂ€ndes osĂ€kert efter att den bekrĂ€ftades mot nullptr. Kontrollera rader: 1819, 1824. Driver.cpp 1824
Fragment N48-N60: Inte kritiskt, men en defekt (möjligt minneslÀckage)
std::unique_ptr createISelMutator() {
....
std::vector<std::unique_ptr> Strategies;
Strategies.emplace_back(
new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps()));
....
}PVS-Studio varning: [CWE-460] En pekare utan Ă€gare lades till i âStrategiesâ-containern via metoden âemplace_backâ. Ett minneslĂ€ckage kommer att intrĂ€ffa vid ett undantag. llvm-isel-fuzzer.cpp 58
För att lÀgga till ett element i slutet av en container av typen std::vector<std::unique_ptr> kan man inte bara skriva xxx.push_back(new X), eftersom det inte finns nÄgon implicit omvandling frÄn X* pÄ std::unique_ptr.
En vanlig lösning Àr att skriva xxx.emplace_back(new X), eftersom det kompilera: metoden emplace_back konstruerar elementet direkt frÄn argumenten och kan dÀrför anvÀnda uttryckliga konstruktörer.
Detta Àr osÀkert. Om vektorn Àr full kan det bli en omallokering av minnet. Omallokeringen kan misslyckas, vilket resulterar i ett undantag std::bad_alloc. I sÄ fall gÄr pekaren förlorad, och det skapade objektet kommer aldrig att tas bort.
En sÀker lösning Àr att skapa en unique_ptr, som kommer att Àga pekaren innan vektorn försöker omallokera minnet:
xxx.push_back(std::unique_ptr(new X))Sedan med C++14 kan man anvÀnda 'std::make_unique':
xxx.push_back(std::make_unique())Den hÀr typen av fel Àr inte kritisk för LLVM. Om minnesallokeringen misslyckas kommer kompilatorn bara att stoppas. Men för applikationer med lÄng , som inte bara kan avslutas om minnesallokeringen misslyckas, kan detta vara ett verkligt obehagligt fel.
SÄ Àven om denna kod inte utgör nÄgon praktisk fara för LLVM, tyckte jag att det var nyttigt att berÀtta om detta felmönster och hur PVS-Studio har lÀrt sig att identifiera det.
Andra varningar av denna typ:
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'Passes'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. PassManager.h 546
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'AAs'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. AliasAnalysis.h 324
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'Entries'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. DWARFDebugFrame.cpp 519
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'AllEdges'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. CFGMST.h 268
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'VMaps'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. SimpleLoopUnswitch.cpp 2012
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'Records'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. FDRLogBuilder.h 30
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'PendingSubmodules'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. ModuleMap.cpp 810
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'Objects'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. DebugMap.cpp 88
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'Strategies'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. llvm-isel-fuzzer.cpp 60
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'Modifiers'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. llvm-stress.cpp 685
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'Modifiers'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. llvm-stress.cpp 686
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'Modifiers'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. llvm-stress.cpp 688
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'Modifiers'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. llvm-stress.cpp 689
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'Modifiers'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. llvm-stress.cpp 690
- V1023 [CWE-460] En pekare utan Àgare lÀggs till 'Modifiers'-behÄllaren med 'emplace_back'-metoden. Ett minneslÀckage kommer att intrÀffa vid ett undantag. llvm-stress.cpp 691
- V1023 [CWE-460] En pekare utan Àgare lÀggs till i 'Modifiers'-containern av metoden 'emplace_back'. En minneslÀcka uppstÄr vid ett undantag. llvm-stress.cpp 692
- V1023 [CWE-460] En pekare utan Àgare lÀggs till i 'Modifiers'-containern av metoden 'emplace_back'. En minneslÀcka uppstÄr vid ett undantag. llvm-stress.cpp 693
- V1023 [CWE-460] En pekare utan Àgare lÀggs till i 'Modifiers'-containern av metoden 'emplace_back'. En minneslÀcka uppstÄr vid ett undantag. llvm-stress.cpp 694
- V1023 [CWE-460] En pekare utan Àgare lÀggs till i 'Operands'-containern av metoden 'emplace_back'. En minneslÀcka uppstÄr vid ett undantag. GlobalISelEmitter.cpp 1911
- V1023 [CWE-460] En pekare utan Àgare lÀggs till i 'Stash'-containern av metoden 'emplace_back'. En minneslÀcka uppstÄr vid ett undantag. GlobalISelEmitter.cpp 2100
- V1023 [CWE-460] En pekare utan Àgare lÀggs till i 'Matchers'-containern av metoden 'emplace_back'. En minneslÀcka uppstÄr vid ett undantag. GlobalISelEmitter.cpp 2702
Slutsats
Totalt har jag skrivit ner 60 varningar, efter det stannade jag. Finns det andra defekter som PVS-Studio-analysatorn upptÀcker i LLVM? Ja, det finns. NÀr jag dokumenterade kodfragment för artikeln blev det sen kvÀll, snarare natt, och jag bestÀmde mig för att det var dags att avsluta.
Jag hoppas att du tyckte att det var intressant och att du vill prova PVS-Studio-analysatorn.
Du kan ladda ner analysatorn och fÄ en provnyckel pÄ .
Det viktigaste Àr att anvÀnda statisk analys regelbundet. EngÄngskontroller, som vi utför för att popularisera metodologin för statisk analys och PVS-Studio, Àr inte ett normalt scenario.
Lycka till med att förbÀttra kvaliteten och pÄlitligheten i koden!
Om du vill dela den hÀr artikeln med en engelsktalande publik, vÀnligen anvÀnd lÀnken till översÀttningen: Andrey Karpov. .
KĂ€lla: habr.com
