
Het is meer dan twee jaar geleden sinds de laatste codecontrole van het LLVM-project met onze PVS-Studio analyzer. Laten we ervoor zorgen dat PVS-Studio nog steeds het toonaangevende hulpmiddel is voor het opsporen van fouten en potentiële kwetsbaarheden. Daarom zullen we nieuwe fouten in de release van LLVM 8.0.0 controleren en vinden.
Een artikel dat geschreven moet worden
Eerlijk gezegd had ik geen zin om dit artikel te schrijven. Het is niet interessant om te schrijven over een project dat we al meerdere keren hebben gecontroleerd (, , ). Het zou beter zijn om over iets nieuws te schrijven, maar ik heb geen keuze.
Telkens wanneer er een nieuwe versie van LLVM uitkomt of wordt bijgewerkt , krijgen we vragen van dit soort in onze inbox:
Kijk, de nieuwe versie van Clang Static Analyzer kan nieuwe fouten vinden! Ik heb het gevoel dat de relevantie van het gebruik van PVS-Studio afneemt. Clang vindt meer fouten dan voorheen en haalt PVS-Studio in op mogelijkheden. Wat denken jullie hiervan?
Daarop wil ik altijd iets antwoorden in de trant van:
Wij zitten ook niet stil! We hebben de mogelijkheden van de PVS-Studio analyzer aanzienlijk verbeterd. Maak je geen zorgen, we blijven, zoals altijd, de leiders.
Helaas is dit een slecht antwoord. Het bevat geen bewijs. Daarom schrijf ik nu dit artikel. Dus, het LLVM-project is opnieuw gecontroleerd en er zijn verschillende fouten gevonden. De fouten die ik interessant vond, zal ik nu demonstreren. Deze fouten kunnen niet worden gevonden door de Clang Static Analyzer (of het is uiterst onhandig om dit te doen). Maar wij kunnen dat. En bovendien heb ik al deze fouten in één avond gevonden en genoteerd.
Het schrijven van het artikel heeft daarentegen enkele weken in beslag genomen. Ik kon mezelf er maar niet toe zetten om dit alles in tekstvorm te gieten :).
Trouwens, als je geïnteresseerd bent in welke technologieën worden gebruikt in de PVS-Studio analyzer om fouten en potentiële kwetsbaarheden op te sporen, dan stel ik voor dat je deze .
Nieuwe en oude diagnoses
Zoals eerder opgemerkt, is het LLVM-project ongeveer twee jaar geleden opnieuw gecontroleerd, en de gevonden fouten zijn gecorrigeerd. Nu zal dit artikel een nieuwe serie fouten presenteren. Waarom zijn er nieuwe fouten gevonden? Dit heeft drie redenen:
- Het LLVM-project ontwikkelt zich, oude code wordt gewijzigd en er verschijnt nieuwe code. Uiteraard zijn er in de gewijzigde en nieuwe code nieuwe fouten. Dit illustreert goed dat statische analyse regelmatig toegepast moet worden, en niet af en toe. Onze artikelen tonen goed de mogelijkheden van de PVS-Studio-analyzer, maar dat staat los van het verbeteren van codekwaliteit en het verlagen van de kosten voor het oplossen van fouten. Gebruik de code-analysetool regelmatig!
- We verbeteren en verfijnen al bestaande diagnoses. Daarom kan de analyzer fouten vinden die tijdens eerdere controles niet opgemerkt werden.
- In PVS-Studio zijn nieuwe diagnoses toegevoegd die er 2 jaar geleden niet waren. Ik besloot ze in een aparte sectie te plaatsen om de ontwikkeling van PVS-Studio duidelijk te tonen.
Defecten, ontdekt door diagnoses die 2 jaar geleden bestonden.
Fragment N1: Copy-Paste
static bool ShouldUpgradeX86Intrinsic(Function *F, StringRef Name) {
if (Name == "addcarryx.u32" || // Toegevoegd in 8.0
....
Name == "avx512.mask.cvtps2pd.128" || // Toegevoegd in 7.0
Name == "avx512.mask.cvtps2pd.256" || // Toegevoegd in 7.0
Name == "avx512.cvtusi2sd" || // Toegevoegd in 7.0
Name.startswith("avx512.mask.permvar.") || // Toegevoegd in 7.0 // <=
Name.startswith("avx512.mask.permvar.") || // Toegevoegd in 7.0 // <=
Name == "sse2.pmulu.dq" || // Toegevoegd in 7.0
Name == "sse41.pmuldq" || // Toegevoegd in 7.0
Name == "avx2.pmulu.dq" || // Toegevoegd in 7.0
....
}PVS-Studio waarschuwing: [CWE-570] Er zijn identieke sub-expressies âName.startswith(«avx512.mask.permvar.»)â aan de linker- en rechterzijde van de â||â operator. AutoUpgrade.cpp 73
Er wordt twee keer gecontroleerd of de naam begint met de substring âavx512.mask.permvar.â. Bij de tweede controle wilde men duidelijk iets anders schrijven, maar vergat men de gekopieerde tekst aan te passen.
Fragment N2: Typefout
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 waarschuwing: V501 Er zijn identieke sub-expressies âCXNameRange_WantQualifierâ aan de linker- en rechterzijde van de â|â operator. CIndex.cpp 7245
Vanwege een typefout wordt dezelfde benoembare constante twee keer gebruikt. CXNameRange_WantQualifier.
Fragment N3: Verwarring met operatorprioriteiten
int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) {
....
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0)
return 0;
....
}PVS-Studio waarschuwing: [CWE-783] Misschien werkt de â?:â operator anders dan verwacht. De â?:â operator heeft een lagere prioriteit dan de â==â operator. PPCTargetTransformInfo.cpp 404
Naar mijn mening is dit een zeer mooie fout. Ja, ik weet dat ik vreemde ideeën over schoonheid heb :)
Momenteel, volgens , wordt de expressie als volgt berekend:
(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0Praktisch gezien heeft deze voorwaarde geen zin, omdat deze kan worden vereenvoudigd tot:
(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())Dit is een duidelijke fout. Waarschijnlijk wilde men 0/1 vergelijken met de variabele Index. Om de code te corrigeren, moeten er haakjes rondom de ternary operator worden toegevoegd:
if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))Trouwens, de ternary operator is erg gevaarlijk en kan logischere fouten veroorzaken. Wees zeer voorzichtig ermee en wees niet gierig met het plaatsen van ronde haakjes. Meer over dit onderwerp heb ik besproken , in het hoofdstuk 'Vrees de operator ?: en sluit deze in haakjes'.
Fragment N4, N5: Nul pointer
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 niet casten '") + LHS->getAsString() +
"' naar string");
return nullptr;
}
....
}PVS-Studio waarschuwing: [CWE-476] Dereferencing van de null pointer âLHSâ kan plaatsvinden. TGParser.cpp 2152
Als de pointer LHS nul blijkt te zijn, moet er een waarschuwing worden gegeven. Echter, in plaats daarvan zal de dereferencing van deze nul pointer plaatsvinden: LHS->getAsString().
Dit is een zeer typische situatie waarin een fout zich verbergt in de foutafhandelaar, omdat deze niemand test. Statistische analysetools controleren alle bereikbare code, ongeacht hoe vaak deze wordt gebruikt. Dit is een zeer goed voorbeeld van hoe statische analyse andere testmethoden en foutbescherming aanvult.
Een soortgelijke fout in de pointerafhandeling RHS is gemaakt in de onderliggende code: V522 [CWE-476] Dereferencing van de null pointer âRHSâ kan plaatsvinden. TGParser.cpp 2186
Fragment N6: Gebruik van pointer na verplaatsing
static Expected
ExtractBlocks(....)
{
....
std::unique_ptr ProgClone = CloneModule(BD.getProgram(), VMap);
....
BD.setNewProgram(std::move(ProgClone)); // getFunction(MisCompFunctions[i].first); // <=
assert(NewF && "Functie niet gevonden??");
MiscompiledFunctions.push_back(NewF);
}
....
}Waarschuwing PVS-Studio: V522 [CWE-476] Dereferencing van de null pointer âProgCloneâ kan plaatsvinden. Miscompilation.cpp 601
Aan het begin verliest de slimme pointer ProgClone de eigendom van het object:
BD.setNewProgram(std::move(ProgClone));In feite is het nu ProgClone â dit is een null-pointer. Daarom moet hieronder dereferencing van de null-pointer plaatsvinden:
Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);Maar in werkelijkheid zal dit niet gebeuren! Let op, de loop wordt in feite niet uitgevoerd.
Aan het begin van de container MiscompiledFunctions wordt gewist:
MiscompiledFunctions.clear();Vervolgens wordt de grootte van deze container gebruikt in de voorwaarde van de lus:
for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {Het is gemakkelijk te zien dat de loop niet start. Ik denk dat dit ook een fout is, en de code anders geschreven zou moeten worden.
Het lijkt erop dat we de beroemde parity van fouten zijn tegengekomen! Eén fout maskeert een andere :).
Fragment N7: Gebruik van pointer na verplaatsing
static Expected TestOptimizer(BugDriver &BD, std::unique_ptr Test,
std::unique_ptr Safe) {
outs() << " Optimaliseren van functies die worden getest: ";
std::unique_ptr Optimized =
BD.runPassesOn(Test.get(), BD.getPassesToRun());
if (!Optimized) {
errs() << " Fout bij het uitvoeren van deze reeks passes"
<< " op het invoerprogramma!n";
BD.setNewProgram(std::move(Test)); // <=
BD.EmitProgressBitcode(*Test, "pass-error", false); // <=
if (Error E = BD.debugOptimizerCrash())
return std::move(E);
return false;
}
....
}Waarschuwing PVS-Studio: V522 [CWE-476] Dereferencing van de null-pointer âTestâ kan plaatsvinden. Miscompilation.cpp 709
Weer dezelfde situatie. Aan het begin wordt de inhoud van het object verplaatst, en dan wordt het gebruikt alsof er niets aan de hand is. Ik kom deze situatie steeds vaker tegen in de code van programma's, sinds C++ de verplaatsingssemantiek heeft geĂŻntroduceerd. Daarom hou ik van de taal C++! Er komen steeds nieuwe manieren om jezelf in de voet te schieten. De PVS-Studio-analyzer zal altijd werk hebben :).
Fragment N8: Null-pointer
void FunctionDumper::dump(const PDBSymbolTypeFunctionArg &Symbol) {
uint32_t TypeId = Symbol.getTypeId();
auto Type = Symbol.getSession().getSymbolById(TypeId);
if (Type)
Printer << "";
else
Type->dump(*this);
}Waarschuwing PVS-Studio: V522 [CWE-476] Dereferencing van de null-pointer âTypeâ kan plaatsvinden. PrettyFunctionDumper.cpp 233
Naast foutafhandelingshandlers worden ook functies voor het debuggen van gegevens vaak niet getest. Voor ons staat precies zo'n geval. De functie wacht op de gebruiker, die in plaats van zijn problemen op te lossen, gedwongen zal worden om deze te repareren.
Correct:
if (Type)
Type->dump(*this);
else
Printer << "";Fragment N9: Null-pointer
void SearchableTableEmitter::collectTableEntries(
GenericTable &Table, const std::vector &Items) {
....
RecTy *Ty = resolveTypes(Field.RecType, TI->getType());
if (!Ty) // getAsString() + " vs. " + // getType()->getAsString());
....
}Waarschuwing PVS-Studio: V522 [CWE-476] Dereferencing van de null pointer 'Ty' kan plaatsvinden. SearchableTableEmitter.cpp 614
Ik denk dat alles duidelijk is en geen uitleg behoeft.
Fragment N10: Typfout
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 waarschuwing: De variabele 'Identifier->Type' wordt aan zichzelf toegewezen. FormatTokenLexer.cpp 249
Het heeft geen zin om een variabele aan zichzelf toe te wijzen. Verplicht was waarschijnlijk om te schrijven:
Identifier->Type = Question->Type;Fragment N11: Verdachte 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 waarschuwing: [CWE-478] Overweeg om de 'switch'-verklaring te inspecteren. Het is mogelijk dat de eerste 'case' operator ontbreekt. SystemZAsmParser.cpp 652
Aan het begin is er een zeer verdachte operator aanwezig break. Hebben we hier niet iets vergeten te schrijven?
Fragment N12: Controle van de pointer na dereferensering
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 waarschuwing: [CWE-476] De 'Callee' pointer werd gebruikt voordat het werd gecontroleerd op nullptr. Controleer regels: 172, 174. AMDGPUInline.cpp 172
Wijzer Callee wordt in het begin gederefererd op het moment van functieaanroep getTTI.
En dan blijkt dat deze pointer op gelijkheid gecontroleerd moet worden nullptr:
if (!Callee || Callee->isDeclaration())Maar het is al te laat...
Fragment N13 â NâŠ: Controle van de pointer na dereferensering
De situatie beschreven in het vorige codefragment is niet uniek. Het komt hier voor:
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()) { // <=
....
}Waarschuwing PVS-Studio: V595 [CWE-476] De 'CalleeFn' pointer werd gebruikt voordat het werd gecontroleerd op nullptr. Controleer regels: 1079, 1081. SimplifyLibCalls.cpp 1079
En hier:
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()); // <=
....
}Waarschuwing PVS-Studio: V595 [CWE-476] De âNDâ-pointer werd gebruikt voordat deze tegen nullptr was gecontroleerd. Controleer regels: 532, 534. SemaTemplateInstantiateDecl.cpp 532
En hier:
- V595 [CWE-476] De âUâ-pointer werd gebruikt voordat deze tegen nullptr was gecontroleerd. Controleer regels: 404, 407. DWARFFormValue.cpp 404
- V595 [CWE-476] De âNDâ-pointer werd gebruikt voordat deze tegen nullptr was gecontroleerd. Controleer regels: 2149, 2151. SemaTemplateInstantiate.cpp 2149
Daarna verloor ik de interesse om waarschuwingen met nummer V595 te bestuderen. Dus ik weet niet of er nog andere soortgelijke fouten zijn, naast de hier genoemde. Waarschijnlijk zijn die er.
Fragment N17, N18: Verdachte verschuiving
static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize,
uint64_t &Encoding) {
....
unsigned Size = RegSize;
....
uint64_t NImms = ~(Size-1) << 1;
....
}PVS-Studio waarschuwing: [CWE-190] Overweeg de expressie â~(Size â 1) << 1â te inspecteren. Bitverschuiving van de 32-bits waarde met een daaropvolgende uitbreiding naar het 64-bits type. AArch64AddressingModes.h 260
Het kan zijn dat dit geen fout is, en dat de code precies werkt zoals bedoeld. Maar dit is duidelijk een zeer verdachte plek, en moet worden gecontroleerd.
Laten we aannemen dat de variabele Grootte gelijk is aan 16, en dan had de auteur van de code gepland om in de variabele NImms de waarde te krijgen:
1111111111111111111111111111111111111111111111111111111111100000
Echter, in werkelijkheid zal de waarde zijn:
0000000000000000000000000000000011111111111111111111111111100000
Het punt is dat alle berekeningen worden uitgevoerd met behulp van het 32-bits unsigned type. En pas daarna zal dit 32-bits unsigned type impliciet worden uitgebreid naar uint64_t. De hogere bits zullen dan nul zijn.
De situatie kan als volgt worden gecorrigeerd:
uint64_t NImms = ~static_cast(Size-1) << 1;Een soortgelijke situatie: V629 [CWE-190] Overweeg de expressie âImmr << 6â te inspecteren. Bitverschuiving van de 32-bits waarde met een daaropvolgende uitbreiding naar het 64-bits type. AArch64AddressingModes.h 269
Fragment N19: Sleutelwoord overgeslagen 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 gebruik "vcc" token.
// Sla het over.
continue;
} if (isRegOrImmWithInputMods(Desc, Inst.getNumOperands())) { // <=
Op.addRegWithFPInputModsOperands(Inst, 2);
} else if (Op.isDPPCtrl()) {
Op.addImmOperands(Inst, 1);
} else if (Op.isImm()) {
// Verwerk optionele argumenten
OptionalIdx[Op.getImmTy()] = I;
} else {
llvm_unreachable("Ongeldig operandtype");
}
....
}PVS-Studio waarschuwing: [CWE-670] Overweeg de logica van de applicatie te inspecteren. Het is mogelijk dat het âelseâ sleutelwoord ontbreekt. AMDGPUAsmParser.cpp 5655
Hier is geen fout. Aangezien de then-blok van de eerste als eindigt op continue, het maakt niet uit of er een trefwoord is else of niet. In elk geval zal de code op dezelfde manier werken. Toch maakt een gemiste else de code minder begrijpelijk en gevaarlijk. Als het later continue verdwijnt, zal de code helemaal anders gaan werken. Naar mijn mening is het beter om else.
Fragment N20: Vier soortgelijke typfouten
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-Studio waarschuwen:
- V655 [CWE-480] De strings zijn samengevoegd, maar worden niet gebruikt. Overweeg de expressie 'Result + Name.str()' te inspecteren. Symbol.cpp 32
- V655 [CWE-480] De strings zijn samengevoegd, maar worden niet gebruikt. Overweeg de expressie 'Result + «(ObjC Class) » + Name.str()' te inspecteren. Symbol.cpp 35
- V655 [CWE-480] De strings zijn samengevoegd, maar worden niet gebruikt. Overweeg de expressie 'Result + «(ObjC Class EH) » + Name.str()' te inspecteren. Symbol.cpp 38
- V655 [CWE-480] De strings zijn samengevoegd, maar worden niet gebruikt. Overweeg de expressie 'Result + «(ObjC IVar) » + Name.str()' te inspecteren. Symbol.cpp 41
Per ongeluk wordt de operator += in plaats van de operator + gebruikt. Dit leidt tot constructies zonder betekenis.
Fragment N21: Onbepaald gedrag
static void getReqFeatures(std::map &FeaturesMap,
const std::vector &ReqFeatures) {
for (auto &R : ReqFeatures) {
StringRef AsmCondString = R->getValueAsString("AssemblerCondString");
SmallVector Ops;
SplitString(AsmCondString, Ops, ",");
assert(!Ops.empty() && "AssemblerCondString kan niet leeg zijn");
for (auto &Op : Ops) {
assert(!Op.empty() && "Lege operator");
if (FeaturesMap.find(Op) == FeaturesMap.end())
FeaturesMap[Op] = FeaturesMap.size();
}
}
}Probeer zelf gevaarlijke code te vinden. Dit is een afbeelding ter afleiding, zodat je niet meteen naar het antwoord kijkt:

PVS-Studio waarschuwing: [CWE-758] Gevaarlijke constructie wordt gebruikt: âFeaturesMap[Op] = FeaturesMap.size()â, waar âFeaturesMapâ van de âmapâ-klasse is. Dit kan leiden tot onbepaald gedrag. RISCVCompressInstEmitter.cpp 490
Probleemregel:
FeaturesMap[Op] = FeaturesMap.size();Als element Op niet wordt gevonden, dan wordt er een nieuw element in de kaart gemaakt en daar wordt het aantal elementen in deze kaart opgeslagen. Alleen is het onduidelijk of de functie size voor of na het toevoegen van het nieuwe element wordt aangeroepen.
Fragment N22-N24: Herhaalde toewijzingen
Fout 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 waarschuwing: [CWE-563] De variabele âNTypeâ wordt twee keer opeenvolgend toegewezen. Misschien is dit een fout. Controleer de regels: 1663, 1664. MachOObjectFile.cpp 1664
Ik denk dat hier geen echte fout is. Gewoon een overbodige, herhaalde toewijzing. Maar toch een blunder.
Evenzo:
- V519 [CWE-563] De variabele âB.NDescâ wordt twee keer opeenvolgend toegewezen. Misschien is dit een fout. Controleer de regels: 1488, 1489. llvm-nm.cpp 1489
- V519 [CWE-563] De variabele wordt twee keer opeenvolgend toegewezen. Misschien is dit een fout. Controleer de regels: 59, 61. coff2yaml.cpp 61
Fragment N25-N27: Nog meer herhaalde toewijzingen
Laten we nu een iets andere variant van herhaalde toewijzing bekijken.
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;
....
}Waarschuwing PVS-Studio: V519 [CWE-563] De variabele âAlignmentâ wordt twee keer opeenvolgend toegewezen. Misschien is dit een fout. Controleer de regels: 1158, 1160. LoadStoreVectorizer.cpp 1160
Dit is een zeer vreemde code die blijkbaar een logische fout bevat. Aan het begin wordt de variabele Alignment toegewezen op basis van een voorwaarde. En vervolgens vindt opnieuw een toewijzing plaats, maar nu zonder enige controle.
Vergelijkbare situaties zijn hier te zien:
- V519 [CWE-563] De variabele âEffectsâ wordt twee keer opeenvolgend toegewezen. Misschien is dit een fout. Controleer de regels: 152, 165. WebAssemblyRegStackify.cpp 165
- V519 [CWE-563] De variabele âExpectNoDerefChunkâ wordt twee keer opeenvolgend toegewezen. Misschien is dit een fout. Controleer de regels: 4970, 4973. SemaType.cpp 4973
Fragment N28: Altijd waar voorwaarde
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 instructie ondersteuning // <=
break;
}
....
}PVS-Studio waarschuwing: [CWE-571] Expressie ânextByte != 0x90â is altijd waar. X86DisassemblerDecoder.cpp 379
De controle heeft geen zin. De variabele nextByte is altijd niet gelijk aan de waarde 0x90, wat voortkomt uit de vorige controle. Dit is een soort logische fout.
Fragment N29 â NâŠ: Altijd waar/onwaar voorwaarden
De analyzer geeft veel waarschuwingen dat de hele voorwaarde () of een deel ervan () is altijd waar of niet waar. Vaak zijn dit geen echte fouten, maar gewoon slordige code, een resultaat van macro-implementatie en dergelijke. Toch is het zinvol om al deze waarschuwingen te bekijken, aangezien er af en toe echte logische fouten optreden. Bijvoorbeeld, dit stuk code is verdacht:
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 waarschuwing: [CWE-570] Een deel van de voorwaardelijke expressie is altijd fout: RegNo == 0xe. ARMDisassembler.cpp 939
De constante 0xE is de waarde 14 in het decimale systeem. De controle RegNo == 0xe heeft geen zin, omdat als RegNo > 13, de functie zijn uitvoering zal beëindigen.
Er waren veel andere waarschuwingen met ID V547 en V560, maar net als bij , vond ik het niet interessant om deze waarschuwingen te bestuderen. Het was al duidelijk dat ik genoeg materiaal had om een artikel te schrijven :). Daarom is het onduidelijk hoeveel fouten van dit type er in LLVM kunnen worden ontdekt met PVS-Studio.
Ik geef een voorbeeld waarom het saai is om deze gebeurtenissen te bestuderen. De analyzer heeft gelijk door een waarschuwing te geven voor de volgende code. Maar dit is geen fout.
bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons,
tok::TokenKind ClosingBraceKind) {
bool HasError = false;
....
HasError = true;
if (!ContinueOnSemicolons)
return !HasError;
....
}PVS-Studio waarschuwing: V547 [CWE-570] Expressie â!HasErrorâ is altijd fout. UnwrappedLineParser.cpp 1635
Fragment N30: Verdachte 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 waarschuwing: [CWE-670] Een onvoorwaardelijke âreturnâ binnen een lus. R600OptimizeVectorRegisters.cpp 63
Dit is ofwel een fout, of een specifieke techniek die bedoeld is om iets uit te leggen aan programmeurs die de code lezen. Voor mij verklaart zo'n constructie niets en ziet er erg verdacht uit. Het is beter om het zo niet te schrijven :).
Moe? Dan is het tijd om thee of koffie te zetten.

Defecten geĂŻdentificeerd door nieuwe diagnostieken
Ik denk dat 30 gebeurtenissen van oude diagnostieken genoeg zijn. Laten we nu eens kijken naar wat interessants we kunnen vinden met nieuwe diagnostieken die in de analyzer zijn toegevoegd na de controle. In totaal zijn er in de C++ analyzer 66 algemene diagnostieken toegevoegd in deze tijd.
Fragment N31: Onbereikbare code
Fout CtorDtorRunner::run() {
....
als (auto CtorDtorMap =
ES.lookup(JITDylibSearchList({{&JD, true}}), std::move(Names),
NoDependenciesToRegister, true))
{
....
return Error::success();
} anders
return CtorDtorMap.takeError();
CtorDtorsByPriority.clear();
return Error::success();
}PVS-Studio waarschuwing: [CWE-561] Ongelooflijke code gedetecteerd. Het is mogelijk dat er een fout aanwezig is. ExecutionUtils.cpp 146
Zoals u kunt zien, eindigen beide takken van de operator als met een aanroep van de operator terug. Bijgevolg zal de container CtorDtorsByPriority nooit worden geleegd.
Fragment N32: Ongelooflijke code
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(), "onverwacht samenvattingssoort");
}
Lex.setIgnoreColonInIdentifiers(false);
return false;
}Waarschuwing PVS-Studio: V779 [CWE-561] Ongelooflijke code gedetecteerd. Het is mogelijk dat er een fout aanwezig is. LLParser.cpp 835
Interessante situatie. Laten we in eerste instantie deze plek bekijken:
return ParseTypeIdEntry(SummaryID);
break;Op het eerste gezicht lijkt het alsof er hier geen fouten zijn. Het lijkt erop dat de operator break hier overbodig is, en dat deze gewoon kan worden verwijderd. Echter, het is niet zo eenvoudig.
De analyser geeft een waarschuwing voor de regels:
Lex.setIgnoreColonInIdentifiers(false);
return false;En inderdaad, deze code is ongelooflijk. Alle gevallen in switch eindigen met een aanroep van de operator terug. En nu lijkt de zinloze eenzame break niet zo onschuldig meer! Misschien moet een van de takken eindigen op break, in plaats van op terug?
Fragment N33: Willekeurig wissen van hogere bits
unsigned getStubAlignment() override {
if (Arch == Triple::systemz)
return 8;
anders
return 1;
}
Expected
RuntimeDyldImpl::emitSection(const ObjectFile &Obj,
const SectionRef &Section,
bool IsCode) {
....
uint64_t DataSize = Section.getSize();
....
als (StubBufSize > 0)
DataSize &= ~(getStubAlignment() - 1);
....
}PVS-Studio waarschuwing: De grootte van de bitmasker is kleiner dan de grootte van de eerste operand. Dit zal leiden tot het verlies van hogere bits. RuntimeDyld.cpp 815
Let op dat de functie getStubAlignment het type unsigned. Laten we de waarde van de uitdrukking berekenen, als we aannemen dat de functie een waarde van 8 retourneert:
~(getStubAlignment() â 1)
~(8u-1)
0xFFFFFFF8âŹu
Let nu op dat de variabele DataSize een 64-bits ongekende type heeft. Dit betekent dat wanneer DataSize & 0xFFFFFFF8âŹu wordt uitgevoerd, alle dertig twee hogere bits zullen worden gewist. Waarschijnlijk is dit niet wat de programmeur voor ogen had. Ik vermoed dat hij wilde berekenen: DataSize & 0xFFFFFFFFFFFFFFF8âŹu.
Om de fout te corrigeren, moet je het als volgt schrijven:
DataSize &= ~(static_cast(getStubAlignment()) - 1);Of zo:
DataSize &= ~(getStubAlignment() - 1ULL);Fragment N34: Ongeldig expliciet typecast
template
void scaleShuffleMask(int Scale, ArrayRef Mask,
SmallVectorImpl &ScaledMask) {
assert(0 < Scale && "Onverwachte schaalfactor");
int NumElts = Mask.size();
ScaledMask.assign(static_cast(NumElts * Scale), -1);
....
}PVS-Studio waarschuwing: [CWE-190] Mogelijke overflow. Overweeg om de operand van de 'NumElts * Scale' operator naar het 'size_t' type te casten, niet de uitkomst. X86ISelLowering.h 1577
Expliciet typecast wordt gebruikt om overflow te voorkomen bij het vermenigvuldigen van variabelen van het type int. Echter, in dit geval beschermt het expliciete typecast niet tegen overflow. In het begin worden de variabelen vermenigvuldigd, en pas daarna wordt het 32-bits resultaat van de vermenigvuldiging uitgebreid naar het type .
Fragment N35: Mislukte 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] Twee soortgelijke codefragmenten zijn gevonden. Misschien is dit een typefout en moet de 'Op1' variabele in plaats van 'Op0' worden gebruikt. InstCombineCompares.cpp 5507
Deze nieuwe interessante diagnose identificeert situaties waarin een codefragment is gekopieerd, en sommige namen zijn gewijzigd, maar op één plaats is het niet gecorrigeerd.
Let op dat in het tweede blok gewijzigd is Op0 en een werkende opdracht krijgen. Op1. Maar op één plaats is het niet gecorrigeerd. Waarschijnlijk had het zo moeten zijn:
if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) {
I.setOperand(1, ConstantFP::getNullValue(Op1->getType()));
return &I;
}Fragment N36: Verwirrung in variabelen
struct Status {
unsigned Mask;
unsigned Mode;
Status() : Mask(0), Mode(0){};
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
Mode &= Mask;
};
....
};PVS-Studio waarschuwing: [CWE-563] De 'Mode' variabele wordt toegewezen maar wordt niet gebruikt aan het einde van de functie. SIModeRegister.cpp 48
Het is erg gevaarlijk om functieargumenten dezelfde namen te geven als de klasseleden. Het is heel gemakkelijk om in de war te raken. Dit is zo'n geval. Deze uitdrukking heeft geen zin:
Mode &= Mask;De functieparameter verandert. En dat is alles. Deze parameter wordt verder nergens gebruikt. Waarschijnlijk had het zo moeten zijn:
Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
this->Mode &= Mask;
};Fragment N37: Verwirrung in variabelen
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 waarschuwing: V1001 [CWE-563] De âSizeâ variabele wordt toegewezen maar niet gebruikt aan het einde van de functie. Object.cpp 424
De situatie is vergelijkbaar met de vorige. Het moet als volgt worden geschreven:
this->Size += this->EntrySize;Fragment N38-N47: De pointer is vergeten te controleren
Eerder hebben we voorbeelden van diagnose-situaties besproken . Het idee is dat de pointer in het begin wordt gederefereerd en pas daarna wordt gecontroleerd. Nieuwe diagnose is de tegengestelde hiervan in betekenis, maar ontdekt ook veel fouten. Het identificeert situaties waarin de pointer in het begin werd gecontroleerd, maar daarna vergeten is dit te doen. Laten we dergelijke gevallen bekijken die binnen LLVM zijn gevonden.
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 waarschuwing: V1004 [CWE-476] De âPtrâ pointer werd onveilig gebruikt nadat deze was gecontroleerd tegen nullptr. Controleer regels: 729, 738. TargetTransformInfoImpl.h 738
Variabele Ptr kan gelijk zijn aan nullptr, wat blijkt uit de controle:
if (Ptr != nullptr)Echter, hieronder wordt deze pointer gederefereerd zonder voorafgaande controle:
auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());Laten we een andere vergelijkbare situatie bekijken.
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 waarschuwing: V1004 [CWE-476] De âFDâ pointer werd onveilig gebruikt nadat deze was gecontroleerd tegen nullptr. Controleer regels: 3228, 3231. CGDebugInfo.cpp 3231
Let op de pointer FD. Ik ben er zeker van dat het probleem goed zichtbaar is en dat speciale uitleg niet nodig is.
En bovendien:
static void computePolynomialFromPointer(Value &Ptr, Polynomial &Result,
Value *&BasePtr,
const DataLayout &DL) {
PointerType *PtrTy = dyn_cast(Ptr.getType());
if (!PtrTy) { // getPointerAddressSpace()); // <=
....
}Waarschuwing PVS-Studio: V1004 [CWE-476] De âPtrTyâ pointer werd onveilig gebruikt nadat het was gecontroleerd tegen nullptr. Controleer de regels: 960, 965. InterleavedLoadCombinePass.cpp 965
Hoe zulke fouten te voorkomen? Wees voorzichtig bij Code-Review en gebruik de statische analyzer PVS-Studio voor regelmatige codecontroles.
Het heeft geen zin om andere codefragmenten met soortgelijke fouten te geven. Ik laat alleen de lijst met waarschuwingen in het artikel staan:
- V1004 [CWE-476] De âExprâ pointer werd onveilig gebruikt nadat het was gecontroleerd tegen nullptr. Controleer de regels: 1049, 1078. DebugInfoMetadata.cpp 1078
- V1004 [CWE-476] De âPIâ pointer werd onveilig gebruikt nadat het was gecontroleerd tegen nullptr. Controleer de regels: 733, 753. LegacyPassManager.cpp 753
- V1004 [CWE-476] De âStatepointCallâ pointer werd onveilig gebruikt nadat het was gecontroleerd tegen nullptr. Controleer de regels: 4371, 4379. Verifier.cpp 4379
- V1004 [CWE-476] De âRVâ pointer werd onveilig gebruikt nadat het was gecontroleerd tegen nullptr. Controleer de regels: 2263, 2268. TGParser.cpp 2268
- V1004 [CWE-476] De âCalleeFnâ pointer werd onveilig gebruikt nadat het was gecontroleerd tegen nullptr. Controleer de regels: 1081, 1096. SimplifyLibCalls.cpp 1096
- V1004 [CWE-476] De âTCâ pointer werd onveilig gebruikt nadat het was gecontroleerd tegen nullptr. Controleer de regels: 1819, 1824. Driver.cpp 1824
Fragment N48-N60: Niet kritiek, maar defect (mogelijke geheugenlek)
std::unique_ptr createISelMutator() {
....
std::vector<std::unique_ptr> Strategies;
Strategies.emplace_back(
new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps()));
....
}PVS-Studio waarschuwing: [CWE-460] Een pointer zonder eigenaar wordt door de âemplace_backâ methode aan de âStrategiesâ container toegevoegd. Een geheugenlek zal optreden in geval van een uitzondering. llvm-isel-fuzzer.cpp 58
Voor het toevoegen van een element aan het einde van een container van het type std::vector<std::unique_ptr> kan niet simpelweg geschreven worden als xxx.push_back(new X), aangezien er geen impliciete conversie is van X* in std::unique_ptr.
Een gebruikelijke oplossing is om te schrijven xxx.emplace_back(new X), aangezien dit compileert: de methode emplace_back constructeert het element rechtstreeks uit de argumenten en kan daarom expliciete constructors gebruiken.
Dit is onveilig. Als de vector vol is, vindt er een herallocatie plaats. De herallocatie kan mislukken, wat resulteert in een gegenereerde uitzondering std::bad_alloc. In dat geval zal de pointer verloren gaan en zal het gemaakte object nooit worden verwijderd.
Een veilige oplossing is het creëren van unique_ptr, die de pointer bezit totdat de vector probeert het geheugen opnieuw te alloceren:
xxx.push_back(std::unique_ptr(new X))Sinds C++14 kan 'std::make_unique' worden gebruikt:
xxx.push_back(std::make_unique())Dit type defect is niet kritisch voor LLVM. Als het niet lukt om geheugen toe te wijzen, zal de werking van de compiler eenvoudig worden gestopt. Voor applicaties met een lange , die niet zomaar kunnen stoppen als het geheugen niet kan worden toegewezen, kan dit echter een echt vervelend probleem zijn.
Dus, hoewel deze code geen praktische bedreiging voor LLVM vormt, vond ik het nuttig om over dit foutpatroon te vertellen en dat de PVS-Studio-analyzer heeft geleerd dit te detecteren.
Andere waarschuwingen van dit type:
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'Passes' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. PassManager.h 546
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'AAs' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. AliasAnalysis.h 324
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'Entries' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. DWARFDebugFrame.cpp 519
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'AllEdges' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. CFGMST.h 268
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'VMaps' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. SimpleLoopUnswitch.cpp 2012
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'Records' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. FDRLogBuilder.h 30
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'PendingSubmodules' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. ModuleMap.cpp 810
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'Objects' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. DebugMap.cpp 88
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'Strategies' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. llvm-isel-fuzzer.cpp 60
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'Modifiers' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. llvm-stress.cpp 685
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'Modifiers' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. llvm-stress.cpp 686
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'Modifiers' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. llvm-stress.cpp 688
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'Modifiers' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. llvm-stress.cpp 689
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'Modifiers' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. llvm-stress.cpp 690
- V1023 [CWE-460] Een pointer zonder eigenaar is toegevoegd aan de 'Modifiers' container door de 'emplace_back' methode. Een geheugenlek zal optreden in geval van een uitzondering. llvm-stress.cpp 691
- V1023 [CWE-460] Een pointer zonder eigenaar wordt toegevoegd aan de âModifiersâ container door de âemplace_backâ methode. Een geheugenlek treedt op in het geval van een uitzondering. llvm-stress.cpp 692
- V1023 [CWE-460] Een pointer zonder eigenaar wordt toegevoegd aan de âModifiersâ container door de âemplace_backâ methode. Een geheugenlek treedt op in het geval van een uitzondering. llvm-stress.cpp 693
- V1023 [CWE-460] Een pointer zonder eigenaar wordt toegevoegd aan de âModifiersâ container door de âemplace_backâ methode. Een geheugenlek treedt op in het geval van een uitzondering. llvm-stress.cpp 694
- V1023 [CWE-460] Een pointer zonder eigenaar wordt toegevoegd aan de âOperandsâ container door de âemplace_backâ methode. Een geheugenlek treedt op in het geval van een uitzondering. GlobalISelEmitter.cpp 1911
- V1023 [CWE-460] Een pointer zonder eigenaar wordt toegevoegd aan de âStashâ container door de âemplace_backâ methode. Een geheugenlek treedt op in het geval van een uitzondering. GlobalISelEmitter.cpp 2100
- V1023 [CWE-460] Een pointer zonder eigenaar wordt toegevoegd aan de âMatchersâ container door de âemplace_backâ methode. Een geheugenlek treedt op in het geval van een uitzondering. GlobalISelEmitter.cpp 2702
Conclusie
In totaal heb ik 60 waarschuwingen genoteerd, waarna ik stopte. Zijn er andere defecten die de PVS-Studio analyser in LLVM vindt? Ja, die zijn er. Echter, terwijl ik codefragmenten voor het artikel noteerde, was het al laat in de avond, of beter gezegd, zelfs nacht, en besloot ik dat het tijd was om te stoppen.
Ik hoop dat je het interessant vond en dat je de PVS-Studio analyser wilt uitproberen.
Je kunt de analyser downloaden en een proeflicentie verkrijgen op .
Het belangrijkste is om statische analyse regelmatig te gebruiken. Eenmalige controles, uitgevoerd door ons om de methodologie van statische analyse en PVS-Studio te populariseren, zijn geen normaal scenario.
Veel succes met het verbeteren van de kwaliteit en betrouwbaarheid van de code!
Als je dit artikel wilt delen met een Engelstalig publiek, gebruik dan alstublieft de link naar de vertaling: Andrey Karpov. .
Bron: habr.com
